英文:
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.Transactional
和javax.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 = "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);
}
}
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 bothorg.springframework.transaction.annotation.Transactional
andjavax.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=true
to 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 && 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<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);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论