添加@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
相关产品推荐
相关产品推荐

