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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27299235/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 00:15:07  来源:igfitidea点击:

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

springhibernatespring-mvc

提问by ba0708

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,无法初始化代理 - 没有会话

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
  2. The service method invokes a method in a DAO class
  3. The method in the DAO class fetches an entity object through Hibernate and returns it to the calling service
  4. The service returns the fetched object to the controller
  5. The controller passes the object to the view (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
  1. 我的 Spring MVC 控制器调用服务中的方法
  2. 服务方法调用DAO类中的方法
  3. DAO类中的方法通过Hibernate获取实体对象并返回给调用服务
  4. 服务将获取的对象返回给控制器
  5. 控制器将对象传递给视图(JSP)
  6. 视图尝试迭代延迟加载的多对多关联(因此是代理对象)
  7. 抛出异常是因为此时会话关闭,代理无法加载关联数据

I have previously worked with PHP and doctrine2, and this way of doing things caused no problems. 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:

我之前用过PHP和doctor2,这种做事方式没有问题。我正在尝试找出解决此问题的最佳方法,因为到目前为止我找到的解决方案似乎不太好:

  • Load the association eagerly, potentially loading lots of unnecessary data
  • Call 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?
  • 急切地加载关联,可能加载大量不必要的数据
  • 调用Hibernate.initialize(myObject.getAssociation());- 这意味着我必须遍历关联来初始化它们(我猜),而且我必须这样做的事实使延迟加载变得不那么整洁
  • 使用 Spring 过滤器使会话在视图中保持打开状态,但我怀疑这是一件好事吗?

I tried to use @Transactionalin my service, but without luck. This makes sense because I am trying to access data that has not yet been loaded aftermy 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. 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.

我试图@Transactional在我的服务中使用,但没有运气。这是有道理的,因为我正在尝试访问之后尚未加载的数据我的服务方法返回。理想情况下,我希望能够从我的视图中访问任何关联。我想在我的服务中初始化关联的缺点是我必须明确定义我需要的数据 - 但这取决于使用服务的上下文(控制器)。我不确定是否可以在我的控制器中执行此操作而不丢失 DBAL 层提供的抽象。我希望这是有道理的。无论哪种方式,如果我不必总是明确定义哪些数据可用于我的视图,而是让视图完成它的工作,那就太好了。如果这是不可能的,那么我只是在寻找解决问题的最优雅的方法。

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

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

提前致谢!

采纳答案by Alan Hay

The simplest and most transparent solution is the OSIV pattern. As I'm sure you aware, there is plentyof discussion about this (anti)pattern and the alternatives both on this site and elsewhere so no need to go over that again. For example:

最简单和最透明的解决方案是 OSIV 模式。我相信你知道,在这个站点和其他地方有很多关于这个(反)模式和替代方案的讨论,所以没有必要再重复一遍。例如:

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 (e.g. 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. I haven't tried it yet but maybe this if finally a solution for this long standing problem!

另外,请注意 JPA 2.1 引入了 Fetch Graphs 的概念,它使您可以更好地控制加载的内容。我还没有尝试过,但也许这最终是解决这个长期存在的问题的方法!

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

回答by overthrown

As @Predrag Maricsaid 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>

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

property. It is designed to solve org.hibernate.LazyInitializationExceptionproblem 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. 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. Because of this TX side effect you should be careful against possible side effects in your system.

那么这个属性有什么作用呢?它只是向 Hibernate 发出信号,如果在当前未初始化的代理中设置的会话关闭,它应该打开一个新会话。您应该知道,如果您有任何其他打开的会话也用于管理当前事务,则这个新打开的会话将不同,除非它是 JTA,否则它可能不会参与当前事务。由于这种 TX 副作用,您应该注意系统中可能出现的副作用。

Find more information here: http://blog.harezmi.com.tr/hibernates-new-feature-for-overcoming-frustrating-lazyinitializationexceptionsand 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 问题

回答by Predrag Maric

My vote goes to Hibernate.initialize(myObject.getAssociation())in service layer (which also means @Transactionalshould 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 Companyinstance), 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;
}

回答by ba0708

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. To make use of it, I change my project to use the javax.persistence.EntityManagerclass instead of the SessionFactory, which would then hand me the current session. Here is how I configured it in my dispatcher servlet configuration (some things excluded):

正如 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>

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 @PersistenceContextbut Autowiredinstead, 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

回答by nvnpnco

Trigger in the service layer requires lazy loading of the Set's size method.

服务层的Trigger需要延迟加载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 applein controller, everything is ok!

然后apple在控制器中使用,一切正常!