'How to save parent and child in one shot (JPA & Hibernate)

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).

enter image description here

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



Solution 1:[1]

For bidirectional relation act like below:

  1. Set cascade to persist or All
  2. Remove mappedBy attribute in @OnToMany if there is
  3. Write @JoinCloumn at both sides (otherwise it creates Join Table) with the same name
  4. Remove (nullable = false) in @JoinColumn (because Hibernate first inserts the parent record then inserts child records and after all, updates the foreign key in child records)

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;

}

Solution 2:[2]

Make sure that your method is Transactional. you can make method Transactional using @Transactional annotation on top of method signature.

Solution 3:[3]

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

Solution 4:[4]

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!

Solution 5:[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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Alien
Solution 3 sqzaman
Solution 4 Palencar
Solution 5 gmode