问题
I start showing you my scenario.
This is my 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;
...
}
This is my 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;
...
}
As you can see looking at the database, in the table cart_item (child object) the field cart_id has a foreign key to the field id of the table cart (parent object).
This is how I save the object:
1) there's a restController that reads a 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) This is the CartService, that's just an Interface:
public interface CartService {
void create(CartDto cartDto);
}
This is the implementation of 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 is just another interface, I show you only its implementation:
@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);
}
}
When I try to save a new cart and its cart_items I get this error:
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]
I suppose the error depends on the fact that when Hibernate try to save the a cart_item, the id of the cart doesn't exist yet!
What's the correct way to save a parent object and its childer in on shot? Thank you
回答1:
Here's the list of rules you should follow, in order to be able to store a parent entity along with its children in a one shot:
- cascade type
PERSIST
should be enabled (CascadeType.ALL
is also fine) - a bidirectional relationship should be set correctly on both sides. E.g. parent contains all children in its collection field and each child has a reference to its parent.
- data manipulation is performed in the scope of a transaction. NO AUTOCOMMIT MODE IS ALLOWED.
- only parent entity should be saved manually (children will be saved automatically because of the cascade mode)
Mapping issues:
- remove
@Column(name="id")
from both entities - make setter for
cartItems
private. Since Hibernate is using its own implementation of theList
, and you should never change it directly via setter - initialize you list
private List<CartItem> cartItems = new ArrayList<>();
- use
@ManyToOne(optional = false)
instead ofnullable = false
inside the@JoinColumn
- prefer
fetch = FetchType.LAZY
for collections it's better to use helper method for setting relationships. E.g. class
Cart
should have a method:public void addCartItem(CartItem item){ cartItems.add(item); item.setCart(this); }
Design issues:
- it's not good to pass DTOs to the DAO layer. It's better to do the conversion between DTOs and entities even above the service layer.
- it's much better to avoid such boilerplate like method
save
with Spring Data JPA repositories
回答2:
Make sure that your method is Transactional. you can make method Transactional using @Transactional
annotation on top of method signature.
回答3:
Did you checked this post? Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
You may find an appropriate answer in this one, I think your problem is coming from your getCurrentSession, even if you use sessions because hibernate is not thread-safe, A session is still a light weight and a non-threadsafe object. You should dig something from here.
In fact when one thread/session save an object in database, if another one try the same operation it will raise this kind of error because id's already exists so the operations is impossible.
Cheers!
回答4:
One important thing is clearly missing from the discussion that is very important to this question that is who is owning this relation. you are putting mappedBy in the parent entity that means the owner of this relation goes to child entity, he has to fill up this relation by explicitly setting property otherwise this relation ship won't be built. Put JoinColumn annotation on top of Parent, it will ensure relation owner is parent, he will establish this relation when the parent entity is saved automatically
回答5:
I know this is not directly relevant to the question, since services are used, but google brought me here when I had a similar problem. In my case I was using Spring JPA repositories. Make sure you annotate repository interface with @org.springframework.transaction.annotation.Transactional
来源:https://stackoverflow.com/questions/53647672/how-to-save-parent-and-child-in-one-shot-jpa-hibernate