本文讨论了Spring JPA的其中一个配置:spring.jpa.open-in-view
,默认情况下该配置值为true。
【参考】
讲的很详细:https://www.baeldung.com/spring-open-session-in-view
官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.data.spring.jpa.open-in-view
1. 什么是Open Session in View(OSIV)
?
这个配置主要与Open Session in View(OSIV)
有关,即在view层(controller)打开Session。具体来理解:
- Spring会帮忙在request的一开始就打开Hibernate Session。
- 每当App需要一个Session的时候,就会重用这个Session。
- 在Request结束的时候,会帮忙关闭该Session。
那么Spring是如何做到的呢?一般来说可以用Filter或Interceptor来实现。具体可以看:OpenSessionInViewInterceptor
或OpenSessionInViewFilter
,可以看到在Interceptor的preHandle(...)
方法中先尝试打开Session(也就是request一开始):
logger.debug("Opening Hibernate Session in OpenSessionInViewInterceptor");
Session session = openSession();
在afterCompletion(...)
方法中尝试关闭Session(即request结束时):
logger.debug("Closing Hibernate Session in OpenSessionInViewInterceptor"); SessionFactoryUtils.closeSession(sessionHolder.getSession());
如果使用了Spring Boot Jpa,那么实现的类可能有所不同,但大同小异,具体的类为:OpenEntityManagerInViewInterceptor
、OpenEntityManagerInViewFilter
。
2. Spring配置的默认值
默认spring.jpa.open-in-view=true
,即在request一开始打开Session/结束时关闭Session。
3. 具体表现
我们具体来测试该功能。我之前写过一篇文章叫:【Spring JPA总结】JPA One-To-Many以及Many-To-One介绍](https://www.jianshu.com/p/1c279b221527)
。在文章中提到如果一对多mapping时,如果使用的是FetchType.LAZY
,那么需要保持Session打开,否则则会报异常:org.hibernate.LazyInitializationException
,可以用@Transactional
来解决。
3.1 代码实现
我们这里用User和Role两个实体来测试,一个用户(User)可以有多个权限(Role),首先是Entity
类(不相干的代码略):
@Entity
@Table(name = "USER_ENTITY")
public class User {
// id, name略
@OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY)
@JoinColumn(name = "user_id")
private Set<Role> roles;
}
@Entity
@Table(name = "ROLE_ENTITY")
public class Role {
// id, name略
}
接着是UserService
类(Autowired及其它注解略):
public class UserServiceImpl implements UserService {
@Transactional(readOnly = true)
public Optional<User> findOne(String username) {
return userRepository.findByName(username);
}
}
最后是UserController
类(Autowired及其它注解略):
public class UserController {
@GetMapping("find")
public User findOne() {
User user = userService.findOne("bill").get();
log.info("user role: {}", user.getRoles());
return user;
}
}
如果在执行user.getRoles()
代码的时候Session处于关闭状态,那么就会报上述的LazyInitializationException错。而我们已经介绍过了,要么将这段代码的方法上加上@Transactional(如放在Service的findOne方法中执行),还有另一种办法就是依赖于Spring对Open Session in View的支持,spring.jpa.open-in-view=true会保证在request前后使Session处于打开状态。
3.2 测试case:spring.jpa.open-in-view=true
首先是在application.properties
中配置(或是不配也可以,因为默认值即为true),顺便打开org.springframework.orm.jpa.support
包的DEBUG日志,以便更好的测试:
spring.jpa.open-in-view=true
logging.level.org.springframework.orm.jpa.support=DEBUG
浏览器访问API:/user/find
,打印日志:
2023-02-13T21:21:08.954+08:00 DEBUG 90094 --- [nio-8080-exec-2] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2023-02-13T21:21:09.092+08:00 INFO 90094 --- [nio-8080-exec-2] com.common.web.UserController : user role: [Role{id=1, name='admin'}]
2023-02-13T21:21:09.131+08:00 DEBUG 90094 --- [nio-8080-exec-2] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
可以看到OpenEntityManagerInViewInterceptor
会帮忙打开/关闭Session。
3.3 测试case:spring.jpa.open-in-view=false
在application.properties
中配置:
spring.jpa.open-in-view=false
logging.level.org.springframework.orm.jpa.support=DEBUG
运行后访问API报错:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.common.entity.User.roles: could not initialize proxy - no Session
4. 打开Open Session in View对Performance的影响
在#3介绍了Spring默认是打开Open Session in View
的,并且OSIV的打开可以很方便的避免LazyInitializationException
异常,那么它对生产环境造成的Performance有多少影响呢?
假如我们有两个微服务:order微服务
和user微服务
,那么在order微服务的OrderService
类中想要拿到User实体,需要通过Http请求(如通过RestTemplate或FeignClient)向user微服务请求User数据。
order微服务的OrderService
中的代码可能如下:
public Optional<Order> findOne(Long orderId) {
Optional<Order> order = orderRepository.findByOrderId(orderId);
if (order.isPresent()) {
// remote call for user
}
return order;
}
我们没有加@Transactional
注解是因为不想在做remote call的时候依旧保持Session的接连状态。
我们的本意是因为有远程http调用,所以不想使Session保持Open状态从而不使用@Transactional
注解。但是如果我们配置spring.jpa.open-in-view=true
依旧会使得整个request hold一个Open状态的Session,即每一次request请求都会从Database Connection Pool中拿到一个连接,假如remote http请求比较占用时间,那么该连接的释放时间就会比较久,从而无端的占用了数据库的连接。
5. 如何选择?
如果我们开发的是比较简单的CRUD服务,那么可以打开spring.jpa.open-in-view
。
如果我们的项目是微服务项目,有大量的remote调用,或是很多逻辑需要在transactional context外部进行,那么推荐关闭spring.jpa.open-in-view
。
网友评论