Search code examples
jpaspring-data-jpaentitymanager

Spring data JPA save vs JPA managed objects


I'm wondering where the actual behaviour of Spring Data JPA is documented, as it happens to be different from what can be expected in regular JPA with an entity manager. I would be interested to find a documentation on the life-cycle of entities in Spring JPA.

For instance, suppose we have three entities, Message, Author and Label. A message can have multiple labels, but only one author.

So Message is basically :

@Entity
public class Message implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    @ManyToMany
    private Set<Label> labels = new HashSet<>();
    @ManyToOne
    private Author author;
    ...
}

Links are unidirectional.

Consider the following code in a service :

  Author a = new Author("auteur A");
  a = authorRepository.save(a);
  Message msg = new Message("un message", "texte du message", a);
  msg = messageRepository.save(msg);            
  for (int i=0; i < 10; i++) {
    Label lab = new Label("label"+i);
    lab = labelRepository.save(lab);
    labelRepository.flush();                
    msg.addLabel(lab);
  }            
  messageRepository.save(msg);

If I omit the last line (messageRepository.save(msg)), the labels are created, but they are not really added to a message. I find this unexpected, considering the underlying technology is JPA:

The equivalent code for standard JPA with Entity manager would be:

 EntityManagerFactory emf = Persistence.createEntityManagerFactory("demo1PU");
 EntityManager em = emf.createEntityManager();
 EntityTransaction transaction = em.getTransaction();
 transaction.begin();
 Author a = new Author("auteur A");
 em.persist(a);
 Message msg = new Message("un message", "texte du message", a);
 em.persist(msg);            
 for (int i=0; i < 10; i++) {
   Label lab = new Label("label"+i);
   em.persist(lab);              
   msg.addLabel(lab);
 }            
 transaction.commit();        
 em.close();
 emf.close();

In the entitymanager-based code, you don't need to save the message twice : as you are still in the same transaction, the message is a managed entity, and all changes to this object made while the transaction is active are also made to the database entries.

Apparently, the entities managed by Spring are a bit different from those manipulated by regular EntityManagers. But is there some explicit documentation somewhere ? The spring-data-jpa-reference.pdf file doesn't help much.


Solution

  • Ok, just to avoid people having hard time figuring what the problem is:

    The offending code was in fact the following :

     @Transactional
     @PostConstruct
     public void initDatabase() {        
            if (messageRepository.count() == 0) {
                Auteur a = new Auteur("auteur A");
                a = auteurRepository.save(a);
                Message msg = new Message("un message", "texte du message", a);
                msg = messageRepository.save(msg); // (pas nécessaire, vu qu'on le fait à la fin de la méthode...)
                for (int i = 0; i < 10; i++) {
                    Label lab = new Label("label" + i);
                    lab = labelRepository.save(lab);
                    msg.addLabel(lab);
                }          
            }
        }
    

    And the problem is that when @PostConstruct annotated methods are called, dependency injection has been done, which is good, but the whole application context is not ready, so the code was not executed in a transaction.

    Simply using

     @Transactional
     @EventListener(ContextRefreshedEvent.class)
     public void initDatabase() {        
      ...
     }
    

    Instead solves the problem.