使用 Spring Roo 项目并获取 org.hibernate.LazyInitializationException: could not initialize proxy - no Sessi
Posted
技术标签:
【中文标题】使用 Spring Roo 项目并获取 org.hibernate.LazyInitializationException: could not initialize proxy - no Session【英文标题】:Using Spring Roo project and getting org.hibernate.LazyInitializationException: could not initialize proxy - no Session 【发布时间】:2014-08-26 13:06:54 【问题描述】:编辑5:
用于探索此问题解决方案的代码已在 bitbucket 上发布:https://bitbucket.org/jcalleja/lazy-init-exception
我意识到 SO 上还有许多其他 LazyInitializationException 帖子,但我无法从他们那里获得所需的信息。
涉及的项目:
memdrill 数据 数据使用memdrill-data 是 Spring Roo 管理的项目。这里没有 Web 组件。我只是:
persistence setup --provider HIBERNATE --database HYPERSONIC_PERSISTENT
并添加了一些实体。例如:
@RooJavaBean
@RooToString
@RooEntity
public class Item
@NotNull
@Column(unique = true)
@Size(min = 1, max = 200)
private String itemId;
@NotNull
@Lob
@OneToOne(cascade = javax.persistence.CascadeType.ALL, fetch = FetchType.LAZY)
private LobString content;
@RooJavaBean
@RooEntity
public class LobString
@Lob
@Basic(fetch=FetchType.LAZY)
private String data;
public LobString()
super();
public LobString(String data)
this.data = data;
没关系,但为了记录,我使用的是 Roo 1.1.4。在这个讨论中可能不需要 applicationContext.xml,但无论如何(由 Roo 生成后未触及):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:property-placeholder location="classpath*:META-INF/spring/*.properties"/>
<context:spring-configured/>
<context:component-scan base-package="com.memdrill.data">
<context:exclude-filter expression=".*_Roo_.*" type="regex"/>
<context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
<bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource">
<property name="driverClassName" value="$database.driverClassName"/>
<property name="url" value="$database.url"/>
<property name="username" value="$database.username"/>
<property name="password" value="$database.password"/>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="true"/>
<property name="testWhileIdle" value="true"/>
<property name="timeBetweenEvictionRunsMillis" value="1800000"/>
<property name="numTestsPerEvictionRun" value="3"/>
<property name="minEvictableIdleTimeMillis" value="1800000"/>
</bean>
<bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
<property name="persistenceUnitName" value="persistenceUnit"/>
<property name="dataSource" ref="dataSource"/>
</bean>
数据使用取决于 memdrill-data,我现在只是通过它测试驱动 memdrill-data。这就是数据使用中正在发生的一切:
public static void main(String[] args)
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
"classpath*:META-INF/spring/applicationContext*.xml");
Item item = new Item();
item.setItemId("abc123");
item.setContent(new LobString("this is the content of the item"));
item.persist();
System.out.println("persist - OK");
List<Item> items = Item.findAllItems();
Item i = items.get(0);
System.out.println("i.getId() = " + i.getId());
System.out.println("i.getItemId() = " + i.getItemId());
System.out.println("i.getContent() = " + i.getContent());
System.out.println("i.getContent().getId() = " + i.getContent().getId());
System.out.println("i.getContent().getData() = " + i.getContent().getData());
appContext.close();
findAllItems() 是 Roo 在 Item_Roo_Entity.aj 中生成的默认值:
public static List<Item> Item.findAllItems()
return entityManager().createQuery("SELECT o FROM Item o", Item.class).getResultList();
这是我得到的:
persist - OK
i.getId() = 1
i.getItemId() = abc123
2014-07-05 13:23:30,732 [main] ERROR org.hibernate.LazyInitializationException - could not initialize proxy - no Session
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
at com.memdrill.data.util.LobString_$$_javassist_0.toString(LobString_$$_javassist_0.java)
at java.lang.String.valueOf(String.java:2826)
at java.lang.StringBuilder.append(StringBuilder.java:115)
at com.memdrill.prototypes.datause.Main.main(Main.java:34)
因此,当我尝试访问延迟加载的数据以填充 LobString POJO 时,没有正在进行的会话并且它崩溃了。
我能得到的最接近(在我的代码中,即不是我所依赖的库)放入 LobString 访问代码(例如,item.getContent().getId())就像推入 findAllItems( ) 或创建一个这样的新方法来说明这一点:
public static List<Item> findAllItemsWithRefs()
List<Item> items = entityManager().createQuery("SELECT o FROM Item o", Item.class).getResultList();
for(Item item : items)
System.out.println("item.getContent().getId() = " + item.getContent().getId());
return items;
这仍然给了我同样的异常,暗示在 .getResultList() 返回列表之后没有会话进行。
正如其他 SO 帖子所建议的那样,我已经尝试将 @Transactional 放在该方法上,但我仍然得到 LazyInitializationException。但即使这确实有效......为什么我需要启动一个事务来从数据库中读取数据?事务不是为了在写入时保持数据完整性吗?
在我看来,解决这个问题的一种方法是以某种方式从“SELECT o FROM Item o”中指定一个不同的查询...一个也获取 LobString 数据的查询...但这似乎不太正确如果它在这种特殊情况下会飞。
当我访问尚未加载的数据时,我希望 Hibernate 启动一个新会话……但显然,它并没有像我预期的那样工作。
熟悉 Hibernate 的人能否解释一下在这种情况下发生了什么,并可能建议可以使用哪些选项来处理这个问题?
谢谢
编辑1:
顺便说一句,如果我将以下 log4j.propeties 添加到类路径:
log4j.rootLogger=error, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Print the date in ISO 8601 format
log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
log4j.logger.org.hibernate.SQL=DEBUG
log4j.logger.org.hibernate.type=TRACE
我明白了:
persist - OK
2014-07-05 16:17:35,707 [main] DEBUG org.hibernate.SQL - select item0_.id as id1_, item0_.content as content1_, item0_.item_id as item2_1_, item0_.version as version1_ from item item0_
2014-07-05 16:17:35,711 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [id1_]
2014-07-05 16:17:35,713 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [content1_]
2014-07-05 16:17:35,713 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [abc123] as column [item2_1_]
2014-07-05 16:17:35,713 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version1_]
i.getId() = 1
i.getItemId() = abc123
2014-07-05 16:17:35,717 [main] ERROR org.hibernate.LazyInitializationException - could not initialize proxy - no Session
因此,正如“找到1”作为列 [content1_] 显示的行,我至少有表中包含 LobString 数据的行的外键......但我无法访问该 id 没有得到LazyInitializationException。如果我能掌握它,也许我可以通过 id 获取 LobString...但是 i.getContent() 和 i.getContent().getId() 给出了异常。外键应该与主键 id 匹配LobString 表 ieigetContent().getId().. 但似乎无法从 i... 访问外键...正如日志所示,它实际上是从数据库中获取的。
无论如何,即使我确实得到了外键......不得不留下 LobString.find(id) 或类似的东西不是一个好的解决方案。 Hibernate 应该填充您的对象图...
编辑2:
在此编辑中要提出 2 点。执行通过以下:org.springframework.orm.jpa.SharedEntityManagerCreator.DeferredQueryInvocationHandler.invoke(Object, Method, Object[])
:
// Invoke method on actual Query object.
try
Object retVal = method.invoke(this.target, args);
return (retVal == this.target ? proxy : retVal);
catch (InvocationTargetException ex)
throw ex.getTargetException();
finally
if (method.getName().equals("getResultList") || method.getName().equals("getSingleResult") ||
method.getName().equals("executeUpdate"))
EntityManagerFactoryUtils.closeEntityManager(this.em);
首先,如果使用“getResultList”,EntityManager 将被关闭。我假设 EntityManager 是一个 JPA 构造,无需详细说明,它映射到 Hibernate 的 Session。
第二......如果我在调试时深入研究,我可以看到应该延迟加载的数据“这是项目的内容”......所以我实际上已经将该数据加载到内存中...... 1)因为它不应该是懒惰的 2) 即使它在那里我也无法访问它,因为我得到了 LazyInitializationException。
在调试的时候,我可以看到数据加载在以下两个地方:
1) 我的主方法(见上:主方法中的 Item i = items.get(0))
com.memdrill.prototypes.datause.Main.main(String[])
:
i -> 内容 -> 处理程序 -> 目标 -> 数据:“这是项目的内容”
2) 在关闭 Session 的方法中:
org.springframework.orm.jpa.SharedEntityManagerCreator.DeferredQueryInvocationHandler.invoke(Object, Method, Object[])
:
retVal -> elementData[0] -> content -> handler -> target -> data: "this is the content of the item"
懒加载的数据太多了……我做错了什么?
我似乎也有延迟加载 LobString 的规范。但如果是这样的话,而且我确实有内存中的内容......为什么我不能访问它?
编辑3:
如果我在调试模式下保持足够长的时间,Edit2 中的行为(在内存中延迟加载数据)似乎只会在调试时发生.....
所以基本上,如果我在调试模式下运行并在断点处一直点击“播放”“播放”“播放”,我会得到 LazyInitializationException...但如果我在调试模式下停留足够长的时间,则不会出现 LazyInitializationException,我得到数据并打印到标准输出。
有什么想法吗?
编辑4:
感谢SO question,我现在能够急切地使用以下方法获取所有 LobString:
EntityManager em = Item.entityManager();
List<Item> items = em.createQuery("SELECT o FROM Item o JOIN FETCH o.content", Item.class).getResultList();
输出:
2014-07-06 21:44:56,862 [main] INFO org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean - Building JPA container EntityManagerFactory for persistence unit 'persistenceUnit'
in LobString default constructor
in LobString (String data) constructor
2014-07-06 21:44:57,891 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Creating new transaction with name [com.memdrill.data.entity.Item.persist]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2014-07-06 21:44:57,944 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Opened new EntityManager [org.hibernate.ejb.EntityManagerImpl@779d9c0d] for JPA transaction
2014-07-06 21:44:57,948 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Not exposing JPA transaction [org.hibernate.ejb.EntityManagerImpl@779d9c0d] as JDBC transaction because JpaDialect [org.springframework.orm.jpa.DefaultJpaDialect@d7e60a1] does not support JDBC Connection retrieval
2014-07-06 21:44:57,999 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Initiating transaction commit
2014-07-06 21:44:58,000 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [org.hibernate.ejb.EntityManagerImpl@779d9c0d]
2014-07-06 21:44:58,002 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.ejb.EntityManagerImpl@779d9c0d] after transaction
2014-07-06 21:44:58,002 [main] DEBUG org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
persist - OK - in Main of memdrill-data not data-use
2014-07-06 21:44:58,008 [main] DEBUG org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler - Creating new EntityManager for shared EntityManager invocation
in LobString default constructor
2014-07-06 21:44:58,133 [main] DEBUG org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
i.getId() = 1
i.getItemId() = abc123
i.getContent() = com.memdrill.data.util.LobString@6fe30af
i.getContent().getId() = 1
i.getContent().getData() = this is the content of the item
2014-07-06 21:44:58,134 [main] INFO org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory for persistence unit 'persistenceUnit'
这几乎是我想要的。我只想要 LobString 的 id。这个想法是这将是一个 REST 调用,我只想返回可用于单独请求的 LobString 的 Item “基本”数据和 id。
知道什么样的 JPQL 查询可以实现这一点吗? (任何有助于理解幕后情况的 cmets 将不胜感激) - 谢谢
【问题讨论】:
【参考方案1】:要解决此问题,您可以在非静态方法上使用 @Transactional
注释。
例子:
@RooJavaBean
@RooToString
@RooEntity
public class Item
@NotNull
@Column(unique = true)
@Size(min = 1, max = 200)
private String itemId;
@NotNull
@Lob
@OneToOne(cascade = javax.persistence.CascadeType.ALL, fetch = FetchType.LAZY)
private LobString content;
@Transactional(readOnly=true)
LobString loadContent()
getContent().getId();
return getContent();
然后,使用loadContent()
方法得到LobString
。
这是因为当您调用i.getContent()
时,与用于加载数据的数据库的连接已经关闭。
使用注解 @Transactional
的方法(警告它必须是非静态方法)强制上下文在执行方法主体之前创建(或继续,参见注解的 JavaDoc)数据库连接。因此,当 hibernate 在Item.content
属性中插入的Proxy
尝试从数据库加载数据时,它会找到一个活动连接。
【讨论】:
您好 jmvivo - 感谢您的回答 :) 我不确定我是否理解 loadContent() 的使用方式。在 findAllItems() 之后会话关闭,所以如果我尝试使用 getContent() 或 loadContent() 访问 Item 的内容,我会得到惰性初始化异常。顺便说一句,我已将代码上传到 bitbucket:bitbucket.org/jcalleja/lazy-init-exception 我刚刚更新了我的代码以强制加载。我认为应该使用loadContent()
解决问题
相同的结果 - 得到异常。如果您愿意,可以在 bitbucket 上的项目中尝试一下。问题是,在我调用 findAllItems() 之后,休眠会话被关闭,并且除非您启动另一个事务,否则无法访问原始查询“SELECT o FROM Item o”中未加载的任何数据。不幸的是,我现在正在编写一个测试来断言加载的预期数据并具有不同的行为......
行为最终符合预期。我在 Roo 生成的测试中推入了@Transactional。我把它推了进去,所以我可以把它注释掉,但是 Roo 正在运行,所以它在 aj 文件上重新生成了 @Transactional。这让我有点搞砸了。在任何情况下,我都解决了这个问题,并且在事务之外运行测试时,它始终通过。不确定这是否是测试这个(外部事务)的正确方法。但不幸的是,这就是代码的设置方式。这个问题的真正答案(我猜)是如何/为什么将事务限定为 getResultList()
"@Transactional annotation on a no-static method" 是我延迟加载问题的答案 - 非常感谢。【参考方案2】:
好的,所以答案毕竟是:SO getid
我最终在 LobString_Roo_Entity.aj 中推入 private Long id
并添加了 @Access(AccessType.PROPERTY)
注释:
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
@Access(AccessType.PROPERTY)
private Long id;
现在,使用 Item.findAllItems() 如下所示:
Item item = new Item();
item.setItemId("abc123");
item.setContent(new LobString("this is the lob data"));
item.persist();
System.out.println("persist - OK");
List<Item> items = Item.findAllItems();
Item i = items.get(0);
System.out.println("i.getId() = " + i.getId());
System.out.println("i.getItemId() = " + i.getItemId());
// System.out.println("i.getContent() = " + i.getContent());
System.out.println("i.getContent().getId() = " + i.getContent().getId());
System.out.println("i.getContent().getData() = " + i.getContent().getData());
当我尝试i.getContent().getData()
或者即使我尝试i.getContent()
(这就是它被注释掉的原因)时,我得到了 LazyInitializationException。但是,如果我这样做 i.getContent().getId()
... 我会得到 id。它的工作原理很奇怪(例如,i.getContent()
会爆炸,但 i.getContent().getId()
不会)。
这是上面代码的控制台输出:
i.getId() = 1
i.getItemId() = abc123
i.getContent().getId() = 1
2014-07-06 22:32:51,309 [main] ERROR org.hibernate.LazyInitializationException - could not initialize proxy - no Session
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
如果注释掉的行未注释,则 LazyInitializationException 在打印i.getContent().getId() = 1
之前发生。
这是我一直在寻找的行为......不幸的是......我真的不明白发生了什么。如果你认为你可以解释一下发生了什么,请做!
谢谢
编辑1:
注意:我不认为这是一个答案,因为它只回答了这种特殊情况。我需要弄清楚如何正确划分交易。我猜我可以手动实例化事务并在代码中关注横切关注点。我想要的是弄清楚当前的配置。是什么将事务限制为 findAllItems()?否则如何配置?
【讨论】:
【参考方案3】:这个答案列出了我犯的一些错误,以防你也这样做。错误在于事务划分。
错误 1:
在 Roo 项目的 pom 文件中,我有:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<!-- NB: do use 1.3 or 1.3.x due to MASPECTJ-90 - wait for 1.4 -->
<!-- <version>1.5</version> -->
<version>1.2</version>
<dependencies>
<!-- NB: You must use Maven 2.0.9 or above or these are ignored (see MNG-2972) -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>$aspectj.version</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>$aspectj.version</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<outxml>true</outxml>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
<complianceLevel>1.6</complianceLevel>
<source>1.6</source>
<target>1.6</target>
<!-- <complianceLevel>$java.version</complianceLevel> -->
<!-- <source>$java.version</source> -->
<!-- <target>$java.version</target> -->
</configuration>
</plugin>
即spring-aspects 正在编译到项目中,正如问题中的 spring 配置所示,我的 Spring 上下文配置为使用:mode="aspectj"
in <tx:annotation-driven>
。现在,我认为 Spring 有某种方式可以配置为在运行时生成这些方面,但在我的情况下,我对所有事情都一无所知,在另一个项目中使用 @Transactional
,它的构建中没有方面编译,所以没有方面已生成.. 所以是的,显然@Transactional
变得非常无用。
如果你 tree ./target/classes
maven 生成项目的编译代码,你应该会看到类似 $AjcClosure1.class 之类的东西。这些不是在没有在 pom.xml 中编译 aspectj 的项目中生成的,而是使用 @Transactional
无论如何 - (您也可以使用 javap -classpath ./target/classes com.blah.MyClass
在原始类上查看编织代码)。
所以第 1 课:确保您在使用 mode="aspectj" 时在代码中实际编织 spring-aspects 的项目中使用 @Transactional
。
错误2:
这是迄今为止的反复试验,但 @Transactional
带注释的方法显然需要是非静态公共的。这有效:
@Transactional(readOnly=true)
public List<Item> findAllItemsWithContentId()
List<Item> items = entityManager().createQuery(
"SELECT o FROM Item o", Item.class)
.getResultList();
for(Item item: items)
System.out.println("about to item.getContent().getId()");
System.out.println(item.getContent().getId());
return items;
就像这样,我可以让事务跨越整个方法,而不仅仅是 getResultList() 调用。
不幸的是,这似乎也获取了 LobString 的所有数据(即使它是延迟定义的)。即如果我尝试:
List<Item> items = new Item().findAllItemsWithContentId();
Item i = items.get(0);
System.out.println("i.getId() = " + i.getId());
System.out.println("i.getItemId() = " + i.getItemId());
System.out.println("i.getContent() = " + i.getContent());
System.out.println("i.getContent().getId() = " + i.getContent().getId());
System.out.println("i.getContent().getData() = " + i.getContent().getData());
我没有得到 LazyInitializationException,这意味着访问 item.getContent().getId()
时仍在 findAllItemsWithContentId()
的事务中最终获取所有数据。如果我将 for 循环排除在 findAllItemsWithContentId()
之外,那么我确实会从 System.out.println 中获取 LazyInitializationException,以获取未在事务方法中获取的数据。
...所以是的,这是另一个问题。
但请注意,这是在我的其他答案中没有 @Access(AccessType.PROPERTY)
的情况下完成的。如果使用它,那么我可以在不获取内容的情况下获取 LobString 的 id。然后,此答案提供了有关扩展事务范围的一些信息。
所以基本上:1. 在编译方面的项目中使用 2. 在公共实例方法上使用。
干杯
【讨论】:
以上是关于使用 Spring Roo 项目并获取 org.hibernate.LazyInitializationException: could not initialize proxy - no Sessi的主要内容,如果未能解决你的问题,请参考以下文章
使用Spring Data防止MongoDB中的重复(Spring Roo)