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 !
Test higlighting the problem :
@Test
@Transactional
public void testFindUserAccount_OrganizationMemberCollectionFetching() {
assertNotNull(userAccountDao.findUserAccount("[email protected]")); //NoProblem
assertNotNull(userAccountDao.findUserAccount("[email protected]").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;
}
}
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 :
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.