Как мне использовать JpaRepository.findOne () с SpringBoot?

Я только начал изучать Spring Boot, прочитав книгу 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> {
}

Конфигурация безопасности:

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 () был переименован в findById () в последних версиях Spring Data. И он возвращает Optional <Reader>, а не Reader или null. Значит вам нужно адаптировать код.

JB Nizet 05.01.2019 09:57

Также обратите внимание, что использование значения String в качестве первичного ключа обычно неэффективно, поскольку поиск обычно выполняется медленно. Long или UUID обычно лучше.

chrylis -cautiouslyoptimistic- 05.01.2019 10:31

Наконец, полезно ознакомиться со спецификацией [Semantic Versioning] (semver.org). Ваше руководство написано для Spring Data 1, в то время как вы используете Spring Data 2, и между основными версиями компоненты API могут быть удалены - в этом случае метод findOne был удален из CrudRepository и заменен аналогичным, но не идентичным findById.

chrylis -cautiouslyoptimistic- 05.01.2019 10:34

@JBNizet Спасибо за ваше замечание. Я протестировал метод getOne (), и, как вы заметили, он выдал точную ошибку. Я обновил свой ответ. Для меня, как студента бакалавриата, большая честь исправить мою ошибку и направить меня на правильный путь. Цените свое время.

Justin Lee 06.01.2019 17:42
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
8
4
9 319
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

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

Ответ принят как подходящий

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 не соответствует:

Returns:

a fully populated user record (never null)

Throws:

UsernameNotFoundException - if the user could not be found or the user has no 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() достаточно сложен, поскольку основан на отложенной загрузке, которая в некоторых случаях может преподнести неприятные сюрпризы. Замечание Ж. Б. Низета было интересным. Так что протестировал прямо сейчас. Бывает, что сессия JPA все еще не открывается, когда к объекту (а именно isAccountNonLocked()) обращаются классы безопасности Spring. Таким образом, 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)

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

Это определенно будет проблемой: getOne () вернет неинициализированный прокси через UserDetails, независимо от того, существует ли пользователь на самом деле или нет. Так что это нарушит контракт, поскольку в этом случае должно быть выброшено исключение UsernameNotFoundException.

JB Nizet 05.01.2019 12:10

@JB Nizet На самом деле в любом случае не получается. Я только что протестировал. Я обновился соответственно. Спасибо за замечание;)

davidxxx 05.01.2019 12:45

Как упоминал @JBNizet. Я обнаружил, что использование getOne() вместо findOne() определенно нарушит договор и время от времени преподнесет неприятные сюрпризы. И я понял, что комментарий Низета был превосходным. Я просто наивный ученик, и для меня большая честь, что вы исправили мою ошибку и направили меня на правильный путь. Я отредактирую свой ответ, чтобы исправить наивные ошибки, которые я сделал. Спасибо && Оцените это!

Justin Lee 06.01.2019 17:08

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

После прочтения отвечать от @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.

getOne() вообще не советую. Он полагается на ленивые ссылки, и у вас могут быть неприятные сюрпризы (здесь не проблема, но хорошо). Вы можете прочитать мой ответ stackoverflow.com/a/47370947/270371 и пост в целом. Кроме того, вы должны соблюдать договор loadUserByUsername(): вернуть полностью инициализированный UserDetail или UsernameNotFoundException. JpaRepository.getOne() не гарантирует этого.

davidxxx 05.01.2019 10:40

@davidxxx Спасибо за ваше замечание. Я тестировал метод getOne(), и, как вы заметили, он выдал точную ошибку. Я обновил свой ответ. Для меня, как студента бакалавриата, большая честь исправить мою ошибку и направить меня на правильный путь. Цените свое время.

Justin Lee 06.01.2019 17:34

Это очень хорошая редакция. Оставлять хороший контент в наших ответах для следующих читателей сообщения - это очень хорошо (+1).

davidxxx 06.01.2019 19:38

Как уже говорили другие, в последних версиях 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

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

На самом деле, я не думаю, что вам следует использовать здесь getOne, возможно, мы совершили ту же ошибку. Пожалуйста, прочтите комментарии, сделанные г-ном Низе под вопросом.

Justin Lee 06.01.2019 17:27

Другие вопросы по теме