如何在 Hibernate 中实现延迟加载,其中模型是在与使用属性的不同事务中创建的

How to implement lazy loading in Hibernate where the model is created in a different transaction to where the properties are used

我知道这个问题已经被问过很多很多次了 - 但我看不到针对这种特殊情况的确切答案:

我有一个控制器 class,它以两种不同的方法处理 @RequestMapping(params = "type", method = RequestMethod.GET) onLoad(...)@RequestMapping(method = RequestMethod.POST) onSubmit(...)

然后调用那些方法

@Transactional()
void load(TypeOfForm form, Long id, TypeOfSessionParams sessionParams);

@Transactional()
void store(TypeOfForm form);

逻辑上分别class。

load 方法进入 dao 并从数据库中获取模型的实例;但该模型包含以下内容:

@OneToMany(mappedBy = "company", cascade = CascadeType.PERSIST)
public Set<CompanyLocations> getCompanyLocations() {
    return companyLocations;}

直到 store() 方法才会被调用。因为它是延迟加载,所以我得到:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.orgName.modelName.companyLocations, could not initialize proxy - no Session

我看到的典型答案是:

  1. 添加@Transactional()注释

我已经在 load() 和 store() 方法上有了这个。我做错了吗?因为它似乎没有帮助(我认为是因为在store()中调用了getCompanyLocations方法,这与最初创建模型对象时不是同一个事务)

  1. , fetch = FetchType.EAGER添加到模型属性的get方法

这可能有效,我不知道 - 它使页面加载时间太长以至于我放弃了它 - 所以它无论如何都不是一个有效的解决方案。

  1. 使用 OpenSessionInViewInterceptor/Filter

实际上,据我所知,该应用程序目前同时使用了两者:

在 applicationContext-hibernate.xml 文件中:

<bean id="openSessionInViewInterceptor"
    class="org.springframework.orm.hibernate4.support.OpenSessionInViewInterceptor">
  <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<bean id="transactionInterceptor" class="org.orgName.util.TransactionalWebRequestInterceptor">
  <property name="transactionManager" ref="transactionManager"/>
  <property name="transactionAttribute" value="PROPAGATION_REQUIRES_NEW"/>
</bean>

在web.xml中:

<filter>
  <filter-name>open-session-in-view</filter-name>
  <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
  <init-param>
    <param-name>sessionFactoryBeanName</param-name>
    <param-value>sessionFactory</param-value>
  </init-param>
</filter>


<filter-mapping>
  <filter-name>open-session-in-view</filter-name>
  <url-pattern>*.jsp</url-pattern>
</filter-mapping>
  1. <prop key="hibernate.enable_lazy_load_no_trans">true</prop> 添加到我的 sessionFactory 属性

这是唯一可行的解​​决方案,但我看过的所有地方都说这是一个非常糟糕的主意。

那么,正确的做法是什么?

如果有帮助,这里是 TransactionalWebRequestInterceptor class:

public class TransactionalWebRequestInterceptor implements WebRequestInterceptor, TransactionDao {
    private static final Log log = LogFactory.getLog(TransactionalWebRequestInterceptor.class);

    private PlatformTransactionManager transactionManager;
    private TransactionAttribute transactionAttribute;
    private ThreadLocal<TransactionStatus> threadTransactionStatus = new ThreadLocal<TransactionStatus>();

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void setTransactionAttribute(TransactionAttribute transactionAttribute) {
        this.transactionAttribute = transactionAttribute;
    }

    public void preHandle(WebRequest request) throws Exception {
        log.debug("preHandle");
        beginTransaction();
    }

    public void postHandle(WebRequest request, ModelMap model) throws Exception {
        log.debug("postHandle");
        commitTransactionIfNoErrors();
    }

    public void afterCompletion(WebRequest request, Exception e) throws Exception {
        log.debug("afterCompletion");
        rollBackTransactionIfInProgress();
    }

    public void setRollbackOnly() {
        log.debug("setRollbackOnly");
        TransactionStatus status = threadTransactionStatus.get();
        if (status == null) {
            log.debug("rollback requested but no transaction in progress");
            return;
        }
        status.setRollbackOnly();
    }

    private void beginTransaction() {
        if (threadTransactionStatus.get() != null)
            throw new IllegalStateException("transaction already in progress");
        TransactionStatus status = transactionManager.getTransaction(transactionAttribute);
        threadTransactionStatus.set(status);
        log.debug("transaction begun");
    }

    private void commitTransactionIfNoErrors() {
        TransactionStatus status = threadTransactionStatus.get();
        if (status == null)
            throw new IllegalStateException("no transaction in progress");
        if (status.isRollbackOnly()) {
            log.debug("commitTransactionIfNoErrors: transaction is rollback-only; not committing");
            return;
        }
        
        UserAttributes.getCurrent().getUser().setIsTransactionCompleted(true);
        
        threadTransactionStatus.set(null);
        transactionManager.commit(status);
        log.debug("transaction committed");
    }

    private void rollBackTransactionIfInProgress() {
        TransactionStatus status = threadTransactionStatus.get();
        if (status == null) {
            log.debug("rollBackTransactionIfInProgress: no transaction in progress");
            return;
        }
        threadTransactionStatus.set(null);
        transactionManager.rollback(status);
        log.debug("transaction rolled back");
    }
}

你应该在同一个事务中进行加载和存储,所以调用加载和存储的任何方法都应该是 @Transactional

延迟加载问题通常通过使用专用的 DTO 模型来解决,该模型可以准确获取所需内容。我在这里写了一些解决方案及其优缺点:

如果您有两个请求,那么您有两个选择。在存储中使用 EntityManager.merge 将状态 as-is 应用到数据库,或使用 EntityManager.find 加载现有数据并将更改的数据应用到存储方法事务中的该实例。