5

My problem is that hibernate retrieve null in the value of the @OneToMany Set organizationMemberCollection when fetching an instance on the following object :

UserAccount.java :

@Entity
@Table(name="USER_ACCOUNT")
public class UserAccount {

    @Id
    @Column(name = "id", nullable = false)
    @SequenceGenerator(name = "generator", sequenceName = "USER_ACCOUNT_id_seq", allocationSize=1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
    private Long id;

    @Column(name = "EMAIL", nullable = false)
    private String email;

    @Column(name = "PASSWORD_HASH")
    private String passwordHash;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "userAccount")
    private Set <OrganizationMember> organizationMemberCollection;

    ...

    /*
     * getters and setters
     */
}

Here is the Object that "owns" the association :

OrganizationMember.java :

@Entity
@Table(name="ORGANIZATION_MEMBER")
public class OrganizationMember{

    @Id
    @Column(name = "id", nullable = false)
    @SequenceGenerator(name = "generator", sequenceName = "ORGANIZATION_MEMBER_id_seq", allocationSize=1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "USER_ACCOUNT_ID", nullable = false)
    private UserAccount userAccount;    

    ...

    /*
     * getters and setters
     */
}

In this application we have two different configuations :

  • Production, where Hibernate is connected to a PostgreSQL database. Here is the sessionFactory configuration for prod :

    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="hibernateProperties"> <props> <prop key="hibernate.jdbc.batch_size">10</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.cglib.use_reflection_optimizer">false</prop> </props> </property> ... </bean>

  • Test, where Hibernate is conencted to an in memory HSQLDB database :

    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop> <prop key="hibernate.show_sql">false</prop> <prop key="hibernate.cglib.use_reflection_optimizer">false</prop> <prop key="hibernate.hbm2ddl.auto">create-drop</prop> <prop key="hibernate.cache.use_second_level_cache">false</prop> <prop key="hibernate.cache.use_query_cache">false</prop> </props> </property> ... </bean>

This issue only show up in testing configuration; In production configuration everything's going nicely and I can get the collection. However, when I fetch an UserAccount in the test configuration I get null in the organizationMemberCollection property (Not an empty Set).

After some hours of research through google and Hibernate's doc I still haven't found any post relating to the same issue/behaviour, so I'm a lillte bit lost and help would be greatly appreciated !

I can of course provide more information if needed, Thanks !

Edit :

Test higlighting the problem :

@Test
@Transactional
public void testFindUserAccount_OrganizationMemberCollectionFetching() {

    assertNotNull(userAccountDao.findUserAccount("user1@test.fr"));  //NoProblem
    assertNotNull(userAccountDao.findUserAccount("user1@test.fr").getCabinetMemberCollection());  //Fails

}

With the following findUserAccount dao

public UserAccount findUserAccount(String email) {
    if (email == null) {
        return null;
    }
    UserAccount userAccount = (UserAccount) this.sessionFactory
            .getCurrentSession().createCriteria(UserAccount.class)
            .add(Restrictions.eq("email", email).ignoreCase())
            .uniqueResult();
    if (userAccount == null) {
        throw new ObjectNotFoundException("UserAccount.notFound");
    } else {
        return userAccount;
    }
}
Tom
  • 375
  • 1
  • 4
  • 13
  • you get a null, because you don't have any organizationMemberCollection associated with your userAccount. Can you provide the code of your test ? maybe you forgot to call setUserAccount method before saving the OrganizationMember – Olivier Boissé Mar 23 '16 at 16:47
  • Thanks for you're fast answer, I think that in such situation Hibernate would return my UserAccount with an empty Set, and not the null value, but maybe I'm wrong. I'm pretty sure the OrganizationMember has a reference to the user account because the db is filled before the tests. And when I fetch the OrganizationMember, the object returned by hibernate has a reference to the UserAccount. – Tom Mar 23 '16 at 16:53
  • are you sure the persistence context is not close when you call getOrganizationMemberCollection in your test ? If we could see the code maybe it could help – Olivier Boissé Mar 23 '16 at 16:55
  • Yep, I'm using Spring to handle the transactions. I edit the post to include a test showing the problem, thanks ! – Tom Mar 23 '16 at 17:11

3 Answers3

9

The issue was that the database population and the test were running in the same transaction, and hibernate cache wasn't cleaned between these two steps.

The consequence was that hibernate didn't really fired the request to the database, but hit the cache instead and returned the object without doing any join with the mapped relation.

The possible solutions are :

  • Populate the database in a different Transaction.
  • Clean the Session SessionFactory.getCurrentSession().clean(); after the population. (Flush the session before if needed : SessionFactory.getCurrentSession().flush();).

Each possibility will force the next query to really hit the database, therefore the join will occur and the mapped Collection will contain the wanted data (or be empty if the join has no result, but in any case the Collection won't have the null value).

In my opinin the first solution is way better as it doesn't rollback the whole population if something goes wrong in the test.

Tom
  • 375
  • 1
  • 4
  • 13
2

It's a lazy loaded collection, so hibernate doesn't do anything to initialize it, quite normal that hibernate returns null here ..

What I usually do is declare an empty HashSet on the property :

@OneToMany(fetch = FetchType.LAZY, mappedBy = "userAccount")
private Set <OrganizationMember> organizationMemberCollection = new hashSet<>();
Pras
  • 1,068
  • 7
  • 18
  • Thanks for your answer, I believe FetchType.LAZY means hibernate will initialize my collection with a Proxy on my collection, and not the null pointer. – Tom Mar 30 '16 at 13:41
  • Actually no ... that `= new hashSet<>();` tells hibernate to initialize with a proxy ... not FetchType.LAZY – Pras Mar 30 '16 at 14:31
  • Trying it solved the NPE but the obtained Set is empty, looks like hibernate doesn't recognize this mapping. I still don't uderstand why it's working on a real database, but not on the in memory database. – Tom Mar 31 '16 at 08:12
  • I don't see any problem in your mapping ... and as you said, it is working fine on a real database ... Are you sure that your test database has some organization members for the given user account ("user1@test.fr") ? – Pras Mar 31 '16 at 08:32
  • Yep, if I run something like `organizationMemberDao.loadOrganizationMembersForUserAccount("user1@test.fr")` I get a `List` containing the expected `OrganizationMember`s. – Tom Mar 31 '16 at 10:33
0

In my case it was because I had "transient" as the entity was not serializable an Sonar told me to add the transient keyword

@OneToMany(mappedBy = "message", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
    private transient List<UserReadSystemMessage> usersRead;

Note the transient