如何一键挽救父母和孩子(JPA和Hibernate)

时间:2018-12-06 08:52:15

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

我开始向您展示我的情况。

这是我的父对象:

@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”。

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)这是 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]

我认为该错误取决于以下事实:当休眠尝试保存购物车的 cart_item id 还不存在!

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

6 个答案:

答案 0 :(得分:5)

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

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

映射问题:

  • 从两个实体中删除@Column(name="id")
  • cartItems 私有
  • 设置代理。由于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 repositories避免类似方法save的样板

答案 1 :(得分:3)

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

  1. 将级联设置为持久或全部
  2. 删除@OnToMany 中的mappedBy 属性
  3. 在两边写@JoinCloumn(否则会创建Join Table)同名
  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;

}

答案 2 :(得分:1)

您是否检查过此帖子? Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

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

实际上,当一个线程/会话将一个对象保存在数据库中时,如果另一个线程/会话尝试执行相同的操作,则将引发此类错误,因为id已经存在,因此该操作是不可能的。

干杯!

答案 3 :(得分:1)

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

注释存储库界面