![](/img/trans.png)
[英]LazyInitializationException: could not initialize proxy - no Session in Spring and Hibernate
[英]Spring MVC + Hibernate: could not initialize proxy - no Session
注意:请参阅我自己对此问题的回答,以获取我如何解决此问题的示例。
我在Spring MVC 4 + Hibernate 4项目中遇到以下异常:
org.hibernate.LazyInitializationException:懒得初始化一个角色集合:com.mysite.Company.acknowledgements,无法初始化代理 - 没有Session
在阅读了很多关于这个问题的其他问题后,我理解为什么会发生这种异常,但我不确定如何以一种好的方式解决它。 我正在做以下事情:
我之前使用过PHP和doctrine2,这种做事方式没有问题。 我试图找出解决这个问题的最佳方法,因为到目前为止我找到的解决方案看起来并不那么好:
Hibernate.initialize(myObject.getAssociation());
- 这意味着我必须遍历关联来初始化它们(我猜),而且事实上我必须这样做,有点使得延迟加载不那么整洁 我尝试在我的服务中使用@Transactional
,但没有运气。 这是有道理的,因为我试图访问我的服务方法返回后尚未加载的数据。 理想情况下,我希望能够从我的视图中访问任何关联。 我想在我的服务中初始化关联的缺点是我必须明确定义我需要的数据 - 但这取决于使用服务的上下文(控制器)。 我不确定我是否可以在我的控制器中执行此操作而不会丢失DBAL层提供的抽象。 我希望这是有道理的。 无论哪种方式,如果我不必总是明确定义我想要哪些数据可用于我的视图,那就太好了,但只是让视图做它的事情。 如果那是不可能的,那么我只是在寻找最优雅的解决方案。
以下是我的代码。
视图
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h1><c:out value="${company.name}" /> (ID: <c:out value="${company.id}" />)</h1>
<c:forEach var="acknowledgement" items="${company.acknowledgements}">
<p><c:out value="${acknowledgement.name}" /></p>
</c:forEach>
调节器
@Controller
public class ProfileController {
@Autowired
private CompanyService companyService;
@RequestMapping("/profile/view/{id}")
public String view(Model model, @PathVariable int id) {
Company company = this.companyService.get(id);
model.addAttribute("company", company);
return "viewCompanyProfile";
}
}
服务
@Service
public class CompanyServiceImpl implements CompanyService {
@Autowired
private CompanyDao companyDao;
@Override
public Company get(int id) {
return this.companyDao.get(id);
}
}
DAO
@Repository
@Transactional
public class CompanyDaoImpl implements CompanyDao {
@Autowired
private SessionFactory sessionFactory;
@Override
public Company get(int id) {
return (Company) this.sessionFactory.getCurrentSession().get(Company.class, id);
}
}
公司实体
@Entity
@Table(name = "company")
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// Other fields here
@ManyToMany
@JoinTable(name = "company_acknowledgement", joinColumns = @JoinColumn(name = "company_id"), inverseJoinColumns = @JoinColumn(name = "acknowledgement_id"))
private Set<Acknowledgement> acknowledgements;
public Set<Acknowledgement> getAcknowledgements() {
return acknowledgements;
}
public void setAcknowledgements(Set<Acknowledgement> acknowledgements) {
this.acknowledgements = acknowledgements;
}
// Other getters and setters here
}
确认实体
@Entity
public class Acknowledgement {
@Id
private int id;
// Other fields + getters and setters here
}
mvc-dispatcher-servlet.xml(它的一部分)
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan">
<list>
<value>com.mysite.company.entity</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven />
提前致谢!
正如@ Predrag Maric所说,编写具有不同初始化实体关联的服务方法可能是一种选择。
OpenSessionInView是一个退出讨论模式,但在您的情况下可以是一个解决方案。
另一个选择是设置
<prop key="hibernate.enable_lazy_load_no_trans">true</prop>
属性。 它旨在解决org.hibernate.LazyInitializationException
问题,并且自hibernate 4.1.6起可用。
那么这个属性有什么作用呢? 它只是告诉Hibernate如果在当前未初始化的代理中设置的会话关闭,它应该打开一个新的会话。 您应该知道,如果您还有用于管理当前事务的任何其他打开会话,则此新打开的会话将不同,并且除非是JTA,否则它可能不会参与当前事务。 由于这种TX副作用,您应该小心系统中可能存在的副作用。
在此处查找更多信息: http : //blog.harezmi.com.tr/hibernates-new-feature-for-overcoming-frustrating-lazyinitializationexceptions并使用hibernate.enable_lazy_load_no_trans解决Hibernate Lazy-Init问题
最简单,最透明的解决方案是OSIV模式。 我相信你已经知道,关于这个(反)模式以及这个网站和其他地方的替代方案都有很多讨论,所以不需要再重复一遍。 例如:
为什么Hibernate Open Session in View被认为是一种不好的做法?
但是,在我看来,并非所有对OSIV的批评都是完全准确的(例如,在您的视图呈现之前事务不会提交?真的吗?如果您使用的是Spring实现,那么https://stackoverflow.com/a/10664815/1356423 )
另外,请注意JPA 2.1引入了Fetch Graphs的概念,它可以让您更好地控制加载的内容。 我还没有尝试过,但也许这可能是最终解决这个长期存在的问题!
http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html
我的投票转到服务层中的Hibernate.initialize(myObject.getAssociation())
(这也意味着@Transactional
应该从DAO转移到服务方法)
恕我直言,服务方法应该返回调用者所需的所有数据。 如果调用者想要在视图上显示某些关联,则服务负责提供该数据。 这意味着你可以有几个方法显然做同样的事情(返回Company
实例),但根据调用者,可以获取不同的关联。
在一个项目中,我们有一种配置类,其中包含应该获取哪些关联的信息,并且我们有一个服务方法,它也接受该类作为参数。 这种方法意味着我们只有一种方法足够灵活,可以支持所有呼叫者。
@Override
public Company get(int id, FetchConfig fc) {
Company result = this.companyDao.get(id);
if (fc.isFetchAssociation1()) {
Hibernate.initialize(result.getAssociation1());
}
...
return result;
}
对于正在寻找解决方案的其他人来说,这就是我解决问题的方法。
正如Alan Hay指出的那样,JPA 2.1+支持实体图,最终解决了我的问题。 为了使用它,我将我的项目更改为使用javax.persistence.EntityManager
类而不是SessionFactory
,然后会将当前会话交给我。 以下是我在调度程序servlet配置中配置它的方法(排除了一些事项):
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
<jee:jndi-lookup id="dataSource" jndi-name="java:/comp/env/jdbc/postgres" expected-type="javax.sql.DataSource"/>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="dk.better.company.entity, dk.better.user.entity" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven />
</beans>
下面是DAO类的示例。
@Transactional
public class CompanyDaoImpl implements CompanyDao {
@PersistenceContext
private EntityManager entityManager;
@Override
public Company get(int id) {
EntityGraph<Company> entityGraph = this.entityManager.createEntityGraph(Company.class);
entityGraph.addAttributeNodes("acknowledgements");
Map<String, Object> hints = new HashMap<String, Object>();
hints.put("javax.persistence.loadgraph", entityGraph);
return this.entityManager.find(Company.class, id, hints);
}
}
我发现如果我没有使用@PersistenceContext
而是使用Autowired
,那么在渲染视图时数据库会话没有关闭,并且数据更新没有反映在后续查询中。
有关实体图的更多信息,建议您阅读以下文章:
http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html
http://www.thoughts-on-java.org/2014/04/jpa-21-entity-graph-part-2-define.html
服务层中的触发器需要延迟加载Set的size方法。
工具:
public class HibernateUtil {
/**
* Lazy = true when the trigger size method is equal to lazy = false (load all attached)
*/
public static void triggerSize(Collection collection) {
if (collection != null) {
collection.size();
}
}
}
在您的服务方法中:
Apple apple = appleDao.findById('xxx');
HibernateUtil.triggerSize(apple.getColorSet());
return apple;
然后在控制器中使用apple
,一切都好!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.