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

新手上路,请多包涵

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

这是我的父对象:

 @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 具有指向表 cart (父对象)的字段 id 的外键。

在此处输入图像描述

这是我保存对象的方式:

  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);
    }
}

  1. 这是 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_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 尚不存在!

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

原文由 MDP 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 599
2 个回答

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

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

映射问题:

  • 从两个实体中删除 @Column(name="id")
  • cartItems 制作 setter --- private 。由于 Hibernate 使用它自己的 List 的实现,你永远不应该通过 setter 直接改变它
  • 初始化你的列表 private List<CartItem> cartItems = new ArrayList<>();
  • 使用 @ManyToOne(optional = false) 代替 nullable = false@JoinColumn
  • 更喜欢 fetch = FetchType.LAZY 收藏
  • 最好使用辅助方法来设置关系。例如类 Cart 应该有一个方法:
   public void addCartItem(CartItem item){
      cartItems.add(item);
      item.setCart(this);
  }

设计问题:

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

原文由 Taras Boychuk 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

  1. 将级联设置为持久或全部
  2. 如果存在,则删除 @OnToMany 中的 mappedBy 属性
  3. 在两边写@JoinCloumn(否则创建Join Table)同名
  4. Remove (nullable = false) in @JoinColumn(因为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;

}

原文由 Ali Sadeghi 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题