【问题标题】:Spring Data failed to lazily initialize a collection of role, could not initialize proxy - no SessionSpring Data 无法懒惰地初始化角色集合,无法初始化代理 - 没有 Session
【发布时间】:2020-11-25 10:19:53
【问题描述】:

我正在使用 Spring Boot 创建一个 API,但我似乎从未设法初始化惰性集合。唯一对我有用的解决方案是将其更改为渴望,但这不是解决方案。

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "user")
public class UserEntity {

    @Id
    @GeneratedValue
    private Long id;

    @Column(unique = true)
    String email;

    @ElementCollection(fetch = LAZY)
    List<String> roles = new ArrayList<>();

    public UserEntity(String email) {
        this.email = email;
    }
}
@Order(Ordered.LOWEST_PRECEDENCE)
@Component
public class AuthFilter extends OncePerRequestFilter {

    Logger LOG = LoggerFactory.getLogger(AuthFilter.class);

    @Autowired
    UserRepository userRepository;

    @Override
    @Transactional
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if(authentication != null && authentication.isAuthenticated()){
            String email = authentication.getName();
            UserEntity user = userRepository.findByEmail(email).orElse(new UserEntity(email));
            user.getRoles().clear();
            user.getRoles().addAll(authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()));
            userRepository.save(user);
        }
        filterChain.doFilter(request, response);
    }
}

这是我的两门课。一个用户实体和一个过滤器,将从身份验证对象创建用户。当调用user.getRoles().clear(); 行时,我得到了著名的错误org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ch.ciip.ressources.api.user.UserEntity.roles, could not initialize proxy - no Session

据我了解,Spring Data 会为每个惰性关系创建一个代理,并且只会在数据被访问时获取数据。但是要访问它,它需要一个会话,显然,Spring 在访问代理时无法创建会话,但是,当我在存储库上调用 save 时,它​​没有问题。

我试过的是:

  • @Transactional 来自 org.springframework.transaction.annotation.Transactionaljavax.transaction.Transactional。但它完全没有改变。
  • 将获取类型更改为EAGER。这种方法有效,但不是我想做的。
  • 在 application.properties 文件中将 enable_lazy_load_no_trans=true 更改为 true。不推荐这种方法,所以我不会尝试。
  • 在尝试修改惰性集合之前调用该方法。例如调用user.getRoles().size(); 使其被初始化。但它不起作用。
  • 调用Hibernate.initialize(user.getRoles()); 来初始化集合。但它不起作用。

我想知道 Spring 为什么说 No Session,因为它确实为 userRepository.findByEmail(email)userRepository.save(user) 创建了一个会话。

我可以手动创建一个实体管理器并使用它来获取/保留我的实体。但这不是我使用 Spring Data JPA 的目标。

【问题讨论】:

    标签: java spring spring-boot hibernate jpa


    【解决方案1】:

    制作方法public,即:

    @Override
    @Transactional
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
    
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    
        if(authentication != null && authentication.isAuthenticated()){
            String email = authentication.getName();
            UserEntity user = userRepository.findByEmail(email).orElse(new UserEntity(email));
            user.getRoles().clear();
            user.getRoles().addAll(authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()));
            userRepository.save(user);
        }
        filterChain.doFilter(request, response);
    }
    

    【讨论】:

    • 如果我将其公开Exception starting filter [authFilter] java.lang.NullPointerException: null ... One or more Filters failed to start.,则会收到错误消息。公开的原因是什么?
    • @Servietsky 原因现在有所不同。该方法应该是公共的,@Transactional 才能工作(bean 的代理必须由 spring 创建)
    • @Servietsky 正如@Andronicus 正确指出的那样,将其设为public 将使其能够被代理,这是@Transactional 工作所必需的。但它有一个例外。如果您使用 AspectJ 进行代理而不是默认的 Spring 代理 AOP,那么修饰符可以保持为 protected
    • @Servietsky 我建议从过滤器中删除 @Transactional 并将其移至单独的 @service 类。将服务类注入过滤器并使用它。
    • @Servietsky 没问题,考虑采纳答案;>
    【解决方案2】:

    创建一个单独的服务类并将其注入此过滤器是我可以提出的一种解决方法。

    @Service
    class UserService {
        @Autowired
        private UserRepository userRepository;
    
        @Transactional
        public void findUserByEmailAndAddRoles(String email, Set<String> authorities) {
            UserEntity user = userRepository.findByEmail(email)
                       .orElse(new UserEntity(email));
            user.getRoles().clear();
            user.getRoles().addAll(authorities);
            userRepository.save(user);
        }
    }
    

    然后在你的过滤器中使用它

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
    
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    
        if(authentication != null && authentication.isAuthenticated()){
            String email = authentication.getName();
            Set<String> authorities = authentication.getAuthorities().stream()
               .map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
            userService.findUserByEmailAndAddRoles(email, authorities);
        }
        filterChain.doFilter(request, response);
    }
    

    【讨论】:

      猜你喜欢
      • 2021-11-27
      • 2014-05-03
      • 2021-06-15
      • 2016-11-10
      • 1970-01-01
      • 2021-08-20
      • 2018-12-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多