Hello guys,
I have some trouble with Hibernate (v3.6.10).
I use the following mapping :
Code:
@Entity(name = "team.Candidacy")
@Table(schema = "team")
public class Candidacy {
@ManyToOne(optional = false)
@ForeignKey(name = "fk_candidacy_team")
private Team team;
}
@Entity(name = "team.Team")
@Table(schema = "team")
public class Team {
@OneToMany(mappedBy = "team")
@OnDelete(action = OnDeleteAction.CASCADE)
private final Collection<Candidacy> candidacies = new LinkedList<>();
}
When I want to create a new candidacy, I use the current code :
Code:
public class CandidacyDao {
public Candidacy apply(Team team) {
Candidacy candidacy = new Candidacy();
candidacy.setTeam(team);
team.getCandidacies().add(candidacy);
getCurrentSession().save(candidacy);
return candidacy;
}
}
But this code doesn't work in all case.
This test case don't works :
Code:
@Test
public void testFail() {
final Team team = this.teamDao.get(1); // team created from dbunit
final Candidacy candidacy = this.apply(team);
assertThat(team.getCandidacies()).hasSize(1).contains(candidacy);
// java.lang.AssertionError: expected size:<1> but was:<2> for <[Candidacy@70dc0648, Candidacy@70dc0648]>
}
Note the duplicated candidacy on the list at the end.
After a (lot of) debug, I understand why :
Code:
public class org.hibernate.collection.PersistentBag {
public boolean add(Object object) {
if ( !isOperationQueueEnabled() ) {
write();
return bag.add(object);
} else {
queueOperation( new SimpleAdd(object) );
return true;
}
}
protected boolean isOperationQueueEnabled() {
return !initialized && isConnectedToSession() && isInverseCollection();
}
}
So, « team.getCandidacies() » is not previously resolved (lazy-loaded) before the « .add(candidacy) », and even this « .add » doesn't trigger the resolution (write access) and postpone it when the « .getCandidacies() » will be really read (on « assert »).
At « assert » time, the children are really resolved, so a SELECT is done in database, fetch all the candidacies including the newly created, and realize the real addition, leading to a duplicated one.
If I force the « .getCandidacies() » or create directly the team from the test, all is good :
Code:
@Test
public void testSuccess1() {
final Team team = this.teamDao.get(1);
this.logger.debug("{}", team.getCandidacies()); // force the read
final Candidacy candidacy = this.apply(team);
assertThat(team.getCandidacies()).hasSize(1).contains(candidacy);
}
@Test
public void testSuccess2() {
final Team team = new Team();
getCurrentSession().save(team); // native initialization
final Candidacy candidacy = this.apply(team);
assertThat(team.getCandidacies()).hasSize(1).contains(candidacy);
}
If I remove the « .add » on the DAO, the behaviour is the opposite :
If lazy-loaded before the « .apply », failure at the end because the candidacy is not in the list of the team.
If not, test success because the « assert » will trigger the resolution, but with no « .add » this time so no duplicate and correct list (fetch from db).
So, my question is : how I can keep in sync the database and the java entity state in case of children and reverse relation, in any case (previously lazy-loaded or not) ? :'(
Thanks in advance.