[hibernate-users] Sync'ing java and db entity state on reverse relation ?

Aeris aeris+hibernate at imirhil.fr
Sat Jul 26 10:15:23 EDT 2014


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 at 70dc0648, Candidacy at 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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 819 bytes
Desc: This is a digitally signed message part.
Url : http://lists.jboss.org/pipermail/hibernate-users/attachments/20140726/a1759aee/attachment.bin 


More information about the hibernate-users mailing list