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

添加@Transactional无法解决SecurityContextHolder认证获取的懒加载异常

解决Spring Boot中SecurityContextHolder获取Authentication时的LazyInitializationException问题

问题根源

你遇到的LazyInitializationException本质是Hibernate懒加载的实体在事务/会话(Session)关闭后被访问。具体来说:

  • OncePerRequestFilter属于Servlet层面的组件,默认不在Spring事务上下文范围内,所以你加的@Transactional注解对Filter完全无效。
  • 你存入SecurityContext的UserDetails大概率是Hibernate代理对象(实体类),其中包含懒加载的关联属性(比如用户和文章的关联)。当Controller中访问这些属性时,Filter阶段的Hibernate Session已经关闭,无法触发懒加载。

可行解决方案

1. 提取用户核心信息到DTO,避免直接操作实体类

这是最推荐的方案,从根源上避免懒加载问题:

  • 创建一个纯数据传输对象(DTO),只包含你需要的用户字段:
public class UserDTO implements Serializable {
    private Long id;
    private String username;
    private Collection<? extends GrantedAuthority> authorities;

    // 构造方法、getter/setter
    public UserDTO(Long id, String username, Collection<? extends GrantedAuthority> authorities) {
        this.id = id;
        this.username = username;
        this.authorities = authorities;
    }

    // getter省略
}
  • 修改JwtAuthenticationFilter,将加载后的实体类转换为DTO存入SecurityContext:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    // 省略JWT解析、用户名获取逻辑
    User user = (User) userDetailsService.loadUserByUsername(username);
    // 转换为DTO,避免携带Hibernate代理
    UserDTO userDTO = new UserDTO(user.getId(), user.getUsername(), user.getAuthorities());
    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDTO, null, userDTO.getAuthorities());
    SecurityContextHolder.getContext().setAuthentication(authToken);
    
    filterChain.doFilter(request, response);
}
  • Controller中直接从DTO拿用户ID查询关联文章:
@RestController
@RequestMapping("/articles")
public class ArticleController {
    @Autowired
    private ArticleRepository articleRepository;

    @GetMapping
    public List<Article> getCurrentUserArticles() {
        UserDTO userDTO = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return articleRepository.findByUserId(userDTO.getId());
    }
}

2. 在UserDetailsService中主动加载关联数据(不推荐,性能风险)

如果必须使用实体类,可以在加载用户时主动触发懒加载,确保关联数据在会话关闭前被初始化:

@Service
public class CustomUserDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    @Override
    @Transactional // 这里的事务生效,因为是Spring管理的Bean方法
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
        // 主动触发懒加载(如果需要用户的文章列表)
        user.getArticles().size(); // 调用size()强制加载关联数据
        return user;
    }
}

注意:这种方法会每次加载用户时都查询关联的文章,可能导致不必要的性能开销,仅适合关联数据量极小的场景。

3. 启用OpenEntityManagerInViewFilter(不推荐,资源泄漏风险)

这个过滤器会延长Hibernate Session的生命周期到请求结束,但会增加资源占用,容易引发性能问题:
在application.yml中添加配置:

spring:
  jpa:
    open-in-view: true

不建议在生产环境使用此方案,因为它会让Session一直保持到响应返回,可能导致连接池耗尽。

关键提醒

  • 永远不要把Hibernate代理实体直接存入SecurityContext,这类对象依赖于当前会话,一旦会话关闭就会失效。
  • Servlet过滤器(如OncePerRequestFilter)不受Spring事务管理,加@Transactional完全无效,事务只对Spring容器管理的Bean方法生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 00:57:21