如何一次性保存 parent 和 child(JPA 和 Hibernate)
How to save parent and child in one shot (JPA & Hibernate)
我开始向您展示我的场景。
这是我的 parent object:
@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;
...
}
这是我的 child object:
@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;
...
}
正如您查看数据库所见,在 table cart_item (child object) 字段中cart_id 有一个外键到 table cart[=60= 的 id 字段] (parent object).
我就是这样保存 object:
1) 有一个 restController 读取 JSON object:
@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,它只是一个 Interface:
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 时,[= 购物车 的 46=]id 还不存在!
在镜头中保存 parent object 及其 child 的正确方法是什么?
谢谢
你检查过这个post吗? Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
你可能会在这找到一个合适的答案,我认为你的问题来自你的getCurrentSession,即使你使用session因为hibernate不是线程安全的,A session仍然是轻量级和非线程安全的目的。你应该从这里挖掘一些东西。
事实上,当一个thread/session在数据库中保存一个对象时,如果另一个尝试相同的操作,它会引发这种错误,因为id已经存在,所以操作是不可能的。
干杯!
确保您的方法是事务性的。您可以在方法签名之上使用 @Transactional
注释使方法成为事务性的。
这是您应该遵循的规则列表,以便能够将 parent 实体与其 children 一起存储在一个射击:
- 应启用级联类型
PERSIST
(CascadeType.ALL
也可以)
- 一个双向关系应该在双方上正确设置。例如。 parent 在其 collection 字段中包含所有 children,并且每个 child 都有对其 parent.
的引用
- 数据操作是在事务范围内执行的。 不允许自动提交模式。
- 只有parent个实体需要手动保存(children因为级联模式会自动保存)
映射问题:
- 从两个实体中删除
@Column(name="id")
- 为
cartItems
私有setter。由于 Hibernate 使用其自己的 List
实现,因此您永远不应通过 setter 直接更改它
- 初始化你的列表
private List<CartItem> cartItems = new ArrayList<>();
- 在
@JoinColumn
中使用 @ManyToOne(optional = false)
而不是 nullable = false
- 更喜欢
fetch = FetchType.LAZY
collections
设置关系最好使用辅助方法。例如。 class Cart
应该有一个方法:
public void addCartItem(CartItem item){
cartItems.add(item);
item.setCart(this);
}
设计问题:
- 把DTO传给DAO层不好。最好在服务层之上进行DTO和实体之间的转换。
- 最好避免像方法
save
和 Spring Data JPA repositories 这样的样板文件
讨论中显然遗漏了一件对这个问题非常重要的重要事情,即谁拥有这种关系。
您将 mappedBy 放在父实体中,这意味着此关系的所有者转到子实体,他必须通过显式设置 属性 来填充此关系,否则将不会建立此关系船。将 JoinColumn 注解放在 Parent 之上,它将确保关系所有者是父实体,他将在自动保存父实体时建立此关系
我知道这与问题没有直接关系,因为使用了服务,但是当我遇到类似问题时 google 把我带到了这里。就我而言,我使用的是 Spring JPA 存储库。确保使用 @org.springframework.transaction.annotation.Transactional
注释存储库接口
双向关系如下:
- 将级联设置为持续或全部
- 删除@OnToMany 中的mappedBy 属性
- 在两边写上@JoinCloumn(否则创建同名的Join Table)
- 去掉@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;
}
我开始向您展示我的场景。
这是我的 parent object:
@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;
...
}
这是我的 child object:
@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;
...
}
正如您查看数据库所见,在 table cart_item (child object) 字段中cart_id 有一个外键到 table cart[=60= 的 id 字段] (parent object).
我就是这样保存 object:
1) 有一个 restController 读取 JSON object:
@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,它只是一个 Interface:
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 时,[= 购物车 的 46=]id 还不存在!
在镜头中保存 parent object 及其 child 的正确方法是什么? 谢谢
你检查过这个post吗? Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
你可能会在这找到一个合适的答案,我认为你的问题来自你的getCurrentSession,即使你使用session因为hibernate不是线程安全的,A session仍然是轻量级和非线程安全的目的。你应该从这里挖掘一些东西。
事实上,当一个thread/session在数据库中保存一个对象时,如果另一个尝试相同的操作,它会引发这种错误,因为id已经存在,所以操作是不可能的。
干杯!
确保您的方法是事务性的。您可以在方法签名之上使用 @Transactional
注释使方法成为事务性的。
这是您应该遵循的规则列表,以便能够将 parent 实体与其 children 一起存储在一个射击:
- 应启用级联类型
PERSIST
(CascadeType.ALL
也可以) - 一个双向关系应该在双方上正确设置。例如。 parent 在其 collection 字段中包含所有 children,并且每个 child 都有对其 parent. 的引用
- 数据操作是在事务范围内执行的。 不允许自动提交模式。
- 只有parent个实体需要手动保存(children因为级联模式会自动保存)
映射问题:
- 从两个实体中删除
@Column(name="id")
- 为
cartItems
私有setter。由于 Hibernate 使用其自己的List
实现,因此您永远不应通过 setter 直接更改它
- 初始化你的列表
private List<CartItem> cartItems = new ArrayList<>();
- 在
@JoinColumn
中使用 - 更喜欢
fetch = FetchType.LAZY
collections 设置关系最好使用辅助方法。例如。 class
Cart
应该有一个方法:public void addCartItem(CartItem item){ cartItems.add(item); item.setCart(this); }
@ManyToOne(optional = false)
而不是 nullable = false
设计问题:
- 把DTO传给DAO层不好。最好在服务层之上进行DTO和实体之间的转换。
- 最好避免像方法
save
和 Spring Data JPA repositories 这样的样板文件
讨论中显然遗漏了一件对这个问题非常重要的重要事情,即谁拥有这种关系。 您将 mappedBy 放在父实体中,这意味着此关系的所有者转到子实体,他必须通过显式设置 属性 来填充此关系,否则将不会建立此关系船。将 JoinColumn 注解放在 Parent 之上,它将确保关系所有者是父实体,他将在自动保存父实体时建立此关系
我知道这与问题没有直接关系,因为使用了服务,但是当我遇到类似问题时 google 把我带到了这里。就我而言,我使用的是 Spring JPA 存储库。确保使用 @org.springframework.transaction.annotation.Transactional
注释存储库接口双向关系如下:
- 将级联设置为持续或全部
- 删除@OnToMany 中的mappedBy 属性
- 在两边写上@JoinCloumn(否则创建同名的Join Table)
- 去掉@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;
}