2014-07-05 23 views
0

Edit5:使用Spring Roo的项目,并获得org.hibernate.LazyInitializationException:无法初始化代理 - 没有会话

用来探讨这个问题解决方案的代码是高达上到位桶:https://bitbucket.org/jcalleja/lazy-init-exception


我意识到在SO上还有很多其他的LazyInitializationException帖子,但我无法从他们那里获得我需要的信息。

所涉及的项目有:

  • memdrill数据
  • 数据使用

memdrill数据是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在这次讨论中,但在这里它是无论如何(按不变袋鼠产生后):

<?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()是由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可以解释发生了什么事在这种情况下,也许有什么建议可供选择,以应对呢?

感谢

EDIT1:

顺便说一句,如果我添加以下log4j.propeties到classpath:

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 as co lumn [content1_]显示,我至少在表中包含LobString数据的表中的行的外键...但我不能访问该ID没有得到LazyInitializationException。也许我可以通过id获取LobString,如果我可以抓住它...但是i.getContent()和i.getContent()。getId()会给出异常。外键应该匹配LobString表ieigetContent()的主键ID。的getId()..但似乎没有访问来自我的外键......它作为日志显示,竟是从获取方式分贝。

在任何情况下,即使我没有得到外键......不必留LobString.find(ID)或类似的东西是不是一个很好的解决方案。休眠应该填入你的对象图...

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); 
      } 
     } 

首先,如果“getResultList”用于EntityManager的被关闭。我假定EntityManager是一个JPA构造,它不需要详细说明就可以映射到Hibernate的Session。

二...如果我深入不够,而调试我可以看到什么应该被延迟加载数据“这是项目的内容” ......所以我实际上有加载到内存中的数据... 1)因为它是懒惰的它不应该2)即使它在那里我不能访问它,因为我得到LazyInitializationException。

调试时,我可以看到数据被加载在以下两个位置:

1)我的主要方法(见上文:项目i = items.get(0)中的主要方法) com.memdrill.prototypes.datause.Main.main(String[])

I - >内容 - >处理 - >目标 - >数据: “这是该项目的内容”

2)在该方法中该关闭会话: org.springframework.orm.jpa.SharedEntityManagerCreator.DeferredQueryInvocationHandler.invoke(Object, Method, Object[])

重新tVal - > elementData [0] - >内容 - >处理程序 - >目标 - >数据:“这是项目的内容”

对于懒惰加载的数据太多了......我在做什么如此严重的错误?

我似乎已经得到了LobString的规格也被延迟加载。但是,如果是这样的话,我确实有内存中的内容...为什么我不能访问它?

EDIT3:

它看起来像在EDIT2行为(具有在内存中的延迟加载数据)仅在调试时,如果我留足够长的时间在调试模式下发生.....

所以基本上,如果我运行调试,并通过我的断点一路点击“播放”,“播放”,“播放”,我得到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 [[email protected]] for JPA transaction 
2014-07-06 21:44:57,948 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Not exposing JPA transaction [[email protected]] as JDBC transaction because JpaDialect [[email protected]] 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 [[email protected]] 
2014-07-06 21:44:58,002 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [[email protected]] 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() = [email protected] 
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的项目“基本”数据和ID。

任何想法什么样的JPQL查询会实现这一目标? (一般的任何意见,以帮助理解这是怎么回事引擎盖下,将不胜感激) - 感谢

回答

0

这个答案列出了我在做同样的事情时所犯的一些错误。错误是关于交易划分。

错误1:

在袋鼠项目的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的配置了,我的Spring环境配置为使用:中的mode="aspectj"。现在,我认为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

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()交易最终获取的所有数据。如果我离开findAllItemsWithContentId()的for循环,那么我会从System.out.println的LazyInitializationException中获取未在事务性方法中获取的数据。

...所以是的,那是另一个问题。

但是请注意,这是在我的其他答案没有@Access(AccessType.PROPERTY)。如果使用了那么我就可以得到LobString的ID而不需要获取内容。这个答案然后提供了一些关于扩展事务范围的信息。

所以基本上:1.用于编译方面的工程2.用于公开实例方法。

干杯

0

好了,得到的答复是对SO毕竟:SO getid

我结束了在LobString_Roo_Entity在private Long id推动。 AJ并添加@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 

如果注释线未被注释,当打印i.getContent().getId() = 1之前LazyInitializationException中发生的情况。

这是我一直在寻找的行为......不幸的是......我不太明白发生了什么事。如果你认为你可以解释一些事情,请做!

感谢

EDIT1:

注:我不认为这是一个答案,因为它只回答了这个特定的情况下。我需要弄清楚如何正确划分交易。我猜测我可以手动实例化事务并在代码中具有交叉关注的问题。我想要弄清楚当前的配置。什么是限制交易findAllItems()?那么如何配置呢?

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()时,用于加载数据的DB连接已关闭。

使用注释@Transactional的方法(警告它必须是没有静态方法)联手打造(或继续,见注解的Javadoc)一个数据库连接之前执行方法体的上下文。所以,当Proxy通过休眠插入Item.content属性尝试从数据库加载数据时,它找到了一个活动连接。

+0

嗨jmvivo - 感谢回答:)我不知道我明白如何使用loadContent()。在findAllItems()之后,会话关闭,所以如果我尝试使用getContent()或loadContent()来访问Item的内容,我会得到惰性初始化异常。顺便说一句,我已经上传代码到bitbucket:https://bitbucket.org/jcalleja/lazy-init-exception – justin

+0

我刚刚更新我的代码强制加载。我认为应该使用'loadContent()'来解决问题' – jmvivo

+0

同样的结果 - 得到异常。如果你愿意,你可以在bitbucket上的项目中试用它。问题是,在我调用findAllItems()之后,关闭了Hibernate Session,并且除非启动另一个Transaction,否则在原始查询“SELECT o FROM Item o”中未加载的任何数据都将无法访问。不幸的是,我现在正在编写一个测试来声明预期的数据加载并具有不同的行为... – justin

相关问题