如何一次性保存父母和孩子(JPA和Hibernate)

2022-09-04 05:12:03

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

这是我的父对象:

@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 的外键。

enter image description here

这是我保存对象的方式:

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

@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)这是购物车服务,这只是一个接口

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_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时,购物车ID还不存在!

在拍摄时保存父对象及其子对象的正确方法是什么?谢谢


答案 1

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

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

映射问题:

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

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

设计问题:

  • 将 DTO 传递到 DAO 层是不好的。最好在 DTO 和实体之间进行转换,甚至高于服务层。
  • 最好避免使用Spring Data JPA存储库的类似样板方法save

答案 2

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

  1. 将级联设置为“持久”或“全部”
  2. 删除映射的 by 属性在 @OnToMany 如果存在
  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;

}

推荐