You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring中选择性禁用OSIV过滤器?大型应用渐进式迁移

渐进式禁用Spring Boot的Open Session In View(OSIV)方案

如果直接全局关闭OSIV(设置spring.jpa.open-in-view=false),大型应用很容易因为懒加载问题全面崩溃。下面是几种按端点逐步禁用OSIV的落地方法,适配渐进式迁移需求:

方法一:自定义OSIV过滤器,按路径精准控制

Spring Boot默认的OSIV由OpenEntityManagerInViewFilter实现,我们可以自定义这个过滤器的逻辑,让它对指定路径跳过OSIV处理:

  1. 先保持全局OSIV开启(spring.jpa.open-in-view=true),避免现有代码报错。
  2. 排除自动配置的OSIV过滤器,然后注册自定义版本:
@SpringBootApplication(exclude = OpenEntityManagerInViewAutoConfiguration.class)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 编写自定义OSIV过滤器:
@Configuration
public class CustomOsivConfig {
    @Bean
    public FilterRegistrationBean<OpenEntityManagerInViewFilter> customOpenEntityManagerInViewFilter() {
        FilterRegistrationBean<OpenEntityManagerInViewFilter> registrationBean = new FilterRegistrationBean<>();
        
        OpenEntityManagerInViewFilter customFilter = new OpenEntityManagerInViewFilter() {
            @Override
            protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
                // 匹配需要禁用OSIV的端点路径,比如/v2开头的接口
                String requestUri = request.getRequestURI();
                return requestUri.startsWith("/api/v2/") || requestUri.equals("/api/admin/dashboard");
            }
        };
        
        registrationBean.setFilter(customFilter);
        // 保持和默认OSIV过滤器相同的优先级
        registrationBean.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
        return registrationBean;
    }
}

注意:shouldNotFilter返回true时,该请求不会应用OSIV逻辑,即禁用OSIV;返回false则保持OSIV开启。

方法二:用AOP注解标记需要禁用OSIV的方法

如果需要更细粒度的控制(比如单个控制器方法),可以用AOP配合自定义注解实现:

  1. 定义一个标记用的注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DisableOsiv {
}
  1. 编写AOP切面,拦截标记了注解的方法,手动关闭OSIV上下文:
@Aspect
@Component
public class OsivDisablingAspect {
    private final EntityManagerFactory entityManagerFactory;

    public OsivDisablingAspect(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }

    // 切点:匹配标记@DisableOsiv的方法,或者指定包下的所有控制器方法
    @Pointcut("@annotation(com.example.annotation.DisableOsiv) || within(com.example.controller.v2..*)")
    public void osivDisabledMethods() {}

    @Around("osivDisabledMethods()")
    public Object handleOsivDisabled(ProceedingJoinPoint joinPoint) throws Throwable {
        // 解绑当前线程绑定的EntityManager(OSIV默认会绑定)
        EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource(entityManagerFactory);
        boolean wasOsivActive = false;

        if (emHolder != null && emHolder.getEntityManager().isOpen()) {
            wasOsivActive = true;
            TransactionSynchronizationManager.unbindResource(entityManagerFactory);
            emHolder.getEntityManager().close();
        }

        try {
            // 执行目标方法
            return joinPoint.proceed();
        } finally {
            // 如果之前OSIV是激活状态,请求结束后无需重新绑定,容器会自动清理
            if (wasOsivActive) {
                EntityManager newEm = entityManagerFactory.createEntityManager();
                TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(newEm));
            }
        }
    }
}
  1. 在需要禁用OSIV的控制器方法上标记注解:
@RestController
@RequestMapping("/api/v2/users")
public class UserV2Controller {
    @GetMapping("/{id}")
    @DisableOsiv
    public UserDTO getUser(@PathVariable Long id) {
        // 这里必须确保Service层已经加载完所有需要的关联数据,避免懒加载异常
        return userService.getUserWithAllAssociations(id);
    }
}

配套:修复禁用OSIV后的懒加载问题

不管用哪种方法禁用OSIV,都必须同步修改对应端点的业务代码,确保所有需要的关联数据在Service层提前加载完成:

  • 在Repository查询中使用JOIN FETCH强制加载关联实体:
@Query("SELECT u FROM User u JOIN FETCH u.roles WHERE u.id = :id")
User findByIdWithRoles(@Param("id") Long id);
  • 避免在Controller/视图层调用实体的懒加载方法;
  • 必要时使用DTO封装数据,在Service层完成实体到DTO的转换。

渐进式迁移步骤

  1. 先全局保持OSIV开启,避免现有代码崩溃;
  2. 选择上述一种方法,针对小范围端点(比如新开发的v2接口)禁用OSIV;
  3. 修复这些端点的懒加载问题,测试验证功能正常;
  4. 逐步扩大禁用范围,覆盖更多旧端点,每次验证后再推进;
  5. 所有端点都适配完成后,全局设置spring.jpa.open-in-view=false,彻底关闭OSIV。

内容的提问来源于stack exchange,提问作者pakman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 11:13:11