使用Hibernate过滤器时,如何禁用会话的懒加载?
我有一个基于XML的Hibernate映射文件,为某一对多关系配置了过滤器条件。执行查询前会启用过滤器并绑定必要数据,借助AutoCloseable,查询执行并获取实体列表后会禁用当前会话的过滤器。过滤器的优势在于其独立于当前查询生效,无需过多关注。
但问题在于:列表中元素的关联实体由Hibernate懒加载(lazy-loading),且仅在AutoCloseable作用域内可正常工作——因为过滤器关闭后,懒加载获取的查询结果会出错。另一方面,由于Hibernate会话可能会被复用执行完全无关的查询,必须禁用过滤器。
核心疑问:是否存在某种方法可以完全禁用某个会话的懒加载?
这样我就可以在启用/禁用过滤器时同步操作,无需关心映射文件等内容。此外,这是一个遗留应用,我不确定Hibernate注解是否被识别,我添加的注解均未生效。
XML过滤器定义
<filter-def name = "mostCurrNonAutoUae" condition = "id IN ([...])" > <filter-param name = "autoRegMails" type = "string" /> </filter-def>
一对多关系映射
<set inverse="true" name="userActivations" access="field"> <key> <column name="user_id" not-null="true" /> </key> <one-to-many class="[...]" /> <filter name = "mostCurrNonAutoUae" /> </set>
问题复现代码
在以下示例中,Eintrag 1的结果错误,它包含未过滤的元素而非过滤后的内容。这是合理的,因为过滤器已被禁用,但若没有Hibernate的懒加载则不会出现此问题。
@Override public FuserEntity findUserByProfileIdAndUsername(int profileId, String username) { String stmt = "[...]"; List<FuserEntity> ret = new ArrayList<>(); try (FilteringSession session = new FilteringSession()) { Query qry = session.get() .createQuery (stmt) .setParameter("profileId", profileId) .setParameter("usrName", username); ret = (List<FuserEntity>) qry.list(); return !ret.isEmpty() ? ret.get(0) : null; } finally { System.err.println("Liste: " + ret.toString()); System.err.println("Eintrag 1: " + ret.get(0).getMostCurrNonAutoUae().get().toString()); System.err.println("Eintrag 2: " + ret.get(0).getMostCurrNonAutoUae().get().getFirstName()); } }
抛出的异常
2023-05-25 18:18:11 ERROR [...].printException: Unexpected many user activation entries: 3 java.lang.UnsupportedOperationException: Unexpected many user activation entries: 3 at [...].FuserEntity.getMostCurrNonAutoUae(FuserEntity.java:319) ~[classes/:?] at [...].FuserDao.findUserByProfileIdAndUsername(FuserDao.java:300) ~[classes/:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
针对你的场景,提供几种无需依赖注解的可行方案:
1. 过滤器生效时主动初始化懒加载关联
在AutoCloseable作用域内,手动触发懒加载关联的初始化,确保数据在过滤器关闭前就已加载完成:
try (FilteringSession session = new FilteringSession()) { Query qry = session.get() .createQuery (stmt) .setParameter("profileId", profileId) .setParameter("usrName", username); ret = (List<FuserEntity>) qry.list(); // 主动初始化关联集合,避免后续懒加载 if (!ret.isEmpty()) { Hibernate.initialize(ret.get(0).getUserActivations()); } return !ret.isEmpty() ? ret.get(0) : null; }
后续在finally块中访问关联数据时,不会触发新的查询,自然不受过滤器关闭的影响。
2. 修改HQL查询,用JOIN FETCH提前加载关联
直接在查询语句中通过JOIN FETCH显式加载关联集合,绕过懒加载机制:
SELECT u FROM FuserEntity u JOIN FETCH u.userActivations WHERE u.profileId = :profileId AND u.username = :usrName
这种方式会在查询主实体的同时,一次性加载关联的userActivations集合,并且会自动应用过滤器条件(因为关联映射上绑定了过滤器),后续访问无需再触发懒加载。
3. 会话级临时禁用懒加载(不推荐)
如果一定要在会话级别禁用懒加载,可以通过Hibernate元数据临时修改关联加载策略,但需注意恢复原设置避免影响会话复用:
try (FilteringSession session = new FilteringSession()) { Session hibernateSession = session.get(); // 获取实体元数据 PersistentClass persistentClass = ((SessionFactoryImplementor) hibernateSession.getSessionFactory()) .getMetamodel().entityPersister(FuserEntity.class.getName()); CollectionPersister collectionPersister = persistentClass.getCollectionPersister("userActivations"); // 临时设置为非懒加载 boolean originalLazy = ((AbstractCollectionPersister) collectionPersister).isLazy(); ((AbstractCollectionPersister) collectionPersister).setLazy(false); // 执行查询 Query qry = hibernateSession.createQuery(stmt) .setParameter("profileId", profileId) .setParameter("usrName", username); ret = (List<FuserEntity>) qry.list(); // 恢复原懒加载设置 ((AbstractCollectionPersister) collectionPersister).setLazy(originalLazy); return !ret.isEmpty() ? ret.get(0) : null; }
此方式侵入性较强,仅在其他方案无法使用时考虑。
内容的提问来源于stack exchange,提问作者Thorsten Schöning

