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

问题描述 投票:2回答:4

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

这是我的父对象:

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

enter image description here

这是我保存对象的方式:

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_items时,我收到此错误:

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还不存在!

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

java hibernate jpa one-to-many many-to-one
4个回答
6
投票

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

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

映射问题:

  • 从两个实体中删除@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和实体之间的转换。
  • 最好避免像save这样的样板Spring Data JPA repositories这样的样板

1
投票

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

你可以在这个中找到一个合适的答案,我认为你的问题来自你的getCurrentSession,即使你使用会话因为hibernate不是线程安全的,一个会话仍然是一个轻量级和一个非线程安全的对象。你应该从这里挖掘一些东西。

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

干杯!


1
投票

确保您的方法是Transactional。你可以在方法签名之上使用@Transactional注释创建方法Transactional。


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.