美文网首页
【Spring JPA总结】Spring Boot JPA配置之

【Spring JPA总结】Spring Boot JPA配置之

作者: 伊丽莎白2015 | 来源:发表于2023-02-12 22:52 被阅读0次

本文讨论了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来实现。具体可以看:OpenSessionInViewInterceptorOpenSessionInViewFilter,可以看到在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,那么实现的类可能有所不同,但大同小异,具体的类为:OpenEntityManagerInViewInterceptorOpenEntityManagerInViewFilter

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

相关文章

网友评论

      本文标题:【Spring JPA总结】Spring Boot JPA配置之

      本文链接:https://www.haomeiwen.com/subject/mepdkdtx.html