【问题标题】:Prevent Dozer from triggering Hibernate lazy loading防止 Dozer 触发 Hibernate 延迟加载
【发布时间】:2011-07-29 23:19:01
【问题描述】:

我正在使用 Spring 事务,因此当 POJO 到 DTO 转换发生时,事务仍然处于活动状态。

我想防止 Dozer 触发延迟加载,以便永远不会发生隐藏的 sql 查询:所有获取都必须通过 HQL 显式完成(以获得对性能的最佳控制)。

  1. 这是一种好习惯吗(我在任何地方都找不到它的文档)?

  2. 如何安全操作?

我在 DTO 转换之前尝试过这个:

PlatformTransactionManager tm = (PlatformTransactionManager) SingletonFactoryProvider.getSingletonFactory().getSingleton("transactionManager");
tm.commit(tm.getTransaction(new DefaultTransactionDefinition()));

我不知道事务发生了什么,但是 Hibernate 会话没有关闭,并且仍然发生延迟加载。

我试过这个:

SessionFactory sf = (SessionFactory) SingletonFactoryProvider.getSingletonFactory().getSingleton("sessionFactory");
sf.getCurrentSession().clear();
sf.getCurrentSession().close();

它可以防止延迟加载,但是直接在应用程序层(在我的项目中称为“外观”)中操作会话是一种好习惯吗?我应该害怕哪些负面影响? (我已经看到涉及 POJO -> DTO 转换的测试无法再通过 AbstractTransactionnalDatasource Spring 测试类启动,因为这些类尝试触发不再与活动会话链接的事务的回滚)。

我也尝试将传播设置为 NOT_SUPPORTED 或 REQUIRES_NEW,但它会重用当前的 Hibernate 会话,并且不会阻止延迟加载。

【问题讨论】:

    标签: java hibernate spring dozer spring-transactions


    【解决方案1】:

    我发现用于管理此问题的唯一通用解决方案(在查看自定义转换器、事件侦听器和代理解析器之后)是实现自定义字段映射器。我发现这个功能隐藏在 Dozer API 中(我不相信它记录在用户指南中)。

    一个简单的例子如下;

    public class MyCustomFieldMapper implements CustomFieldMapper 
    {
        public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) 
        {       
            // Check if field is a Hibernate collection proxy
            if (!(sourceFieldValue instanceof AbstractPersistentCollection)) {
                // Allow dozer to map as normal
                return false;
            }
    
            // Check if field is already initialized
            if (((AbstractPersistentCollection) sourceFieldValue).wasInitialized()) {
                // Allow dozer to map as normal
                return false;
            }
    
            // Set destination to null, and tell dozer that the field is mapped
            destination = null;
            return true;
        }   
    }
    

    这会将任何未初始化的 PersistentSet 对象返回为 null。我这样做是为了当它们被传递给客户端时,我可以区分 NULL(未加载)集合和空集合。这允许我在客户端中定义通用行为以使用预加载的集合,或者进行另一个服务调用来检索集合(如果需要)。此外,如果您决定在服务层中急切地加载任何集合,那么它们将照常映射。

    我使用 spring 注入自定义字段映射器:

    <bean id="dozerMapper" class="org.dozer.DozerBeanMapper" lazy-init="false">
        <property name="mappingFiles">
            ...
        </property>
        <property name="customFieldMapper" ref="dozerCustomFieldMapper" />
    </bean>
    <bean id="dozerCustomFieldMapper" class="my.project.MyCustomFieldMapper" />
    

    我希望这可以帮助任何寻找解决方案的人,因为我在搜索 Internet 时没有找到任何示例。

    【讨论】:

    【解决方案2】:

    上面流行版本的变体,确保同时捕获 PersistentBags、PersistentSets,你可以命名它......

    public class LazyLoadSensitiveMapper implements CustomFieldMapper {
    
    public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) {
        //if field is initialized, Dozer will continue mapping
    
        // Check if field is derived from Persistent Collection
        if (!(sourceFieldValue instanceof AbstractPersistentCollection)) {
            // Allow dozer to map as normal
            return false;
        }
    
        // Check if field is already initialized
        if (((AbstractPersistentCollection) sourceFieldValue).wasInitialized()) {
            // Allow dozer to map as normal
            return false;
        }
    
        return true;
    }
    

    }

    【讨论】:

      【解决方案3】:

      我没有让上述工作(可能不同的版本)。但是这很好用

      public class HibernateInitializedFieldMapper implements CustomFieldMapper {
          public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) {
              //if field is initialized, Dozer will continue mapping
              return !Hibernate.isInitialized(sourceFieldValue));
          }
      }
      

      【讨论】:

        【解决方案4】:

        您是否考虑过完全禁用延迟加载?

        它似乎与您声明要使用的模式不符:

        我想防止 Dozer 触发延迟加载,这样隐藏的 sql 查询就不会发生:所有的提取都必须通过 HQL 显式完成(以获得对性能的最佳控制)。

        这表明您永远不想使用延迟加载。

        Dozer 和你传递给它的支持 Hibernate 的 bean 幸福地相互无知; Dozer 所知道的是它正在访问 bean 中的属性,并且 Hibernate 支持的 bean 正在响应对 get() 的调用,这是一个延迟加载的集合,就像您自己访问这些属性一样。

        任何使 Dozer 了解您的 bean 中的 Hibernate 代理或反之亦然的技巧,IMO 都会分解您的应用程序的层。

        如果您不希望在意外时间触发任何“隐藏的 SQL 查询”,只需禁用延迟加载即可。

        【讨论】:

        • 当然,如果可能的话,我想“禁用延迟加载”,但该怎么做呢?我的意思是“default-lazy=false”意味着我所有的关联都会被急切地获取不是吗?
        • 是的,或者您可以在类或属性上指定lazy="false",是的,这将导致急切获取。你必须要么渴望获取要么延迟加载;您不能使用 Hibernate 映射属性/集合,并且只能在某些时候由 Hibernate 加载它。
        • 好的,我想要的是:当调用 getter 时,Hibernate 永远不会自动加载我的集合。 Hibernate 仅在我在 HQL 查询中使用显式“join”指定它时才加载我的集合(例如“from Person join fetch Orders”)你能确认这种行为在 Hibernate 中是不可能的吗? (是否有某种原因......?或其他适合这种行为的流行框架?)
        • Hibernate 的理念是在您请求时将您的对象返回给您,包括所有集合(可能是延迟加载)、关联等。您所描述的是更接近 SQL 层的东西,您可以在其中对获取的内容和不获取的内容进行一些控制。您最好查看其他数据访问库,例如 iBatis SqlMaps,它可以让您将查询结果(您自己的规范)映射到对象
        • 抱歉,马特,有更好的答案(见下方)。
        【解决方案5】:

        mapper 的短版将是

        return sourceFieldValue instanceof AbstractPersistentCollection && 
        !( (AbstractPersistentCollection) sourceFieldValue ).wasInitialized();
        

        【讨论】:

        • 我不确定如何重构代码以降低可读性确实为这个答案增加了价值。
        【解决方案6】:

        使用 CustomFieldMapper 可能不是一个好主意,因为它会为源类的每个字段调用,但我们关心的只是惰性关联映射(子对象列表),因此我们可以在实体对象的 getter 中设置 null 值,

        public Set<childObject> getChild() {
        if(Hibernate.isInitialized(child){
            return childObject;
        }else
         return null;
        }
        

        【讨论】:

        • 这有效地禁用了所有适用集合的延迟加载,不仅适用于 VO 映射。如果您有任何需要延迟加载此集合的应用程序内部逻辑,它只会变为 null。这个问题专门关于禁用 VO 映射的延迟加载,而不是完全禁用。
        猜你喜欢
        • 2014-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-18
        • 2011-07-25
        • 2018-11-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多