Spring Data无法延迟初始化角色的集合,无法初始化代理 – 没有会话。

huangapple go评论63阅读模式
英文:

Spring Data failed to lazily initialize a collection of role, could not initialize proxy - no Session

问题

这是您提供的代码部分的中文翻译:

我正在使用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会为每个懒加载关系创建代理,并且只有在访问时才会获取数据。但要访问它,它需要一个会话(session),显然,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)创建了会话。

我可以手动创建一个EntityManager并使用它来获取/持久化我的实体,但这不是目标,因为我正在使用Spring Data JPA。

英文:

I am making an API with Spring Boot and I never seem to manage to initialize lazy collections. The only solution that has ever worked for me is changing it to eager, but that is no solution.

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = &quot;user&quot;)
public class UserEntity {
@Id
@GeneratedValue
private Long id;
@Column(unique = true)
String email;
@ElementCollection(fetch = LAZY)
List&lt;String&gt; roles = new ArrayList&lt;&gt;();
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 &amp;&amp; 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);
}
}

These are my two classes. A user entity and a filter that will create user from an authentication object. When the line user.getRoles().clear(); is called, I get the famous error org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ch.ciip.ressources.api.user.UserEntity.roles, could not initialize proxy - no Session.

From my understanding, Spring Data will create a proxy for each lazy relation, and will only fetch the data once it is accessed. But to access it it needs a session and apparently, Spring is not able to create a session when accessing a proxy, however, it has no problem doing it when I call save on a repository.

What I've tried is :

  • @Transactional from both org.springframework.transaction.annotation.Transactional and javax.transaction.Transactional. But it changes absolutely nothing.
  • Change fetch type to EAGER. This method works, but it is not what I want to do.
  • Change enable_lazy_load_no_trans=trueto true in application.properties file. This method is not recommended, so I will not try it.
  • Calling a method on the lazy collection before trying to modify it. For example calling user.getRoles().size(); so it gets initialized. But it does not work.
  • Calling Hibernate.initialize(user.getRoles()); to initialize the collection. But it does not work.

I wonder why Spring says No Session because it does create a session for the userRepository.findByEmail(email) as well as for userRepository.save(user).

I could create an entitymanager manually and use it to fetch/persist my entities. But that is not the goal as I'm using Spring Data JPA.

答案1

得分: 5

将方法设为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);
}
英文:

Make the method public, i.e.:

@Override
@Transactional
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if(authentication != null &amp;&amp; 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);
}

答案2

得分: 2

Creating a separate service class and injecting it into this filter is a workaround I can propose.

@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);
    }
}

Then use this in your filter

@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);
}
英文:

Creating a separate service class and injecting it into this filter is a workaround i can propose.

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

Then use this in your filter

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

huangapple
  • 本文由 发表于 2020年8月5日 17:04:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/63261801.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定