Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 229
MDP Avatar asked Dec 06 '18 08:12

MDP


People also ask

How do you update parent and child records in JPA?

My parent class : @Entity @DynamicUpdate @Table(name = "person") public class Person { @Id @GeneratedValue(strategy = GenerationType. IDENTITY) @Column(name="person_pk", nullable=false) private Long personPK; @OneToMany(fetch = FetchType. EAGER, mappedBy = "person",cascade = CascadeType.

How Save method works in JPA?

save(…)- Method. It will persist or merge the given entity using the underlying JPA EntityManager. If the entity has not been persisted yet Spring Data JPA will save the entity via a call to the entityManager.

What is the difference between save and saveAndFlush in JPA?

Save and saveAndFlush both can be used for saving entities. They both are both belong to the Spring data library. save may or may not write your changes to the DB straight away. When we call saveAndFlush system are enforcing the synchronization of your model state with the DB.

What is findById in JPA?

The findById() method is used to retrieves an entity by its id and it is available in CrudRepository interface. The CrudRepository extends Repository interface. In Spring Data JPA Repository is top-level interface in the hierarchy.


1 Answers

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 the List, and you should never change it directly via setter
  • initialize you list private List<CartItem> cartItems = new ArrayList<>();
  • use @ManyToOne(optional = false) instead of nullable = 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
like image 186
Taras Boychuk Avatar answered Nov 14 '22 09:11

Taras Boychuk