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

MDP*_*MDP 5 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; 

    ...
}
Run Code Online (Sandbox Code Playgroud)

这是我的孩子对象:

@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;

    ...
}
Run Code Online (Sandbox Code Playgroud)

正如您看到数据库一样,在表cart_item(子对象)中,字段cart_id具有指向表cart(父对象)的字段ID的外键。

在此处输入图片说明

这是我保存对象的方式:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

2)这是CartService,仅是一个接口

public interface CartService {  
    void create(CartDto cartDto); 
}
Run Code Online (Sandbox Code Playgroud)

这是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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

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);                  
    }
}
Run Code Online (Sandbox Code Playgroud)

当我尝试保存新购物车及其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]
Run Code Online (Sandbox Code Playgroud)

我猜想误差取决于事实,当Hibernate的尝试保存一个cart_item,该ID的的还不存在!

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

Ali*_*ghi 12

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

  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;

}
Run Code Online (Sandbox Code Playgroud)


Tar*_*ras 11

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

  • 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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

设计问题:

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