Sync'ing java and db entity state on reverse relation ?
by Aeris
Hello guys,
I have some trouble with Hibernate.
I use the following mapping :
@Entity
public class Candidacy {
@ManyToOne(optional = false)
@ForeignKey(name = "fk_candidacy_team")
private Team team;
}
@Entity
public class Team {
@OneToMany(mappedBy = "team")
@OnDelete(action = OnDeleteAction.CASCADE)
private final Collection<Candidacy> candidacies;
}
When I want to create a new candidacy, I use the current 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 :
@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 :
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 :
@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.
--
Aeris
10 years, 3 months