在 Hibernate 中,StatelessSession 在有 EAGER JOIN 时防止过滤掉重复项
In Hibernate Does StatelessSession prevent filtering out duplicates when have an EAGER JOIN
我有一首 歌曲 class 包含 CoverArt 的集合
例如
@OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
@JoinColumn(name = "recNo")
private List<CoverArt> coverArts;
我正在使用 Hibernate 4.3.11 和 DB2 数据库,我有这个查询来通过主键和封面艺术检索歌曲列表。
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);
}
}
请注意,我们在 coverArts 集合上将获取模式设置为 JOIN,并且我们需要设置 s etResultTransformer(Criteria.DISTINCT_ROOT_ENTITY),否则如果我们有一首包含两个翻唱唱片的歌曲,我们将得到两个 Song 对象 return 返回。但是当使用 Criteria.DISTINCT_ROOT_ENTITY Hibernate 时会正确 return 一首包含两个封面的歌曲。
但是我刚刚尝试做同样的事情,但使用的是 StatelessSession。原因是我只是想 select 数据来创建报告,我想最大化速度并最小化内存消耗,但是
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);
}
}
这似乎忽略了 .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) 和 returns 重复行。
这是已知错误吗?它的行为方式是什么?
看起来这是 StatelessSessionImpl
在 Hibernate 中实现的方式的缺点,但修复可能也在路上...
很明显,对于 FetchMode.JOIN
,SQL 查询将(左外)连接两个表,因此每首歌曲可能 return 多行。通常 Hibernate 通过其 PersistenceContext
解析每一行 returned。
如果有兴趣,您可以在撰写本文时在 Loader
here. Then, depending on the type of Session
, SessionImpl.getEntityUsingInterceptor() talks to the PersistenceContext
, 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 的 Hibernate 源代码中查看。
与此同时,一种解决方法是推出您自己的 ResultTransformer
。这是一个相当 'to the point' 的例子:
public class DistinctSongResultTransformer implements ResultTransformer {
private ResultTransformer defaultTransformer = Criteria.DISTINCT_ROOT_ENTITY;
@Override
public Object transformTuple(Object[] tuple, String[] aliases) {
return defaultTransformer.transformTuple(tuple, aliases);
}
@SuppressWarnings("rawtypes")
@Override
public List transformList(List collection) {
Map<Integer, Song> distinctSongs = new LinkedHashMap<>();
for (Object object : collection) {
Song song = (Song) object;
distinctSongs.putIfAbsent(song.getId(), song);
}
return new ArrayList<>(distinctSongs.values());
}
}
不同之处在于正常的 DistinctRootEntityResultTransformer
假定会话中只有一个实体的唯一实例 - 您可以看到比较 here.
显然,还有使该示例更具可重用性的空间,特别是抽象 getId()
。
我有一首 歌曲 class 包含 CoverArt 的集合
例如
@OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
@JoinColumn(name = "recNo")
private List<CoverArt> coverArts;
我正在使用 Hibernate 4.3.11 和 DB2 数据库,我有这个查询来通过主键和封面艺术检索歌曲列表。
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);
}
}
请注意,我们在 coverArts 集合上将获取模式设置为 JOIN,并且我们需要设置 s etResultTransformer(Criteria.DISTINCT_ROOT_ENTITY),否则如果我们有一首包含两个翻唱唱片的歌曲,我们将得到两个 Song 对象 return 返回。但是当使用 Criteria.DISTINCT_ROOT_ENTITY Hibernate 时会正确 return 一首包含两个封面的歌曲。
但是我刚刚尝试做同样的事情,但使用的是 StatelessSession。原因是我只是想 select 数据来创建报告,我想最大化速度并最小化内存消耗,但是
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);
}
}
这似乎忽略了 .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) 和 returns 重复行。
这是已知错误吗?它的行为方式是什么?
看起来这是 StatelessSessionImpl
在 Hibernate 中实现的方式的缺点,但修复可能也在路上...
很明显,对于 FetchMode.JOIN
,SQL 查询将(左外)连接两个表,因此每首歌曲可能 return 多行。通常 Hibernate 通过其 PersistenceContext
解析每一行 returned。
如果有兴趣,您可以在撰写本文时在 Loader
here. Then, depending on the type of Session
, SessionImpl.getEntityUsingInterceptor() talks to the PersistenceContext
, 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 的 Hibernate 源代码中查看。
与此同时,一种解决方法是推出您自己的 ResultTransformer
。这是一个相当 'to the point' 的例子:
public class DistinctSongResultTransformer implements ResultTransformer {
private ResultTransformer defaultTransformer = Criteria.DISTINCT_ROOT_ENTITY;
@Override
public Object transformTuple(Object[] tuple, String[] aliases) {
return defaultTransformer.transformTuple(tuple, aliases);
}
@SuppressWarnings("rawtypes")
@Override
public List transformList(List collection) {
Map<Integer, Song> distinctSongs = new LinkedHashMap<>();
for (Object object : collection) {
Song song = (Song) object;
distinctSongs.putIfAbsent(song.getId(), song);
}
return new ArrayList<>(distinctSongs.values());
}
}
不同之处在于正常的 DistinctRootEntityResultTransformer
假定会话中只有一个实体的唯一实例 - 您可以看到比较 here.
显然,还有使该示例更具可重用性的空间,特别是抽象 getId()
。