无法懒惰初始化角色集合,..无法初始化代理-没有会话-JPA+SPRING

17

我正在使用JPA(使用Hibernate 4.3.3作为持久化提供程序)与Spring(3.2.2),所有字段都正常加载,但当我尝试访问我的集合时,它会抛出错误 -

Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.br.common.catalog.entity.Category.allParentCategoryXrefs, could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:572)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:212)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:551)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:140)
    at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:526)
    at java.lang.String.valueOf(String.java:2827)
    at java.io.PrintStream.println(PrintStream.java:771)
    at test.Test.main(Test.java:30)

当我调试时,我的实体类中每个定义的集合都出现错误 - com.sun.jdi.InvocationException occurred invoking method.

我尝试使用collection.size()Hibernate.initialize(),但都没有起作用。 在网上搜索后,我发现扩展Persitence可以解决这个问题。

 @PersistenceContext(type=PersistenceContextType.EXTENDED)

        protected EntityManager em;

这个可以工作得很好,但通过这个我发现em将始终保持打开状态,现在Spring将无法处理这个。有没有办法使用Spring解决这个问题。非常感谢任何帮助。

我的实体如下 -

  @Entity
    @Inheritance(strategy = InheritanceType.JOINED)
    @Table(name="CATEGORY")
    public class Category implements Serializable {
       @Id
        @GeneratedValue(generator= "CategoryId")
         @Column(name = "CATEGORY_ID")
        protected Long id;

        @ManyToOne(targetEntity = Category.class)
        @JoinColumn(name = "DEFAULT_PARENT_CATEGORY_ID")
        @Index(name="CATEGORY_PARENT_INDEX", columnNames={"DEFAULT_PARENT_CATEGORY_ID"})
        protected Category defaultParentCategory;

        @OneToMany(targetEntity = Categoryref.class, mappedBy = "categoryrefPK.category")
        @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region="test")
        @OrderBy(value="displayOrder")
        @BatchSize(size = 50)

        protected List<Categoryref> childCategoryRefs = new ArrayList<Categoryref>(10);
        @OneToMany(targetEntity = Categoryref.class, mappedBy = "categoryrefPK.subCategory",fetch=FetchType.LAZY)
        @Cascade(value={org.hibernate.annotations.CascadeType.MERGE, org.hibernate.annotations.CascadeType.PERSIST})
        @OrderBy(value="displayOrder")
        @BatchSize(size = 50)
        protected List<Categoryref> parentCategoryRefs = new ArrayList<Categoryref>(10);

    }


@Entity
@Polymorphism(type = PolymorphismType.EXPLICIT)
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "CATEGORY_REF")
public class Categoryref implements Serializable {
    /** The category id. */
    @EmbeddedId
    CategoryrefPK categoryrefPK = new CategoryrefPK();

    public CategoryrefPK getCategoryrefPK() {
        return categoryrefPK;
    }

    public void setCategoryrefPK(final CategoryrefPK categoryrefPK) {
        this.categoryrefPK = categoryrefPK;
    }
  }

@Embeddable
public class CategoryrefPK implements Serializable {

    @ManyToOne(targetEntity = Category.class, optional=false)
    @JoinColumn(name = "CATEGORY_ID")
    protected Category category = new Category();

    @ManyToOne(targetEntity = Category.class, optional=false)
    @JoinColumn(name = "SUB_CATEGORY_ID")
    protected Category subCategory = new Category();

}

我的Xml配置如下-

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-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/tx  
           http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.br" />

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        ....
    </bean>
<!--  this is also used we can used this also  -->
  <tx:annotation-driven transaction-manager="transactionManager" />  
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml" />
        <property name="dataSource" ref="dataSource" />
         <property name="persistenceUnitName" value="abc" />   
        <property name="packagesToScan" value="com.br.common.*" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="true" />
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
            </bean>
        </property>
    </bean>  


</beans>  

Persistence.xml

    <?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
    xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="abc">
        transaction-type="RESOURCE_LOCAL">
          <mapping-file>META-INF/category.orm.xml</mapping-file>
        <class>com.br.common.Category</class>
        <class>com.br.common.Categoryref</class>
        <class>com.br.common.CategoryrefPK</class>

        <properties>
            <property name="javax.persistence.jdbc.user" value="user"
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"></property>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/test>
            <property name="javax.persistence.jdbc.password" value="...">
            <property name="hibernate.show_sql" value="true" />

            <property name="hibernate.transaction.flush_before_completion"
                value="false" />
            <property name="hibernate.connection.autocommit" value="true" />
            <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/>
            <property name="hibernate.cache.use_second_level_cache" value="true" /> 
            <property name="hibernate.cache.use_query_cache" value="true"/>
            <property name="hibernate.generate_statistics" value="false" />
            <property name="hibernate.archive.autodetection" value="false" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
            <property name="hibernate.id.new_generator_mappings" value="true" />
        </properties>
    </persistence-unit>
</persistence>

这是我的DAO,我通过服务层调用DAO。

@Repository("categoryDaoImpl")
public class CategoryDaoImpl implements ICategoryDAO {

    @PersistenceContext
    protected EntityManager em;


    public Category save(Category category) {
      Category category2=   em.merge(category);

         em.flush();
         return category2;
  }
    public Category readCategoryById(Long categoryId) {
        return em.find(Category.class, categoryId);
    }


}

服务层

    @Service("blCatalogService")
@Transactional(propagation=Propagation.REQUIRED)
public class CatalogServiceImpl implements ICatalogService {

    @Resource(name="categoryDaoImpl")
    protected ICategoryDAO categoryDao;

    @Transactional
    public Product saveProduct(Product product) {
        return productDao.save(product);
    }

    public Category findCategoryById(Long categoryId) {
        return categoryDao.readCategoryById(categoryId);
    }

}

这是主要内容

public class Test {


        public static void main(String[] args) {
                ApplicationContext context = new ClassPathXmlApplicationContext(
                    "applicationContext-persistence.xml");
            ICatalogService serviceCategory= (ICatalogService) context
                    .getBean("blCatalogService");
                Category parentCategory=serviceCategory.findCategoryById(2l);
    System.out.println(parentCategory.getAllParentCategoryrefs());//here error is coming while accessing collection
    }
    }

这个问题已经被问了好几次,你正在尝试在会话上下文之外访问子集合,你可能需要在会话上下文内加载它以避免出现此错误。 - Zeus
@Zeus 我知道这个错误是懒加载初始化时出现的,但我想通过使用Spring来解决它。 - henrycharles
4个回答

21

问题在于当这个方法调用返回时:

Category parentCategory=serviceCategory.findCategoryById(2l);

在这里,您已经不再处于@Transactional上下文中。这意味着与parentCategory链接的会话已经关闭。现在当您尝试访问链接到已关闭会话的集合时,将出现No Session错误。

需要注意的一点是,主方法在任何Spring Bean之外运行,没有持久化上下文的概念。

解决方案是从一个事务上下文中调用parentCategory.getAllParentCategoryrefs(),这永远不能是应用程序的主方法。

然后重新将parentCategory附加到新的持久化上下文中,然后调用getter。

例如,尝试将parentCategory传回同一服务的事务性方法:

serviceCategory.nowItWorks(parentCategory);

服务中的方法是事务性的:

@Transactional(readOnly=true)
public void nowItWorks(Category category) {
    dao.nowItWorks(category);
}

还有在DAO中:

public void nowItWorks(Category category) {
    Category reattached = em.merge(category);
    System.out.println("It works: " + reattached.getAllParentCategoryrefs());
}

在我的情况下,我有两种方法。一种是获取主实体,另一种是获取关联实体的惰性加载。我不得不在这两个方法中都加上 @Transactional,这样它就起作用了。 - Pedro Romão

10

就像 @Zeus 所说,这个问题已经被回答过很多次了。你在测试类中遇到这个问题是因为你的事务在服务调用时开始和结束:

Category parentCategory=serviceCategory.findCategoryById(2l);

回想一下 Hibernate 文档中提到的懒加载只在 Hibernate Session 内起作用(在这种情况下,Hibernate 会话始于您的服务调用并结束于其调用结束)。你不能重新连接到 Hibernate 会话 (仅仅是)来初始化集合。

当你想要"在spring中"解决它时,我不确定你具体意思。因为这不是一个Spring问题。基本上有两种解决方法:在加载父对象时,将集合加载到 Hibernate 会话中或在原始 Hibernate 会话之外执行单独的数据获取操作。


4
我不知道为什么你们会认为这是一种简单的解决方案。我一直在关注Zeus的回复,但是他们中没有一个对我有用。我尝试了Transactional,但它只会返回null而不是错误响应,这让情况变得更糟。我也尝试了其他人的解决方案,但大多数都已经过时了。我正在使用JUNIT和嵌入式Hibernate。 - numerical25
我认为这是一个答案简单但解决方案复杂的情况。我并不是要将问题轻描淡写。答案取决于您的代码设计方式以及您尝试进行测试的级别。Spring为您抽象了很多样板代码,但您必须知道它的工作原理才能有效地使用它。 - Aaron

4

在您的领域集合上使用@Fetch(FetchMode.SELECT)@LazyCollection(LazyCollectionOption.FALSE),它将起作用。


只有使用@LazyCollection(LazyCollectionOption.FALSE),它就可以正常工作了,您是否有使用非Hibernate库的解决方案?谢谢。 - José Mendes
2
这与fetch = FetchType.EAGER完全相同。 - Uri Loya

-1

尝试使用fetch=FetchType.EAGER,它会起作用的。


7
这是一种快速但并非始终最佳的解决方案。 - Amer A.
1
但是仍值得一提。 - Sebastian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接