I have a Song class containing a collection of CoverArts
e.g
@OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
@JoinColumn(name = "recNo")
private List<CoverArt> coverArts;
and am using Hibernate 4.3.11 and DB2 database and I have this query for retrieving a list of Songs by their primary key together with their coverArt.
public static List<Song> getSongsWithCoverArtFromDatabase(Session session, List<Integer> ids)
{
try
{
Criteria c = session
.createCriteria(Song.class)
.setFetchMode("coverArts", FetchMode.JOIN)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.add(Restrictions.in("recNo", ids));
List<Song> songs = c.list();
return songs;
}
catch (Exception e)
{
MainWindow.logger.log(Level.SEVERE, "Failed LoadSongToDatabase:" + e.getMessage(), e);
throw new RuntimeException(e);
}
}
Note we have set fetch mode to JOIN on the coverArts collection, and that we need to set setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY), otherwise if we had a song with two coverart records we would get two Song objects returned back. But when using Criteria.DISTINCT_ROOT_ENTITY Hibernate would correctly return one Song containing two coverArts.
However I have just tried to do the same thing but using a StatelessSession. The reasoning being I am just trying to select data for creating a report and I want maximize speed and minimize memory consumption, however
public static List<Song> getSongsWithCoverArtFromDatabase(StatelessSession session, List<Integer> ids)
{
try
{
Criteria c = session
.createCriteria(Song.class)
.setFetchMode("coverArts", FetchMode.JOIN)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.add(Restrictions.in("recNo", ids));
List<Song> songs = c.list();
return songs;
}
catch (Exception e)
{
MainWindow.logger.log(Level.SEVERE, "Failed LoadSongToDatabase:" + e.getMessage(), e);
throw new RuntimeException(e);
}
}
this seems to ignore the .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) ands returns duplicates rows.
Is this a known bug, how it meant to behave ?
Looks like this is shortcoming in the way
StatelessSessionImplis implemented in Hibernate, but a fix might be on the way too...Clearly with the
FetchMode.JOIN, the SQL query will be (left outer) joining across the two tables, so may return several rows per song. Normally Hibernate resolves each row returned via itsPersistenceContext.If interested, you can see this in the Hibernate source for
Loaderhere. Then, depending on the type ofSession, SessionImpl.getEntityUsingInterceptor() talks to thePersistenceContext, but StatelessSessionImpl.getEntityUsingInterceptor() just returns null. However there is a later commit to this method that looks to do the right thing. The commit is part of HHH-11147, which says the fix versions are Hibernate 5.3.11 and 5.4.4 - not showing in the Maven repo at the time of writing.In the meantime, one fix would be to roll your own
ResultTransformer. This is a fairly 'to the point' example:The difference is that the normal
DistinctRootEntityResultTransformerassumes there will only be a unique instance of the entity in the session - you can see the compare here.Clearly there's room to make that example more reuseable too, particularly to abstract the
getId().