簡體   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