I just started learning Spring Boot by reading the book Spring Boot in Action and I am learning the examples of this book, trying to run them myself but I have a problem using JpaRepository.findOne()
.
I've gone allover the Chapter to find my possible mismatches. However, it just DO NOT work.
The project is supposed to be a simple Reading List.
Here is the code :
The Reader @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;
}
}
The Jpa interface:
package com.lixin.readinglist;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author lixin
*/
public interface ReaderRepository extends JpaRepository<Reader, String> {
}
The 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));
}
}
And I kept getting this ERROR:
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>)
To activate the Spring JPA repository support, we can use the @EnableJpaRepositories annotation and specify the package that contains the DAO interfaces: @EnableJpaRepositories(basePackages = "com.
JPA Repository is mainly used for managing the data in a Spring Boot Application. We all know that Spring is considered to be a very famous framework of Java. We mainly use this Spring Boot to create the Spring-based stand-alone and production-based applications with a very minimal amount of effort.
Crud Repository doesn't provide methods for implementing pagination and sorting. JpaRepository ties your repositories to the JPA persistence technology so it should be avoided. We should use CrudRepository or PagingAndSortingRepository depending on whether you need sorting and paging or not.
findOne()
is defined as <S extends T> Optional<S> findOne(Example<S> example);
.
It means that in your case it accepts a Example<Reader>
and returns an Optional<Reader>
.
You passed to it a String
, which is wrong and you use it as lambda return in AuthenticationManagerBuilder.userDetailsService()
, which is also wrong
because UserDetailsService
is an interface functional defined as
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
So you need to return an UserDetails
instance not an Optional
of it or to throw UsernameNotFoundException
if no matching with the username to be compliant with the javadoc :
Returns:
a fully populated user record (never null)
Throws:
UsernameNotFoundException - if the user could not be found or the user has no GrantedAuthority
Besides you don't need to use findOne()
that is a query by example. A query by ID is enough.
So you could write something like that :
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(username -> readerRepository.findById(username)
.orElseThrow( () -> new UsernameNotFoundException("user with username " + username + " not found"));
}
As a side note, getOne()
is tricky enough as it relies on lazy loading that may give bad surprises in some cases.
The remark of JB Nizet was interesting.
So I tested right now. It happens that the JPA session is not still opened when the entity (namely isAccountNonLocked()
) is accessed by the Spring Security classes.
So a LazyInitializationException
is thrown in any case (username correct or no) :
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)
This question may interest you.
As others have said, in the latest versions of Spring Data 2.x, you should use findById, instead of findOne, findOne in the latest version of Spring Data (that is part of Spring-Boot 2.x if you are using that) wants an example object. My guess is that the book you were using was written before the recent release of Spring 5 / Spring Boot 2 / Spring Data 2.x.
Hopefully reading the migration guide as a reference alongside your [slightly out-of-date] book will help: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide
You can use findById, instead of findOne, findOne wants an example object, you can look here for more
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With