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 配置。
我有以下问题。我有一个名为 UserSettings
的 JPA-带注释的实体类:
@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<UserSettings,Long>
的存储库接口。
此外,我有一个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时如何延迟加载收集