6
votes

Comment utiliser Jpa Repository.findOne () avec Spring Boot?

Je viens de commencer à apprendre Spring Boot en lisant le livre Spring Boot in Action et je apprends les exemples de ce livre. problème lors de l'utilisation de JpaRepository.findOne () .

J'ai parcouru tout le chapitre pour trouver mes éventuelles incohérences. Cependant, cela NE FONCTIONNE PAS.

Le projet est censé être une simple liste de lecture.

Voici le code:

Le Reader @Entity:

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

L'interface Jpa:

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

Le SecurityConfig:

package com.lixin.readinglist;

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

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

Et j'ai continué à recevoir cette ERREUR:

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


4 commentaires

Vous lisez probablement un vieux livre. findOne () a été renommé findById () dans les versions récentes de Spring Data. Et il renvoie un facultatif, pas un lecteur ou null. Vous devez donc adapter le code.


Notez également que l'utilisation d'une valeur String comme clé primaire est généralement un choix inefficace, car les recherches ont tendance à être lentes. Long ou UUID est généralement préférable.


Enfin, revoir la spécification [Semantic Versioning] (semver.org) est utile. Votre guide est écrit pour Spring Data 1, pendant que vous utilisez Spring Data 2, et entre les versions principales, les composants de l'API peuvent être supprimés - dans ce cas, la méthode findOne a été supprimée de CrudRepository < / code> et remplacé par le findById similaire mais pas identique.


@JBNizet Merci pour votre remarque. J'ai testé la méthode getOne () et elle a généré l'erreur exacte comme vous l'avez remarqué. J'ai mis à jour ma réponse. En tant qu'étudiant de premier cycle, c'est un honneur de vous avoir corrigé mon erreur et de me conduire dans la bonne direction. Appréciez votre temps.


6 Réponses :


1
votes

Vous pouvez utiliser findById, au lieu de findOne, findOne veut un exemple d'objet, vous pouvez regarder ici pour en savoir plus


0 commentaires

6
votes

findOne () est défini comme facultatif findOne (exemple exemple); .
Cela signifie que dans votre cas, il accepte un Example et renvoie un Optional .
Vous lui avez passé une String , ce qui est faux et vous l'utilisez comme retour lambda dans AuthenticationManagerBuilder.userDetailsService () , ce qui est également faux car UserDetailsService est une interface fonctionnelle définie comme

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)

Vous devez donc renvoyer une instance UserDetails et non un code facultatif > de celui-ci ou de lancer UsernameNotFoundException si aucune correspondance avec le nom d'utilisateur pour être compatible avec le javadoc :

Retours:

un enregistrement utilisateur entièrement rempli (jamais nul)

Lance:

UsernameNotFoundException - si l'utilisateur n'a pas pu être trouvé ou l'utilisateur n'a pas d'autorité accordée

De plus, vous n'avez pas besoin d'utiliser findOne () qui est une requête par exemple. Une requête par ID suffit.

Vous pouvez donc écrire quelque chose comme ça:

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

En remarque, getOne () est délicat assez car il repose sur un chargement paresseux qui peut donner de mauvaises surprises dans certains cas.
La remarque de JB Nizet était intéressante. Alors j'ai testé en ce moment. Il arrive que la session JPA ne soit pas encore ouverte lorsque l'entité (à savoir isAccountNonLocked () ) est accédée par les classes Spring Security.
Ainsi une LazyInitializationException est lancée dans tous les cas (nom d'utilisateur correct ou non):

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

Cette question peut vous intéresser.


3 commentaires

Ce serait certainement un problème ici: getOne () retournera un proxy non initialisé sur un UserDetails, que l'utilisateur existe ou non. Cela violerait donc le contrat, car une exception UsernameNotFoundException est censée être lancée dans ce cas.


@JB Nizet En fait en tout cas ça échoue. Je viens de tester. J'ai mis à jour en conséquence. Merci pour votre remarque;)


Tout comme @JBNizet l'a mentionné. J'ai trouvé que l'utilisation de getOne () au lieu de findOne () violerait définitivement le contrat et produirait de temps en temps de mauvaises surprises. Et j'ai réalisé que le commentaire de Nizet était au-delà de l'excellence. Je ne suis qu'un étudiant naïf, et c'est un honneur que vous corrigiez mon erreur et que vous me conduisiez dans la bonne direction. Je vais modifier ma réponse pour corriger les erreurs naïves que j'ai faites. Merci && Merci!



0
votes

La méthode findOne provient d'une interface nommée QueryByExampleExecutor, l'interface JpaRepository l'a étendue. tandis que QueryByExampleExecutor est utilisé pour QBE (une sorte de requête qui utilise Example). Dans votre code, vous ne devriez pas l'utiliser, vous pouvez utiliser la méthode getOne ou la méthode findById, le findById est hérité de l'interface CrudRepository.


0 commentaires

0
votes

Après avoir lu la réponse de @davidxxx et le commentaire de @JB Nizet

j'ai constaté que je fait une terrible erreur, l'idée d'utiliser getOne () pour remplacer findOne () enfreindra définitivement le contrat et produira de temps en temps de mauvaises surprises.

Et j'ai réalisé que le commentaire de Nizet était au-delà de l'excellence. Je ne suis qu'un étudiant naïf, et c'est un honneur que vous corrigiez mon erreur et que vous me conduisiez dans la bonne direction. Je vais modifier ma réponse pour corriger les erreurs naïves que j'ai faites. Merci (@JB Nizet && @davidxxx) && Merci!

La solution:

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

Et vous pouvez trouver la raison ici # En fait, c'est la réponse de @davidxxx.

p>


3 commentaires

Je ne conseille généralement pas getOne () . Il repose sur des références paresseuses et vous pouvez avoir de mauvaises surprises (pas un problème ici mais bon). Vous pouvez lire ma réponse stackoverflow.com/a/47370947/270371 et le message plus généralement. De plus, vous devez respecter le contrat de loadUserByUsername () : renvoyer un UserDetail ou UsernameNotFoundException entièrement initialisé. JpaRepository.getOne () ne le garantit pas.


@davidxxx Merci pour votre remarque. J'ai testé la méthode getOne () et elle a jeté l'erreur exacte comme vous l'avez remarqué. J'ai mis à jour ma réponse. En tant qu'étudiant de premier cycle, c'est un honneur de vous avoir corrigé mon erreur et de me conduire dans la bonne direction. Appréciez votre temps.


C'est un très bon montage. Laisser un bon contenu dans nos réponses pour les prochains lecteurs de l'article est une très bonne chose (+1).



2
votes

Comme d'autres l'ont dit, dans les dernières versions de Spring Data 2.x, vous devriez utiliser findById, au lieu de findOne, findOne dans la dernière version de Spring Data (qui fait partie de Spring-Boot 2.x si vous êtes en utilisant ça) veut un exemple d'objet. Je suppose que le livre que vous utilisiez a été écrit avant la sortie récente de Spring 5 / Spring Boot 2 / Spring Data 2.x.

Nous espérons que la lecture du guide de migration comme référence à côté de votre livre [légèrement obsolète] vous aidera: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide


0 commentaires

0
votes

Vous pouvez utiliser getOne () , au lieu de findOne () . L'auteur a peut-être fait une erreur.


1 commentaires

En fait, je ne pense pas que vous devriez utiliser getOne ici, peut-être que nous avons fait la même erreur. Veuillez lire les commentaires de M. Nizet sous la question.