簡體   English   中英

我應該如何在SpringBoot中使用JpaRepository.findOne()?

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

我剛開始通過閱讀Spring Boot in Action這本書開始學習Spring Boot,我正在學習本書的例子 ,嘗試自己運行它們但是我在使用JpaRepository.findOne()了問題。

我已經全身心地去尋找可能的不匹配。 但是,它只是不起作用。

該項目應該是一個簡單的閱讀清單。

這是代碼:

讀者@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;
    }
}

Jpa界面:

package com.lixin.readinglist;

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

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

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

我一直得到這個錯誤:

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()定義為<S extends T> Optional<S> findOne(Example<S> example);
這意味着在您的情況下,它接受Example<Reader>並返回Optional<Reader>
你傳遞給它一個String ,這是錯誤的,你在AuthenticationManagerBuilder.userDetailsService()使用它作為lambda返回,這也是錯誤的,因為UserDetailsService是一個定義為

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

所以你需要返回一個UserDetails實例而不是它的可Optional ,或者如果與用戶名不匹配則拋出UsernameNotFoundException 以符合javadoc

返回:

完全填充的用戶記錄(永不為空)

拋出:

UsernameNotFoundException - 如果找不到用戶或用戶沒有GrantedAuthority

此外,您不需要使用通過示例查詢的findOne() 按ID查詢就足夠了。

所以你可以這樣寫:

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

作為旁注, getOne()非常棘手,因為它依賴於延遲加載,在某些情況下可能會帶來不良意外。
JB Nizet的評論很有意思。 所以我現在測試了。 碰巧當Spring Security類訪問實體(即isAccountNonLocked() )時,JPA會話仍未打開。
所以在任何情況下拋出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)

這個問題可能會讓你感興趣

閱讀@davidxxx的答案和@JB Nizet的評論后

我發現我犯了一個可怕的錯誤,使用getOne()取代findOne()的想法肯定會違反合同並不時產生一些不好的意外。

我意識到Nizet的評論非常出色。 我只是一個天真的學生,很榮幸能讓你糾正我的錯誤並引導我走向正確的道路。 我會編輯我的答案來糾正我所犯的天真錯誤。 謝謝(@JB Nizet && @davidxxx) &&欣賞!

解決方案:

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

你可以在這里找到原因#實際上它是來自@davidxxx的答案。

正如其他人所說,在Spring Data 2.x的最新版本中,您應該在最新版本的Spring Data中使用findById而不是findOne,findOne(如果您使用的話,它是Spring-Boot 2.x的一部分)想要一個示例對象。 我的猜測是你使用的那本書是在最近發布的Spring 5 / Spring Boot 2 / Spring Data 2.x之前編寫的。

希望閱讀遷移指南作為您的[稍微過時的]書的參考將有助於: https//github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide

您可以使用getOne()而不是findOne() 作者可能犯了一個錯誤。

您可以使用findById而不是findOne,findOne想要一個示例對象,您可以在這里查看更多內容

方法findOne來自名為QueryByExampleExecutor的接口,JpaRepository接口擴展了它。 而QueryByExampleExecutor用於QBE(一種使用Example的查詢)。 在您的代碼中,您不應該使用它,您可以使用getOne方法或findById方法,findById繼承自CrudRepository接口。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM