我应该如何将 JpaRepository.findOne() 与 SpringBoot 一起使用? [英] How should I use JpaRepository.findOne() with SpringBoot?
问题描述
我刚刚通过阅读Spring Boot in Action这本书开始学习 Spring Boot,我正在学习本书的示例,尝试自己运行它们,但我有使用 JpaRepository.findOne()
的问题.
我已经翻遍了整个章节来寻找可能的不匹配.但是,它只是不起作用.
该项目应该是一个简单的阅读清单.
代码如下:
读者@Entity:
package com.lixin.readinglist;导入 org.springframework.data.annotation.Id;导入 org.springframework.security.core.GrantedAuthority;导入 org.springframework.security.core.authority.SimpleGrantedAuthority;导入 org.springframework.security.core.userdetails.UserDetails;导入 javax.persistence.Entity;导入 java.util.Collection;导入 java.util.Collections;/*** @author 立新*/@实体公共类 Reader 实现 UserDetails {private static final long serialVersionUID = 1L;@Id私人字符串用户名;私人字符串全名;私人字符串密码;@覆盖公共字符串 getUsername() {返回用户名;}公共无效设置用户名(字符串用户名){this.username = 用户名;}公共字符串 getFullname() {返回全名;}public void setFullname(String fullname) {this.fullname = 全名;}@覆盖公共字符串 getPassword() {返回密码;}public void setPassword(字符串密码){this.password = 密码;}@覆盖公共收藏getAuthorities() {返回 Collections.singletonList(new SimpleGrantedAuthority("READER"));}@覆盖公共布尔 isAccountNonExpired() {返回真;}@覆盖公共布尔 isAccountNonLocked() {返回真;}@覆盖公共布尔 isCredentialsNonExpired() {返回真;}@覆盖公共布尔 isEnabled() {返回真;}}
Jpa 接口:
package com.lixin.readinglist;导入 org.springframework.data.jpa.repository.JpaRepository;/*** @author 立新*/公共接口 ReaderRepository 扩展 JpaRepository{}
安全配置:
package com.lixin.readinglist;导入 org.springframework.beans.factory.annotation.Autowired;导入 org.springframework.context.annotation.Configuration;导入 org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;导入 org.springframework.security.config.annotation.web.builders.HttpSecurity;导入 org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;导入 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;导入 org.springframework.security.core.userdetails.UserDetailsService;/*** @author 立新*/@配置@启用网络安全公共类 SecurityConfig 扩展了 WebSecurityConfigurerAdapter {私人最终 ReaderRepository readerRepository;@自动连线公共安全配置(ReaderRepository readerRepository){this.readerRepository = readerRepository;}@覆盖protected void configure(HttpSecurity http) 抛出异常 {http.authorizeRequests().antMatchers("/").access("hasRole('READER')").antMatchers("/**").permitAll().and().formLogin().loginPage("/登录").failureUrl("/login?error=true");}@覆盖protected void configure(AuthenticationManagerBuilder auth) 抛出异常 {授权.userDetailsService((UserDetailsService) username -> readerRepository.findOne(username));}}
我一直收到这个错误:
Error:(40, 86) java: method findOne in interface org.springframework.data.repository.query.QueryByExampleExecutor<T>不能应用于给定类型;需要:org.springframework.data.domain.Example找到:java.lang.String原因:无法推断类型变量 S(参数不匹配;java.lang.String 无法转换为 org.springframework.data.domain.Example)
findOne()
定义为
.可选的 SfindOne(Exampleexample);
这意味着在您的情况下,它接受 Example
并返回 Optional
.
你传递给它一个 String
,这是错误的,你在 AuthenticationManagerBuilder.userDetailsService()
中使用它作为 lambda 返回,这也是错误的因为 UserDetailsService
是一个接口函数,定义为
UserDetails loadUserByUsername(String username) 抛出 UsernameNotFoundException;
所以你需要返回一个 UserDetails
实例而不是它的 Optional
或者如果与用户名 UsernameNotFoundException="https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/core/userdetails/UserDetailsService.html#loadUserByUsername-java.lang.String-" rel="nofollow noreferrer">符合javadoc :
返回:
完全填充的用户记录(从不为空)
抛出:
UsernameNotFoundException - 如果找不到用户或用户没有 GrantedAuthority
此外,您不需要使用作为示例查询的 findOne()
.通过 ID 查询就足够了.
所以你可以这样写:
@Overrideprotected void configure(AuthenticationManagerBuilder auth) 抛出异常 {auth.userDetailsService(username -> readerRepository.findById(username).orElseThrow(() -> new UsernameNotFoundException("user with username " + username + " not found"));}
<小时>
附带说明一下,getOne()
非常棘手,因为它依赖于延迟加载,这在某些情况下可能会带来糟糕的意外.
JB Nizet 的话很有趣.所以我现在就测试了.碰巧当 Spring Security 类访问实体(即 isAccountNonLocked()
)时,JPA 会话仍未打开.
所以在任何情况下都会抛出 LazyInitializationException
(用户名正确与否):
这个问题您可能会感兴趣.
I just started learning Spring Boot by reading the book Spring Boot in Action and I am learning the examples of this book, trying to run them myself but I have a problem using JpaRepository.findOne()
.
I've gone allover the Chapter to find my possible mismatches. However, it just DO NOT work.
The project is supposed to be a simple Reading List.
Here is the code :
The Reader @Entity:
package com.lixin.readinglist;
import org.springframework.data.annotation.Id;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.Entity;
import java.util.Collection;
import java.util.Collections;
/**
* @author lixin
*/
@Entity
public class Reader implements UserDetails {
private static final long serialVersionUID = 1L;
@Id
private String username;
private String fullname;
private String password;
@Override
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFullname() {
return fullname;
}
public void setFullname(String fullname) {
this.fullname = fullname;
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("READER"));
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
The Jpa interface:
package com.lixin.readinglist;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author lixin
*/
public interface ReaderRepository extends JpaRepository<Reader, String> {
}
The SecurityConfig:
package com.lixin.readinglist;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* @author lixin
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final ReaderRepository readerRepository;
@Autowired
public SecurityConfig(ReaderRepository readerRepository) {
this.readerRepository = readerRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").access("hasRole('READER')")
.antMatchers("/**").permitAll()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error=true");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService((UserDetailsService) username -> readerRepository.findOne(username));
}
}
And I kept getting this ERROR:
Error:(40, 86) java: method findOne in interface org.springframework.data.repository.query.QueryByExampleExecutor<T> cannot be applied to given types;
required: org.springframework.data.domain.Example<S>
found: java.lang.String
reason: cannot infer type-variable(s) S
(argument mismatch; java.lang.String cannot be converted to org.springframework.data.domain.Example<S>)
findOne()
is defined as <S extends T> Optional<S> findOne(Example<S> example);
.
It means that in your case it accepts a Example<Reader>
and returns an Optional<Reader>
.
You passed to it a String
, which is wrong and you use it as lambda return in AuthenticationManagerBuilder.userDetailsService()
, which is also wrong
because UserDetailsService
is an interface functional defined as
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
So you need to return an UserDetails
instance not an Optional
of it or to throw UsernameNotFoundException
if no matching with the username to be compliant with the javadoc :
Returns:
a fully populated user record (never null)
Throws:
UsernameNotFoundException - if the user could not be found or the user has no GrantedAuthority
Besides you don't need to use findOne()
that is a query by example. A query by ID is enough.
So you could write something like that :
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(username -> readerRepository.findById(username)
.orElseThrow( () -> new UsernameNotFoundException("user with username " + username + " not found"));
}
As a side note, getOne()
is tricky enough as it relies on lazy loading that may give bad surprises in some cases.
The remark of JB Nizet was interesting.
So I tested right now. It happens that the JPA session is not still opened when the entity (namely isAccountNonLocked()
) is accessed by the Spring Security classes.
So a LazyInitializationException
is thrown in any case (username correct or no) :
org.hibernate.LazyInitializationException: could not initialize proxy - no Session at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:155) at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:268) at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73) at davidhxxx.example.angularsboot.model.db.User_$$_jvstd90_5.isAccountNonLocked(User_$$_jvstd90_5.java) at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider$DefaultPreAuthenticationChecks.check(AbstractUserDetailsAuthenticationProvider.java:352) at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:165)
This question may interest you.
这篇关于我应该如何将 JpaRepository.findOne() 与 SpringBoot 一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!