@OneToMany

unidirectionnel

JPA-oneToMany-unidirectional

@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>();

}

:boom: 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 … :mag_right:
org.hibernate.TransientObjectExceptionsave the transient instance before flushing: net.jidify.jpa.entities.Question

:bulb: Hey Hey …. Cascade Persist

2eme mapping : cascading

Génial, ça marche !!!

...
@OneToMany(cascade = CascadeType.ALL)
private Set<Question> questions = new HashSet<Question>();

:smiley: 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 (?, ?)

:confused: 3 insert … je pensais en avoir seulement 2 !!

3 tables, dont cette table de jointure GAME_QUESTION

JPA - oneToMany - default Join Table

alors qu’il me semble qu’avec 2 tables, ça serait largement suffisant :

JPA - oneToMany - default Join Table custom

:bulb: 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>();

:smile: J’ai bien mes 2 tables !!

JPA - oneToMany - Join Column

###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=?

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

JPA - oneToMany - Join Column custom logs

:bulb: 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=?

:frowning: j’ai bien le game_id dans l’insert, mais j’ai encore un update (en trop à mon avis)!!

:cry: 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.”

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 :

JPA OneToMany tables Values

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 :

JPA OneToMany tables Values

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



Published

31 October 2014