Reputation: 4267
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
Upvotes: 23
Views: 48597
Reputation: 322
For bidirectional relation act like below:
Here is the sample code:
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;
}
Upvotes: 12
Reputation: 3740
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
Upvotes: 0
Reputation: 639
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
Upvotes: 4
Reputation: 2439
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:
PERSIST
should be enabled (CascadeType.ALL
is also fine)Mapping issues:
@Column(name="id")
from both entitiescartItems
private. Since Hibernate is using its own implementation of the List
, and you should never change it directly via setter private List<CartItem> cartItems = new ArrayList<>();
@ManyToOne(optional = false)
instead of nullable = false
inside the @JoinColumn
fetch = FetchType.LAZY
for collectionsit'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:
save
with Spring Data JPA repositoriesUpvotes: 51
Reputation: 15878
Make sure that your method is Transactional. you can make method Transactional using @Transactional
annotation on top of method signature.
Upvotes: 2
Reputation: 159
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!
Upvotes: 1