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