简体   繁体   English

我应该如何在SpringBoot中使用JpaRepository.findOne()?

[英]How should I use JpaRepository.findOne() with SpringBoot?

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() . 我刚开始通过阅读Spring Boot in Action这本书开始学习Spring Boot,我正在学习本书的例子 ,尝试自己运行它们但是我在使用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: 读者@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: Jpa界面:

package com.lixin.readinglist;

import org.springframework.data.jpa.repository.JpaRepository;

/**
 * @author lixin
 */
public interface ReaderRepository extends JpaRepository<Reader, String> {
}

The SecurityConfig: 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); findOne()定义为<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> . 这意味着在您的情况下,它接受Example<Reader>并返回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 你传递给它一个String ,这是错误的,你在AuthenticationManagerBuilder.userDetailsService()使用它作为lambda返回,这也是错误的,因为UserDetailsService是一个定义为

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 : 所以你需要返回一个UserDetails实例而不是它的可Optional ,或者如果与用户名不匹配则抛出UsernameNotFoundException 以符合javadoc

Returns: 返回:

a fully populated user record (never null) 完全填充的用户记录(永不为空)

Throws: 抛出:

UsernameNotFoundException - if the user could not be found or the user has no GrantedAuthority UsernameNotFoundException - 如果找不到用户或用户没有GrantedAuthority

Besides you don't need to use findOne() that is a query by example. 此外,您不需要使用通过示例查询的findOne() A query by ID is enough. 按ID查询就足够了。

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. 作为旁注, getOne()非常棘手,因为它依赖于延迟加载,在某些情况下可能会带来不良意外。
The remark of JB Nizet was interesting. JB Nizet的评论很有意思。 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. 碰巧当Spring Security类访问实体(即isAccountNonLocked() )时,JPA会话仍未打开。
So a LazyInitializationException is thrown in any case (username correct or no) : 所以在任何情况下抛出LazyInitializationException (用户名正确或没有):

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. 这个问题可能会让你感兴趣

After reading the answer from @davidxxx and the comment from @JB Nizet 阅读@davidxxx的答案和@JB Nizet的评论后

I found that I've made a terrible mistake, the idea of using getOne() to take the place of findOne() will definitely violate the contract and produce some bad surprises from time to time. 我发现我犯了一个可怕的错误,使用getOne()取代findOne()的想法肯定会违反合同并不时产生一些不好的意外。

And I realized that Nizet's comment was beyond excellent. 我意识到Nizet的评论非常出色。 I'm just a naive student, and it's an honor to have you correct my mistake and lead me to the correct way. 我只是一个天真的学生,很荣幸能让你纠正我的错误并引导我走向正确的道路。 I gonna edit my answer to correct the naive mistakes I've made. 我会编辑我的答案来纠正我所犯的天真错误。 Thank you (@JB Nizet && @davidxxx) && Appreciate that! 谢谢(@JB Nizet && @davidxxx) &&欣赏!

The Solution: 解决方案:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.
            userDetailsService(username -> readerRepository.findById(username)
                    .orElseThrow(() -> new UsernameNotFoundException("user with username " + username + " not found")));
}

And you can find the reason here # Actually it is the answer from @davidxxx. 你可以在这里找到原因#实际上它是来自@davidxxx的答案。

As others have said, in the latest versions of Spring Data 2.x, you should use findById, instead of findOne, findOne in the latest version of Spring Data (that is part of Spring-Boot 2.x if you are using that) wants an example object. 正如其他人所说,在Spring Data 2.x的最新版本中,您应该在最新版本的Spring Data中使用findById而不是findOne,findOne(如果您使用的话,它是Spring-Boot 2.x的一部分)想要一个示例对象。 My guess is that the book you were using was written before the recent release of Spring 5 / Spring Boot 2 / Spring Data 2.x. 我的猜测是你使用的那本书是在最近发布的Spring 5 / Spring Boot 2 / Spring Data 2.x之前编写的。

Hopefully reading the migration guide as a reference alongside your [slightly out-of-date] book will help: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide 希望阅读迁移指南作为您的[稍微过时的]书的参考将有助于: https//github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide

You can use getOne() , instead of findOne() . 您可以使用getOne()而不是findOne() The author might have made a mistake. 作者可能犯了一个错误。

您可以使用findById而不是findOne,findOne想要一个示例对象,您可以在这里查看更多内容

The method findOne is from an interface named QueryByExampleExecutor, the JpaRepository interface extended it. 方法findOne来自名为QueryByExampleExecutor的接口,JpaRepository接口扩展了它。 while QueryByExampleExecutor is used for QBE(a kind of query which uses Example). 而QueryByExampleExecutor用于QBE(一种使用Example的查询)。 In your code, you shouldn't use it, you can use the getOne method or findById method, the findById is inherited from the CrudRepository interface. 在您的代码中,您不应该使用它,您可以使用getOne方法或findById方法,findById继承自CrudRepository接口。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM