JPA Mapping des relation Supporting tagline
@OneToMany
unidirectionnel
@Test
public void createGameEntity_validOne() {
Question q = new Question("Question", "Réponse");
Game g1 = new Game("test_game");
g1.addQuestion(q);
gameDao.save(g1);
}
1er mapping
@Entity
class Game {
...
@OneToMany
private Set<Question> questions = new HashSet<Question>();
}
Booooommmm
StackTrace :
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: net.jidify.jpa.entities.Question; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: net.jidify.jpa.entities.Question
Hummm …
org.hibernate.TransientObjectException
… save the transient instance before flushing: net.jidify.jpa.entities.Question
Hey Hey …. Cascade Persist
2eme mapping : cascading
Génial, ça marche !!!
...
@OneToMany(cascade = CascadeType.ALL)
private Set<Question> questions = new HashSet<Question>();
Génial, ça marche !!!
Regardons de plus prés
Voyons voir le détail dans les logs
spring.jpa.properties.hibernate.show_sql=true
logging.level.org.hibernate.type=TRACE
logging.level.org.hibernate.type
permet de voir la valeur des paramètres des requètes.
Donc, dans ces logs ….
insert into GAME (id, name) values (default, ?)
insert into QUESTION (id, answer, text) values (default, ?, ?)
insert into GAME_QUESTION (game_id, questions_id) values (?, ?)
3
insert
… je pensais en avoir seulement 2 !!
3 tables, dont cette table de jointure GAME_QUESTION
alors qu’il me semble qu’avec 2 tables, ça serait largement suffisant :
Mais je peux le faire avec un @JoinColumn ….
3eme mapping : joinColumn
Génial, ça marche !!!
...
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "GAME_ID")
private Set<Question> questions = new HashSet<Question>();
J’ai bien mes 2 tables !!
###Regardons de plus prés
Dans ces logs ….
insert into GAME (id, name) values (default, ?)
insert into QUESTION (id, answer, text) values (default, ?, ?)
update QUESTION set game_id=? where id=?
Problème = 2 insert (trés bien) + 1 update
OK …. je vois que l’insert
dans QUESTION ne comprend pas le game_id.
A priori, me dis-je, si je peux dire à Hibernate de rajouter le game_id au moment de l’insert
, ça devrai être bon.
et si j’interdisais la possibilité de ne pas avoir de game_id …
4eme mapping : nullable = false
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "GAME_ID", nullable = false)
private Set<Question> questions = new HashSet<Question>();
En rajoutant nullable = false
à @JoinColumn
, j’espère avoir le résultat escompté.
Verdict :
insert into GAME (id, name) values (default, ?)
insert into QUESTION (id, answer, text, game_id) values (default, ?, ?, ?)
update QUESTION set game_id=? where id=?
j’ai bien le game_id dans l’
insert
, mais j’ai encore un update
(en trop à mon avis)!!
J’ai beau chercher partout, je ne trouve pas de solution satisfaisante. La seule solution consiste à ajouté
updatable = false
à @JoinColumn
:
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "GAME_ID", nullable = false, updatable = false)
private Set<Question> questions = new HashSet<Question>();
Mais cela revient à dire que l’association Game-Question est non-modifiable (immutable) , ce qui n’est pas vraiment ce que j’avais en tête au début!!
De plus, lors de mes recherches je tombe sur cette mise en garde dans la documentation d’Hibernate :
“A unidirectional one to many using a foreign key column in the owned entity is not that common and not really recommended.”
- Hibernate unidirectional one to many association - why is a join table better?
- Documentation Hibernate (2.2.5.3.1.2. Unidirectional)
OneToMany unidirectionnel : une mauvaise idée ?
Comme j’ai toujours entendu dire que les associations bi-directionnelles étaient rarement une bonne idée, je suis étonné qu’il n’y ai pas de solution (comme une petite annotation propriétatire d’Hibernate) qui me permette d’avoir ce gain en performance (2 insert
- point barre)!
En fait, le mal est plus profond. Ce n’est pas uniquement d’un problème de performance qu’il s’agit. C’est plutôt un problème de cohérence des données.
Prennons le cas suivant :
Question q = new Question("Question", "Réponse");
Game g1 = new Game("test_game_1");
g1.addQuestion(q);
gameDao.save(g1);
Game g2 = new Game("test_game_2");
g2.addQuestion(q);
gameDao.save(g2);
Une fois exécuté, en base, il y a :
Ca colle, j’ai bien 1 seul Game associé à une Question - même si, j’ai le sentiment d’avoir “écrasé” l’association Game g1 - Question q qui est bien présente dans le code.
En effet, en mémoire, il y a :
J’ai l’inverse de ce que je voulais !! Il y a 2 Game pointant vers la même Question, soit une association @ManyToMany au lieu d’une association @OneToMany.
Mais en base il y a bien une @OneTo… (Hibernate à veillé au grain en faisant 2 update
correspondant aux 2 associations).
** MORALITE** :
Le seul moyen de garantir la cohésion entre la base de données et la représentation en mémoire des associations @OneToMany … c’est d’avoir une relation bi-directionnelle (de type parent/child).