我应该如何将 JpaRepository.findOne() 与 SpringBoot 一起使用? [英] How should I use JpaRepository.findOne() with SpringBoot?

查看:19
本文介绍了我应该如何将 JpaRepository.findOne() 与 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.UserDetailsS​​ervice;/*** @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) 抛出异常 {授权.userDetailsS​​ervice((UserDetailsS​​ervice) 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.userDetailsS​​ervice() 中使用它作为 lambda 返回,这也是错误的因为 UserDetailsS​​ervice 是一个接口函数,定义为

UserDetails loadUserByUsername(String username) 抛出 UsernameNotFoundException;

所以你需要返回一个 UserDetails 实例而不是它的 Optional 或者如果与用户名 UsernameNotFoundException="https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/core/userdetails/UserDetailsS​​ervice.html#loadUserByUsername-java.lang.String-" rel="nofollow noreferrer">符合javadoc :

<块引用>

返回:

完全填充的用户记录(从不为空)

抛出:

UsernameNotFoundException - 如果找不到用户或用户没有 GrantedAuthority

此外,您不需要使用作为示例查询的 findOne().通过 ID 查询就足够了.

所以你可以这样写:

@Overrideprotected void configure(AuthenticationManagerBuilder auth) 抛出异常 {auth.userDetailsS​​ervice(username -> readerRepository.findById(username).orElseThrow(() -> new UsernameNotFoundException("user with username " + username + " not found"));}

<小时>

附带说明一下,getOne() 非常棘手,因为它依赖于延迟加载,这在某些情况下可能会带来糟糕的意外.
JB Nizet 的话很有趣.所以我现在就测试了.碰巧当 Spring Security 类访问实体(即 isAccountNonLocked())时,JPA 会话仍未打开.
所以在任何情况下都会抛出 LazyInitializationException(用户名正确与否):

<前>org.hibernate.LazyInitializationException:无法初始化代理 - 没有会话在 org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:155)在 org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:268)在 org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73)在 davidhxxx.example.angularsboot.model.db.User_$$_jvstd90_5.isAccountNonLocked(User_$$_jvstd90_5.java)在 org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider$DefaultPreAuthenticationChecks.check(AbstractUserDetailsAuthenticationProvider.java:352)在 org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:165)

这个问题您可能会感兴趣.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆