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; } }
6 Réponses :
Vous pouvez utiliser findById, au lieu de findOne, findOne veut un exemple d'objet, vous pouvez regarder ici pour en savoir plus
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.
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!
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.
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>
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).
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
Vous pouvez utiliser getOne ()
, au lieu de findOne ()
. L'auteur a peut-être fait une erreur.
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.
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
ouUUID
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 deCrudRepository < / 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.