【问题标题】:How to save parent and child in one shot (JPA & Hibernate)如何一次性保存父母和孩子(JPA和Hibernate)
【发布时间】:2018-12-06 08:52:14
【问题描述】:

我开始向你展示我的场景。

这是我的父对象:

@Entity
@Table(name="cart")
public class Cart implements Serializable{  

    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Id
    @Column(name="id")
    private Integer id; 

    @OneToMany(mappedBy="cart", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<CartItem> cartItems; 

    ...
}

这是我的子对象:

@Entity
@Table(name="cart_item")
public class CartItem implements Serializable{  

    @GeneratedValue(strategy=GenerationType.IDENTITY)   
    @Id
    @Column(name="id")
    private Integer id;     

    @ManyToOne
    @JoinColumn(name="cart_id", nullable=false)
    private Cart cart;

    ...
}

正如您在查看数据库时看到的那样,在表 cart_item(子对象)中,字段 cart_id 具有字段 id 的外键strong> 表 cart(父对象)。

这是我保存对象的方式:

1) 有一个读取 JSON 对象的 restController

@RestController
@RequestMapping(value = "rest/cart")
public class CartRestController {

    @Autowired
    private CartService cartService;    

    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(value = HttpStatus.CREATED)
    public void create(@RequestBody CartDto cartDto) {
        cartService.create(cartDto);
    }
}

2) 这是 CartService,它只是一个接口

public interface CartService {  
    void create(CartDto cartDto); 
}

这是 CartService 的实现:

import org.springframework.transaction.annotation.Transactional;

    @Service
    @Transactional
    public class CartServiceImpl implements CartService {   
        @Autowired
        private CartDao cartDao;

        @Override
        public void create(CartDto cartDto) {
            cartDao.create(cartDto);
        }
    }

CartDao 只是另一个接口,我只展示它的实现:

@Repository
public class CartDaoImpl implements CartDao {

    @Autowired 
    private SessionFactory sessionFactory;

    // in this method I save the parent and its children
    @Override
    public void create(CartDto cartDto) {       

        Cart cart = new Cart(); 

        List<CartItem> cartItems = new ArrayList<>();                   

        cartDto.getCartItems().stream().forEach(cartItemDto ->{     
            //here I fill the CartItem objects;     
            CartItem cartItem = new CartItem();         
            ... 
            cartItem.setCart(cart);
            cartItems.add(cartItem);                
        });
        cart.setCartItems(cartItems);

        sessionFactory.getCurrentSession().save(cart);                  
    }
}

当我尝试保存新的 cart 及其 cart_item 时,我收到此错误:

SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/webstore] threw 
exception [Request processing failed; nested exception is 
org.springframework.orm.hibernate5.HibernateOptimisticLockingFailureException: Object of 
class     
[com.depasmatte.webstore.domain.CartItem] with identifier [7]: optimistic locking failed; 
nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by 
another transaction (or unsaved-value mapping was incorrect) : 
[com.depasmatte.webstore.domain.CartItem#7]] with root cause
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction
 (or unsaved-value mapping was incorrect) : [com.depasmatte.webstore.domain.CartItem#7]

我想错误取决于当 Hibernate 尝试保存 cart_item 时,cart 的 id 还不存在!

在镜头中保存父对象及其子对象的正确方法是什么? 谢谢

【问题讨论】:

  • 相关的代码可能就是你构建和保存数据的地方。
  • 方法是事务性的吗?
  • 嗨@AlanHay,我编辑了我的代码,现在应该更清楚了
  • 嗨@Alien,我编辑了我的代码。如您所见,类 CartServiceImpl 是事务性的
  • 如果我有 Set 而不是 List 怎么办?它的 Set,而不是 HashSet,所以我不能 dp mySet.add()

标签: java hibernate jpa one-to-many many-to-one


【解决方案1】:

这是您应该遵循的规则列表,以便能够一次性存储父实体及其子实体:

  • 应该启用级联类型PERSISTCascadeType.ALL 也可以)
  • 双向关系应在双方正确设置。例如。 parent 在其集合字段中包含所有子项,并且每个子项都有对其父项的引用。
  • 数据操作在事务范围内执行。 不允许使用自动提交模式。
  • 仅应手动保存父实体(由于级联模式,子实体将自动保存)

映射问题:

  • 从两个实体中删除 @Column(name="id")
  • cartItems私人制作setter。由于 Hibernate 使用自己的 List 实现,因此您永远不应该通过 setter 直接更改它
  • 初始化你的列表private List&lt;CartItem&gt; cartItems = new ArrayList&lt;&gt;();
  • @JoinColumn 中使用@ManyToOne(optional = false) 而不是nullable = false
  • 收藏首选fetch = FetchType.LAZY
  • 最好使用辅助方法来设置关系。例如。 Cart 类应该有一个方法:

    public void addCartItem(CartItem item){
        cartItems.add(item);
        item.setCart(this);
    }
    

设计问题:

  • 将 DTO 传递给 DAO 层并不好。最好在服务层之上做 DTO 和实体之间的转换。
  • 最好避免使用saveSpring Data JPA repositories 这样的样板方法

【讨论】:

  • CascadeType.PERSIST(或 CascadeType.ALL)是我所缺少的。为这个答案添加书签,因为它有所有的陷阱。谢谢。
  • 我有一个个人模式可以解决您的 dto-design-issue。到 SepOfConcerns 的翻译。 public interface InternalMyThingJpaRepository extends JpaRepository { /* spring data ONLY methods / } ... public class MyThingJpaRepository implements IMyThingJpaRepository { @Inject public MyThingJpaRepository(InternalMyThingJpaRepository springDataDeptRepo, IMyThingEntityDtoConverter / org.modelmapper.ModelMapper */ myThingConverter) { } MyThingJpa接受“Dtos”..转换为 Jpa 实体并使用 InternalMyThingJpaRepository
【解决方案2】:

对于双向关系,如下所示:

  1. 将级联设置为持久或全部
  2. 删除 @OnToMany 中的 mappedBy 属性(如果有)
  3. 在两边写@JoinCloumn(否则会创建连接表)同名
  4. 在@JoinColumn 中删除(nullable = false)(因为Hibernate 先插入父记录,再插入子记录,毕竟更新子记录中的外键)

这里是示例代码:

public class Parent {

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "fk_parent")
    private List<Child> children;

}

public class Child {

    @ManyToOne
    @JoinColumn(name = "fk_parent")
    private Parent parent;

}

【讨论】:

    【解决方案3】:

    确保您的方法是事务性的。您可以在方法签名之上使用 @Transactional 注释使方法事务化。

    【讨论】:

    • 嗨@Alien,我编辑了我的代码。如您所见,类 CartServiceImpl 是事务性的
    【解决方案4】:

    讨论中显然遗漏了一个对这个问题非常重要的重要事情,即谁拥有这种关系。 您将 mappedBy 放在父实体中,这意味着此关系的所有者转到子实体,他必须通过显式设置属性来填充此关系,否则将不会建立此关系。在Parent上面加上JoinColumn注解,保证关系的拥有者是父,当父实体自动保存时,他会建立这个关系

    【讨论】:

      【解决方案5】:

      你看过这篇文章了吗? Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

      您可能会在此找到合适的答案,我认为您的问题来自您的 getCurrentSession,即使您使用会话,因为休眠不是线程安全的,会话仍然是轻量级且非线程安全的对象。你应该从这里挖点东西。

      事实上,当一个线程/会话在数据库中保存一个对象时,如果另一个线程/会话尝试相同的操作,它会引发这种错误,因为 id 已经存在,所以操作是不可能的。

      干杯!

      【讨论】:

        【解决方案6】:

        我知道这与问题没有直接关系,因为使用了服务,但是当我遇到类似问题时,谷歌将我带到了这里。就我而言,我使用的是 Spring JPA 存储库。确保使用 @org.springframework.transaction.annotation.Transactional

        注释存储库接口

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-03-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-05-13
          • 1970-01-01
          • 2022-01-01
          • 2012-10-16
          相关资源
          最近更新 更多