Spring Boot Data JPA:休眠会话问题

Posted

技术标签:

【中文标题】Spring Boot Data JPA:休眠会话问题【英文标题】:Spring Boot Data JPA: Hibernate Session issue 【发布时间】:2014-08-14 14:55:37 【问题描述】:

我正在开发一个基于 Spring Boot 的 Web 应用程序。我严重依赖 @ComponentScan@EnableAutoConfiguration 并且没有明确的 XML 配置。

我有以下问题。我有一个名为 UserSettingsJPA-带注释的实体类:

@Entity public class UserSettings 

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @OneToMany(cascade = CascadeType.ALL)
    private Set<Preference> preferences; // 'Preference' is another @Entity class

    public UserSettings() 
         this.preferences = new HashSet<Preference>();
    

// some more primitive properties, Getters, Setters...

我按照this 教程创建了一个扩展JpaRepository&lt;UserSettings,Long&gt; 的存储库接口。

此外,我有一个UserManager bean:

@Component public class SettingsManager 

@Autowired
UserSettingsRepository settingsRepository;

@PostConstruct
protected void init() 
    // 'findGlobalSettings' is a simple custom HQL query 
    UserSettings globalSettings = this.settingsRepository.findGlobalSettings();
    if (globalSettings == null) 
        globalSettings = new UserSettings();
        this.settingsRepository.saveAndFlush(globalSettings);
    

稍后在代码中,我再次使用findGlobalSetttings 查询加载此处创建的UserSettings 对象。

问题是:每次我尝试访问设置对象的@OneToMany属性时,都会出现以下异常:

org.hibernate.LazyInitializationException: 无法延迟初始化角色 org.example.UserSettings.preferences 的集合,无法初始化代理 - 没有会话

我了解每个 HTTP 会话都有自己的休眠会话,如this question 的接受答案中所述,但这不适用于我的情况(目前我正在同一个 HTTP 会话中对此进行测试),即为什么我不知道这个异常是从哪里来的。

我做错了什么,如何避免错误?

【问题讨论】:

休眠会话不是 Http 会话。一旦您的交易结束,休眠会话就会关闭。不是附加到 http 会话的休眠会话。它附加到当前事务(或在查看请求时将其与打开的会话/实体管理器一起使用)。 感谢您指出这一点。但是,我相信错误存在于 Spring 在运行时自动生成的 JpaRepository 类的实现中。我只是尝试使用 EntityManager 直接检索所需的实例——效果很好。使用 JpaRepository 进行相同的检索会产生具有相同 ID (!) 的同一类的对象,但该对象在 == 运算符方面与实体管理器返回的对象不同... 不,这不是同一个问题,与 Spring Data 无关。从 2 个不同的实体管理器中检索对象将始终为您提供另一个对象。问题很简单,因为您将引用存储在 http 会话中,当您尝试从会话中对该实体执行操作时,原始休眠会话就消失了。每次需要时检索它,或者在将对象放入会话之前完全初始化它。 我只是仔细检查了一遍:底层 Hibernate 'Session' 对象在存储我的 UserSettings 对象时与检索 UserSettings 对象时具有相同的 Java 对象 ID。据我所知(我使用 Vaadin 作为 Web UI 工具包和 Vaadin4Spring),我仍然在同一个 HTTP 会话和请求中。无论如何,在存储和查询它时我得到了一个不同的 UserSettings 对象,并且我得到了上述异常。 正如在事务结束后会话关闭之前两次所述,除非您在视图中使用打开的会话,这将保持休眠会话直到呈现视图。否则,一旦 tx 完成,休眠会话就会消失。 【参考方案1】:

如果您希望能够访问事务之外的映射实体(您似乎正在这样做),您需要将其标记为“渴望”加入。即

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)

【讨论】:

如果您以前没有使用过它,可能值得注意的是,通过将连接标记为急切,JPA/Hibernate 会将连接添加到所有查询并生成连接对象。正如您可能想象的那样,过度使用会导致生成大量实体图的大而慢的查询。因此,请注意不要在任何地方使用它。 :) 是的,我是这么认为的。我会小心的。目前,我的应用程序中的域模型非常简单,元素可达性闭包也很小,所以我想我现在可以接受它。如果性能受到明显影响,无论如何我将不得不考虑其他事情。感谢您指出这一点。 btw ... 如果确实出现问题,通常的替代方法是创建一个 @Transactional 方法,该方法返回将在页面上呈现的完整模型,而不是传递您的实体无论您的视图呈现什么。要么这样,要么您可以在 @Transactional 方法中遍历延迟加载的集合,以强制生成它们。 我会确保记住这一点,可能会派上用场。我们的数据模型相当简单(至少是 O/R 映射的部分),但应用程序本身相当复杂。所以也许我们需要尽快使用它。谢谢!【参考方案2】:

@Steve 已经很好地回答了这个问题。但是,如果您仍想维护您的 lazy loading 实现,您可能想尝试一下

import javax.servlet.Filter;

import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.hibernate4.support.OpenSessionInViewFilter;  

@Configuration
@ComponentScan
public class AppConfig 

    @Bean
    public FilterRegistrationBean filterRegistration() 
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(openSessionInView());
        registration.addUrlPatterns("/*");

        return registration;
    

    @Bean
    public Filter openSessionInView() 
        return new OpenSessionInViewFilter();
    

此配置的作用是,它会在对路径 "/*" 的请求上注册一个 Filter,从而使您的 Hibernate Session 在您的视图中保持打开状态。

这是anti-pattern,必须小心使用。

注意:从 Spring Boot 1.3.5.RELEASE 开始,当您使用 Spring Data JPA 自动配置的默认配置时,您应该不会遇到此问题

【讨论】:

OpenEntityManagerInViewInterceptor 是自动注册的,但是当过滤器中发生逻辑时它不起作用,对我来说,我注册了 OpenEntityManagerInViewFilter 并解决了我的问题。【参考方案3】:

我在 Spring Boot 应用程序中遇到了类似的问题,谷歌搜索后,我可以通过将以下代码添加到我的应用程序来解决此问题。

    @Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, DataSource dataSource) 
    LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
    entityManagerFactoryBean.setDataSource(dataSource);
    entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
    Properties jpaProperties = new Properties();
    jpaProperties.put("hibernate.enable_lazy_load_no_trans", true);
    entityManagerFactoryBean.setJpaProperties(jpaProperties);
    return entityManagerFactoryBean;

推荐here。

【讨论】:

以上是关于Spring Boot Data JPA:休眠会话问题的主要内容,如果未能解决你的问题,请参考以下文章

在spring boot中使用JDBC和spring data jpa为我的会话spring创建单独的数据源

spring-data-jpa 和 spring-boot-starter-data-jpa 的区别

从控制台应用程序使用带有休眠功能的spring-data-jpa时如何延迟加载收集

休眠 | Spring Data JPA | @OneToOne

具有多租户休眠的 Spring-Data JPA

spring boot 1.5.1.RELEASE 问题与休眠会话工厂创建有关