Edit5:
用于探索此问题解决方案的代码取决于bitbucket:https://bitbucket.org/jcalleja/lazy-init-exception
我意识到SO上还有很多其他的LazyInitializationException帖子,但是我无法从他们那里得到我需要的信息。
涉及的项目:
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数据,我现在只是通过它测试驱动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()返回List。
正如其他SO帖子所建议的那样,我试过将@Transactional放在方法上,但我仍然得到LazyInitializationException。但即使这确实有效......为什么我需要启动事务来从数据库中读取数据? Aren的交易意味着在写作时保持数据完整性吗?
在我看来,解决这个方法的一种方法是以某种方式指定一个不同的查询来自&#34; SELECT o FROM Item o&#34; ...一个也获取LobString数据......但这并不是&# 39;即使它会在这种特殊情况下飞行,也显得非常正确。
当我访问尚未加载的数据时,我期待Hibernate开始一个新的会话......但显然,它并没有像我期望的那样工作。
熟悉Hibernate的人是否可以解释在这种情况下发生了什么?可能会建议有哪些选项可以解决这个问题?
由于
EDIT1:
顺便说一句,如果我将以下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
因为作为列[content1_]显示的行#34; 1,我至少在表中包含LobString数据的行的外键...但我可以&#39;在没有获得LazyInitializationException的情况下访问该id。也许我可以通过id获取LobString,如果我能抓住它...但i.getContent()和i.getContent()。getId()给出异常。外键应该匹配LobString表的主键id ieigetContent()。getId()..但似乎没有办法从i中访问外键...正如日志所示,实际上是从db。
在任何情况下,即使我确实获得了外键...必须保留LobString.find(id)或类似的东西不是一个很好的解决方案。 Hibernate应该填充你的对象图...
EDIT2:
在此编辑中要做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);
}
}
首先,如果&#34; getResultList&#34;正在关闭EntityManager。用来。我假设EntityManager是一个JPA结构,在没有详细说明的情况下,映射到Hibernate的会话。
第二......如果我在调试时向下钻取足够的东西,我可以看到什么应该是延迟加载的数据&#34;这是项目的内容&#34; ...所以我实际上已经将数据加载到内存中。 .. 1)因为它不应该是懒惰的,即使它在那里我也无法访问它,因为我得到了LazyInitializationException。
调试时,我可以看到数据是在以下两个地方加载的:
1)我的主要方法(见上文:主方法中的项目i = items.get(0))
com.memdrill.prototypes.datause.Main.main(String[])
:
i - &gt;内容 - &gt;处理程序 - &gt;目标 - &gt;数据:&#34;这是项目的内容&#34;
2)在关闭Session的方法中:
org.springframework.orm.jpa.SharedEntityManagerCreator.DeferredQueryInvocationHandler.invoke(Object, Method, Object[])
:
retVal - &gt; elementData [0] - &gt;内容 - &gt;处理程序 - &gt;目标 - &gt;数据:&#34;这是项目的内容&#34;
对于懒惰的数据来说太多了......我在做什么这么糟糕?
我似乎已经将LobString的规范也懒得加载了。但如果是这样的话,我确实在内存中有内容......为什么我无法访问它?
EDIT3:
看起来Edit2中的行为(在内存中加载延迟的数据)只会在调试时发生,如果我在调试模式下保持足够长的时间......
所以基本上,如果我在调试中运行并且只是点击&#34;播放&#34; &#34;发挥&#34; &#34;发挥&#34;一直到我的断点,我得到了LazyInitializationException ......但是如果我在调试模式下保持足够长的时间,没有LazyInitializationException,我会得到数据并打印到标准输出。
有什么想法吗?
Edit4:
感谢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调用,我只想返回Item&#34; basic&#34; LobString的数据和ID,可用于单独的请求。
知道什么样的JPQL查询会实现这个目标吗? (一般来说,任何评论都有助于理解引擎盖下的内容) - 谢谢
答案 0 :(得分: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)数据库连接。因此,当Proxy
属性中的hibernate插入的Item.content
尝试从DB加载数据时,它会建立一个活动连接。
答案 1 :(得分:0)
好的,毕竟答案是关于SO: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()
...我得到了身份证明。它的工作原理很奇怪(例如,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
如果注释掉的行被取消注释,则在打印i.getContent().getId() = 1
之前会发生LazyInitializationException。
这是我正在寻找的行为......不幸的是......我真的不明白发生了什么。如果你认为你可以解释一下发生了什么,请做!
由于
<强> EDIT1:强>
注意:我不认为这是一个答案,因为它只回答了这个特例。我需要弄清楚如何正确划分交易。我猜我可以手动实例化事务,并在代码中具有交叉问题。我想要的是弄清楚当前的配置。什么是限制事务findAllItems()?那怎么可以配置呢?
答案 2 :(得分:0)
这个答案列出了我在做同样事情时所犯的一些错误。关于交易划分的错误。
错误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"
中使用<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方面的项目中使用@Transactional
。
<强> Mistake2:强>
这是来自试验和错误,但@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.用于公共实例方法。
干杯