繁体   English   中英

Spring MVC + Hibernate:无法初始化代理 - 没有Session

[英]Spring MVC + Hibernate: could not initialize proxy - no Session

注意:请参阅我自己对此问题的回答,以获取我如何解决此问题的示例。

我在Spring MVC 4 + Hibernate 4项目中遇到以下异常:

org.hibernate.LazyInitializationException:懒得初始化一个角色集合:com.mysite.Company.acknowledgements,无法初始化代理 - 没有Session

在阅读了很多关于这个问题的其他问题后,我理解为什么会发生这种异常,但我不确定如何以一种好的方式解决它。 我正在做以下事情:

  1. 我的Spring MVC控制器调用服务中的方法
  2. service方法调用DAO类中的方法
  3. DAO类中的方法通过Hibernate获取实体对象并将其返回给调用服务
  4. 该服务将获取的对象返回给控制器
  5. 控制器将对象传递给视图(JSP)
  6. 该视图尝试迭代一个懒惰加载的多对多关联(因此是一个代理对象)
  7. 抛出异常是因为此时会话已关闭,并且代理无法加载关联数据

我之前使用过PHP和doctrine2,这种做事方式没有问题。 我试图找出解决这个问题的最佳方法,因为到目前为止我找到的解决方案看起来并不那么好:

  • 急切加载关联,可能会加载大量不必要的数据
  • 调用Hibernate.initialize(myObject.getAssociation()); - 这意味着我必须遍历关联来初始化它们(我猜),而且事实上我必须这样做,有点使得延迟加载不那么整洁
  • 使用Spring过滤器在视图中打开会话,但我怀疑这是一件好事吗?

我尝试在我的服务中使用@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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM