如何在 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
我看到的典型答案是:
- 添加
@Transactional()
注释
我已经在 load() 和 store() 方法上有了这个。我做错了吗?因为它似乎没有帮助(我认为是因为在store()中调用了getCompanyLocations方法,这与最初创建模型对象时不是同一个事务)
- 将
, fetch = FetchType.EAGER
添加到模型属性的get方法
这可能有效,我不知道 - 它使页面加载时间太长以至于我放弃了它 - 所以它无论如何都不是一个有效的解决方案。
- 使用 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>
- 将
<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 模型来解决,该模型可以准确获取所需内容。我在这里写了一些解决方案及其优缺点:
- https://blazebit.com/blog/2016/getting-started-with-blaze-persistence-entity-views.html
- https://blazebit.com/blog/2017/entity-view-subview-as-rescue-for-lazyinitializationexception.html
如果您有两个请求,那么您有两个选择。在存储中使用 EntityManager.merge 将状态 as-is 应用到数据库,或使用 EntityManager.find 加载现有数据并将更改的数据应用到存储方法事务中的该实例。
我知道这个问题已经被问过很多很多次了 - 但我看不到针对这种特殊情况的确切答案:
我有一个控制器 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
我看到的典型答案是:
- 添加
@Transactional()
注释
我已经在 load() 和 store() 方法上有了这个。我做错了吗?因为它似乎没有帮助(我认为是因为在store()中调用了getCompanyLocations方法,这与最初创建模型对象时不是同一个事务)
- 将
, fetch = FetchType.EAGER
添加到模型属性的get方法
这可能有效,我不知道 - 它使页面加载时间太长以至于我放弃了它 - 所以它无论如何都不是一个有效的解决方案。
- 使用 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>
- 将
<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 模型来解决,该模型可以准确获取所需内容。我在这里写了一些解决方案及其优缺点:
- https://blazebit.com/blog/2016/getting-started-with-blaze-persistence-entity-views.html
- https://blazebit.com/blog/2017/entity-view-subview-as-rescue-for-lazyinitializationexception.html
如果您有两个请求,那么您有两个选择。在存储中使用 EntityManager.merge 将状态 as-is 应用到数据库,或使用 EntityManager.find 加载现有数据并将更改的数据应用到存储方法事务中的该实例。