简体   繁体   English

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

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

Note: See my own answer to this question for an example of how I solved this issue. 注意:请参阅我自己对此问题的回答,以获取我如何解决此问题的示例。

I am getting the following exception in my Spring MVC 4 + Hibernate 4 project: 我在Spring MVC 4 + Hibernate 4项目中遇到以下异常:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.mysite.Company.acknowledgements, could not initialize proxy - no Session org.hibernate.LazyInitializationException:懒得初始化一个角色集合:com.mysite.Company.acknowledgements,无法初始化代理 - 没有Session

After reading a lot of other questions about this issue, I understand why this exception occurs, but I am not sure how to fix it in a good way. 在阅读了很多关于这个问题的其他问题后,我理解为什么会发生这种异常,但我不确定如何以一种好的方式解决它。 I am doing the following: 我正在做以下事情:

  1. My Spring MVC controller calls a method in a service 我的Spring MVC控制器调用服务中的方法
  2. The service method invokes a method in a DAO class service方法调用DAO类中的方法
  3. The method in the DAO class fetches an entity object through Hibernate and returns it to the calling service DAO类中的方法通过Hibernate获取实体对象并将其返回给调用服务
  4. The service returns the fetched object to the controller 该服务将获取的对象返回给控制器
  5. The controller passes the object to the view (JSP) 控制器将对象传递给视图(JSP)
  6. The view tries to iterate on a many-to-many association that is lazily loaded (and is thus a proxy object) 该视图尝试迭代一个懒惰加载的多对多关联(因此是一个代理对象)
  7. The exception is thrown because the session is closed at this point, and the proxy cannot load the associated data 抛出异常是因为此时会话已关闭,并且代理无法加载关联数据

I have previously worked with PHP and doctrine2, and this way of doing things caused no problems. 我之前使用过PHP和doctrine2,这种做事方式没有问题。 I am trying to figure out the best way to solve this, because the solutions that I found so far don't seem so great: 我试图找出解决这个问题的最佳方法,因为到目前为止我找到的解决方案看起来并不那么好:

  • Load the association eagerly, potentially loading lots of unnecessary data 急切加载关联,可能会加载大量不必要的数据
  • Call Hibernate.initialize(myObject.getAssociation()); 调用Hibernate.initialize(myObject.getAssociation()); - this means that I have to loop through associations to initialize them (I guess), and just the fact that I have to do this, kind of makes the lazy loading less neat - 这意味着我必须遍历关联来初始化它们(我猜),而且事实上我必须这样做,有点使得延迟加载不那么整洁
  • Using a Spring filter to leave the session open in the views, but I doubt that this is a good thing to do? 使用Spring过滤器在视图中打开会话,但我怀疑这是一件好事吗?

I tried to use @Transactional in my service, but without luck. 我尝试在我的服务中使用@Transactional ,但没有运气。 This makes sense because I am trying to access data that has not yet been loaded after my service method returns. 这是有道理的,因为我试图访问我的服务方法返回尚未加载的数据。 Ideally I would like to be able to access any association from within my view. 理想情况下,我希望能够从我的视图中访问任何关联。 I guess the drawback of initializing the associations within my service is that I have to explicitly define which data I need - but this depends on the context (controller) in which the service is used. 我想在我的服务中初始化关联的缺点是我必须明确定义我需要的数据 - 但这取决于使用服务的上下文(控制器)。 I am not sure if I can do this within my controller instead without losing the abstraction that the DBAL layer provides. 我不确定我是否可以在我的控制器中执行此操作而不会丢失DBAL层提供的抽象。 I hope that makes sense. 我希望这是有道理的。 Either way, it would be great if I didn't have to always explicitly define which data I want to be available to my view, but just let the view do its thing. 无论哪种方式,如果我不必总是明确定义我想要哪些数据可用于我的视图,那就太好了,但只是让视图做它的事情。 If that is not possible, then I am just looking for the most elegant solution to the problem. 如果那是不可能的,那么我只是在寻找最优雅的解决方案。

Below is my code. 以下是我的代码。

View 视图

<%@ 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 调节器

@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 服务

@Service
public class CompanyServiceImpl implements CompanyService {
    @Autowired
    private CompanyDao companyDao;

    @Override
    public Company get(int id) {
        return this.companyDao.get(id);
    }
}

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

Company entity 公司实体

@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
}

Acknowledgement entity 确认实体

@Entity
public class Acknowledgement {
    @Id
    private int id;

    // Other fields + getters and setters here

}

mvc-dispatcher-servlet.xml (a part of it) 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 />

Thanks in advance! 提前致谢!

As @ Predrag Maric said writing service methods with different initialization for entity assotiations might be an option. 正如@ Predrag Maric所说,编写具有不同初始化实体关联的服务方法可能是一种选择。

OpenSessionInView is a quit discussable pattern, which can be a solution in your case, though. OpenSessionInView是一个退出讨论模式,但在您的情况下可以是一个解决方案。

Another option is to set 另一个选择是设置

<prop key="hibernate.enable_lazy_load_no_trans">true</prop>

property. 属性。 It is designed to solve org.hibernate.LazyInitializationException problem and is available since hibernate 4.1.6. 它旨在解决org.hibernate.LazyInitializationException问题,并且自hibernate 4.1.6起可用。

So what does this property do? 那么这个属性有什么作用呢? It simply signals Hibernate that it should open a new session if session which is set inside current un-initialised proxy is closed. 它只是告诉Hibernate如果在当前未初始化的代理中设置的会话关闭,它应该打开一个新的会话。 You should be aware that if you have any other open session which is also used to manage current transaction, this newly opened session will be different and it may not participate into the current transaction unless it is JTA. 您应该知道,如果您还有用于管理当前事务的任何其他打开会话,则此新打开的会话将不同,并且除非是JTA,否则它可能不会参与当前事务。 Because of this TX side effect you should be careful against possible side effects in your system. 由于这种TX副作用,您应该小心系统中可能存在的副作用。

Find more information here: http://blog.harezmi.com.tr/hibernates-new-feature-for-overcoming-frustrating-lazyinitializationexceptions and Solve Hibernate Lazy-Init issue with hibernate.enable_lazy_load_no_trans 在此处查找更多信息: http//blog.harezmi.com.tr/hibernates-new-feature-for-overcoming-frustrating-lazyinitializationexceptions使用hibernate.enable_lazy_load_no_trans解决Hibernate Lazy-Init问题

The simplest and most transparent solution is the OSIV pattern. 最简单,最透明的解决方案是OSIV模式。 As I'm sure you aware, there is plenty of discussion about this (anti)pattern and the alternatives both on this site and elsewhere so no need to go over that again. 我相信你已经知道,关于这个(反)模式以及这个网站和其他地方的替代方案都有很多讨论,所以不需要再重复一遍。 For example: 例如:

Why is Hibernate Open Session in View considered a bad practice? 为什么Hibernate Open Session in View被认为是一种不好的做法?

However, in my opinion, not all criticisms of OSIV are entirely accurate (eg the transaction will not commit till your view is rendered? Really? If you are using the Spring implementation then https://stackoverflow.com/a/10664815/1356423 ) 但是,在我看来,并非所有对OSIV的批评都是完全准确的(例如,在您的视图呈现之前事务不会提交?真的吗?如果您使用的是Spring实现,那么https://stackoverflow.com/a/10664815/1356423

Also, be aware that JPA 2.1 introduced the concept of Fetch Graphs which give you more control over what is loaded. 另外,请注意JPA 2.1引入了Fetch Graphs的概念,它可以让您更好地控制加载的内容。 I haven't tried it yet but maybe this if finally a solution for this long standing problem! 我还没有尝试过,但也许这可能是最终解决这个长期存在的问题!

http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html

My vote goes to Hibernate.initialize(myObject.getAssociation()) in service layer (which also means @Transactional should be moved from DAO to service methods) 我的投票转到服务层中的Hibernate.initialize(myObject.getAssociation()) (这也意味着@Transactional应该从DAO转移到服务方法)

IMHO, service methods should return all data that is required by the caller. 恕我直言,服务方法应该返回调用者所需的所有数据。 If the caller wants to display some association on the view, it is service's responsibility to supply that data. 如果调用者想要在视图上显示某些关联,则服务负责提供该数据。 This means you could have several methods that apparently do the same thing (return Company instance), but depending on the caller, different associations could be fetched. 这意味着你可以有几个方法显然做同样的事情(返回Company实例),但根据调用者,可以获取不同的关联。

On one project we had kind of configuration class, which contained information on what associations should be fetched, and we had a single service method which also accepted that class as a parameter. 在一个项目中,我们有一种配置类,其中包含应该获取哪些关联的信息,并且我们有一个服务方法,它也接受该类作为参数。 This approach meant we have only one method which is flexible enough to support all callers. 这种方法意味着我们只有一种方法足够灵活,可以支持所有呼叫者。

@Override
public Company get(int id, FetchConfig fc) {
    Company result = this.companyDao.get(id);
    if (fc.isFetchAssociation1()) {
        Hibernate.initialize(result.getAssociation1());
    }
    ...
    return result;
}

For others who are looking for a solution, here is how I solved the problem. 对于正在寻找解决方案的其他人来说,这就是我解决问题的方法。

As Alan Hay pointed out, JPA 2.1+ supports entity graphs, which ended up solving my problem. 正如Alan Hay指出的那样,JPA 2.1+支持实体图,最终解决了我的问题。 To make use of it, I change my project to use the javax.persistence.EntityManager class instead of the SessionFactory , which would then hand me the current session. 为了使用它,我将我的项目更改为使用javax.persistence.EntityManager类而不是SessionFactory ,然后会将当前会话交给我。 Here is how I configured it in my dispatcher servlet configuration (some things excluded): 以下是我在调度程序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>

Below is an example of a DAO class. 下面是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);
    }
}

I found that if I did not use @PersistenceContext but Autowired instead, then the database session was not closed when rendering the view, and data updates were not reflected in subsequent queries. 我发现如果我没有使用@PersistenceContext而是使用Autowired ,那么在渲染视图时数据库会话没有关闭,并且数据更新没有反映在后续查询中。

For more information on entity graphs, I encourage you to read the following articles: 有关实体图的更多信息,建议您阅读以下文章:

http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html 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 http://www.thoughts-on-java.org/2014/04/jpa-21-entity-graph-part-2-define.html

Trigger in the service layer requires lazy loading of the Set's size method. 服务层中的触发器需要延迟加载Set的size方法。

Tools: 工具:

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

in your service method: 在您的服务方法中:

Apple apple = appleDao.findById('xxx');
HibernateUtil.triggerSize(apple.getColorSet());
return apple;

then use apple in controller, everything is ok! 然后在控制器中使用apple ,一切都好!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 LazyInitializationException:无法初始化代理-Spring和Hibernate中没有会话 - LazyInitializationException: could not initialize proxy - no Session in Spring and Hibernate Hibernate / Spring3:无法初始化代理 - 没有Session - Hibernate/Spring3: could not initialize proxy - no Session Vaadin Spring 和 Hibernate:无法初始化代理 - 否 Session - Vaadin Spring and Hibernate: could not initialize proxy - no Session 无法初始化代理-没有会话(春季休眠一对一) - could not initialize proxy - no Session (Spring-Hibernate-one to one) Spring JPA - org.hibernate.LazyInitializationException:无法初始化代理 - 无 Z71AB3B3AE294B3ABDE46 - Spring JPA - org.hibernate.LazyInitializationException: could not initialize proxy - no Session Spring DATA JPA + Hibernate-无法初始化代理-修复后无会话: - Spring DATA JPA + Hibernate - could not initialize proxy - no Session after fix: Spring MVC:无法初始化代理-没有会话(通过引用链) - Spring MVC :could not initialize proxy - no Session (through reference chain) 休眠延迟异常无法初始化代理-无会话 - hibernate lazy exception could not initialize proxy - no Session Hibernate中的LazyInitializationException:无法初始化代理 - 没有Session - LazyInitializationException in Hibernate : could not initialize proxy - no Session Hibernate 4 + Spring Data CrudRepository,CLI应用程序:无法延迟初始化集合:无法初始化代理-没有会话 - Hibernate 4 + Spring Data CrudRepository, CLI application: failed to lazily initialize a collection: could not initialize proxy - no Session
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM