Как я должен использовать JpaRepository.findOne () с SpringBoot? - PullRequest
0 голосов
/ 05 января 2019

Я только начал изучать Spring Boot, прочитав книгу Spring Boot in Action , и я изучаю примеры этой книги, пытаясь запустить их самостоятельно, но у меня возникла проблема с использованием 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>)

Ответы [ 6 ]

0 голосов
/ 06 января 2019

Вы можете использовать getOne() вместо findOne(). Автор мог ошибиться.

0 голосов
/ 05 января 2019

Как уже говорили другие, в последних версиях Spring Data 2.x вы должны использовать findById, а не findOne, findOne в последней версии Spring Data (которая является частью 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

0 голосов
/ 05 января 2019

После прочтения ответа от @davidxxx и комментария от @JB Nizet

Я обнаружил, что совершил ужасную ошибку, идея использования getOne() вместо findOne() определенно нарушит контракт и будет время от времени преподносить неприятные сюрпризы.

И я понял, что комментарий Низет был за гранью превосходного. Я просто наивный ученик, и для меня большая честь, что вы исправили мою ошибку и привели меня на правильный путь. Я собираюсь отредактировать свой ответ, чтобы исправить наивные ошибки, которые я сделал. Спасибо (@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.

0 голосов
/ 05 января 2019

Метод findOne из интерфейса с именем QueryByExampleExecutor, интерфейс JpaRepository расширил его. в то время как QueryByExampleExecutor используется для QBE (разновидность запроса, который использует Пример). В вашем коде вы не должны его использовать, вы можете использовать метод getOne или метод findById, findById наследуется от интерфейса CrudRepository.

0 голосов
/ 05 января 2019

findOne() определяется как <S extends T> Optional<S> findOne(Example<S> example);.
Это означает, что в вашем случае он принимает Example<Reader> и возвращает Optional<Reader>.
Вы передали ему String, что неверно, и вы используете его как лямбда-возврат в AuthenticationManagerBuilder.userDetailsService(), что также неверно потому что UserDetailsService - это интерфейсный функционал, определенный как

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

Таким образом, вам нужно вернуть UserDetails экземпляр, а не Optional его или выбросить UsernameNotFoundException, если нет совпадения с именем пользователя , чтобы соответствовать javadoc :

Возвращает:

полностью заполненная запись пользователя (никогда не ноль)

Выдает:

UsernameNotFoundException - если пользователь не может быть найден или пользователь не имеет предоставленных полномочий

Кроме того, вам не нужно использовать findOne(), это пример запроса. Запрос по идентификатору достаточно.

Так что вы могли бы написать что-то вроде этого:

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

В качестве примечания, getOne() достаточно сложен, поскольку полагается на ленивую загрузку, которая в некоторых случаях может преподнести неприятные сюрпризы.
Замечание Дж. Б. Низета было интересным. Итак, я проверил прямо сейчас. Случается, что сессия JPA все еще не открыта, когда к объекту (а именно isAccountNonLocked()) обращаются классы Spring Security.
Поэтому в любом случае выдается 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)

Этот вопрос может вас заинтересовать.

0 голосов
/ 05 января 2019

Вы можете использовать findById, вместо findOne, findOne хочет пример объекта, вы можете посмотреть здесь , чтобы узнать больше

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...