【问题标题】:Spring, @Transactional and Hibernate Lazy LoadingSpring,@Transactional 和 Hibernate 延迟加载
【发布时间】:2011-05-11 14:05:09
【问题描述】:

我正在使用 spring + hibernate。我所有的 HibernateDAO 都直接使用 sessionFactory。

我有应用层 -> 服务层 -> DAO 层,所有集合都是延迟加载的。

所以,问题是有时在应用程序层(包含 GUI/swing)中,我使用服务层方法(包含 @Transactional 注释)加载实体,并且我想使用该对象的惰性属性,但是显然会话已经关闭。

解决这个问题的最佳方法是什么?

编辑

我尝试使用MethodInterceptor,我的想法是为我所有的实体写一个AroundAdvice并使用注释,例如:

// Custom annotation, say that session is required for this method
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SessionRequired {


// An AroundAdvice to intercept method calls
public class SessionInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation mi) throws Throwable {
        bool sessionRequired=mi.getMethod().isAnnotationPresent(SessionRequired.class);
        // Begin and commit session only if @SessionRequired
        if(sessionRequired){
            // begin transaction here
        }
        Object ret=mi.proceed();
        if(sessionRequired){
            // commit transaction here
        }
        return ret;
    }
}

// An example of entity
@Entity
public class Customer implements Serializable {

    @Id
    Long id;

    @OneToMany
    List<Order> orders;  // this is a lazy collection

    @SessionRequired
    public List<Order> getOrders(){
        return orders;
    }
}

// And finally in application layer...
public void foo(){
    // Load customer by id, getCustomer is annotated with @Transactional
    // this is a lazy load
    Customer customer=customerService.getCustomer(1); 

    // Get orders, my interceptor open and close the session for me... i hope...
    List<Order> orders=customer.getOrders();

    // Finally use the orders
}

你认为这行得通吗? 问题是,如何在 xml 文件中为我的所有实体注册此拦截器? 有没有办法通过注释来做到这一点?

【问题讨论】:

    标签: java hibernate spring lazy-loading


    【解决方案1】:

    Hibernate 最近引入了 fetch 配置文件,它(除了性能调整之外)是解决此类问题的理想选择。它允许您(在运行时)在不同的加载和初始化策略之间进行选择。

    http://docs.jboss.org/hibernate/core/3.5/reference/en/html/performance.html#performance-fetching-profiles

    编辑(添加了有关如何使用拦截器设置提取配置文件的部分):

    开始之前:检查获取配置文件是否真的适合您。我自己没有使用过它们,并且看到它们目前仅限于加入提取。在您浪费时间实施和连接拦截器之前,请尝试手动设置 fetch 配置文件,看看它是否确实解决了您的问题。

    在 Spring 中有很多方法可以设置拦截器(根据偏好),但最直接的方法是实现 MethodInterceptor(参见http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html#aop-api-advice-around)。让它有一个你想要的获取配置文件的设置器和 Hibernate 会话工厂的设置器:

    public class FetchProfileInterceptor implements MethodInterceptor {
    
        private SessionFactory sessionFactory;
        private String fetchProfile;
    
        ... setters ...    
    
        public Object invoke(MethodInvocation invocation) throws Throwable {
            Session s = sessionFactory.openSession(); // The transaction interceptor has already opened the session, so this returns it.
            s.enableFetchProfile(fetchProfile);
            try {
                return invocation.proceed();
            } finally {
                s.disableFetchProfile(fetchProfile);
            }
        }
    }
    

    最后,在 Spring 配置中启用拦截器。这可以通过多种方式完成,并且您可能已经拥有可以添加的 AOP 设置。见http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-schema

    如果您是 AOP 新手,我建议您先尝试“旧”ProxyFactory 方式(http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ aop-api.html#aop-api-proxying-intf),因为它更容易理解它是如何工作的。下面是一些示例 XML,可帮助您入门:

    <bean id="fetchProfileInterceptor" class="x.y.zFetchProfileInterceptor">
      <property name="sessionFactory" ref="sessionFactory"/>
      <property name="fetchProfile" ref="gui-profile"/>
    </bean>
    
    <bean id="businessService" class="x.y.x.BusinessServiceImpl">
      <property name="dao" .../>
      ...
    </bean>
    
    <bean id="serviceForSwinGUI" 
        class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces" value="x.y.z.BusinessServiceInterface/>
    
        <property name="target" ref="businessService"/>
        <property name="interceptorNames">
            <list>
                <value>existingTransactionInterceptorBeanName</value>
                <value>fetchProfileInterceptor</value>
            </list>
        </property>
    </bean>
    

    【讨论】:

    • @DaGGeRRz:这没用,在 DAO 中使用 loadLazly 和 loadEager 等两种方法并使用 fetch-profile 有什么区别?没有区别,如果我需要加载某个延迟加载的对象,我必须编写不同的方法。
    • 例如,您可以使用拦截器包装服务/dao,该拦截器在从 gui 调用时设置获取配置文件“gui”。如果您需要更多详细信息,请告诉我。
    • 谢谢 DaGGeRRz,我有一个简单的问题:此时我使用自动扫描来扫描我的组件(DAO、服务存储库等...),我可以为我的所有实体注册一个拦截器吗自动地?我将为这个问题编辑我的第一篇文章。
    • 有关 AOP,请参阅 *.com/questions/2611986/…。它不使用 MethodInterceptor,但如果您使用 AspectJ 和纯注释,则没有必要。对您在编辑中实现的拦截器的评论:我确定您正在使用的标准 @Transactional 注释已经为您提供了一个会话并处理事务(具有传播等功能),所以坚持 just 启用和禁用提取配置文件。
    • 不,因为我没有启用和禁用提取配置文件的会话,会话已经关闭,这就是问题!
    【解决方案2】:
    1. 在服务层创建一个方法,返回该实体的延迟加载对象
    2. 更改为 fetch eager :)
    3. 如果可能的话,将您的事务扩展到应用层

    (就在我们等待知道他们在说什么的人时)

    【讨论】:

    • 谢谢,1.有点无聊,每次我想加载一个惰性对象都得写一个方法,2.性能太膨胀了。也许 3. 是解决方案...
    【解决方案3】:

    很遗憾,您需要重新设计会话管理。这是处理 Hibernate 和 Spring 时的一个主要问题,而且非常麻烦。

    本质上,您需要让应用程序层在获取您的 Hibernate 对象时创建一个新会话,并正确管理该会话并关闭该会话。这东西很棘手,而且很重要。管理此问题的最佳方法之一是通过应用程序层可用的工厂来调解会话,但您仍然需要能够正确结束会话,因此您必须了解数据的生命周期需求。

    这东西是最常见的关于以这种方式使用 Spring 和 Hibernate 的抱怨;实际上,管理它的唯一方法是准确掌握您的数据生命周期。

    【讨论】:

    • 我可以在每次使用 Spring AOP 遇到惰性对象时创建一个会话吗?
    • @blow:是的,你可以,这是个好主意;问题变成知道何时终止会话(即,当您的对象修改完成时)。在某些情况下,这很容易;在其他方面,非常困难。