Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should I use JpaRepository.findOne() with SpringBoot?

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>)
like image 951
Justin Lee Avatar asked Jan 05 '19 08:01

Justin Lee


People also ask

How do I enable JpaRepository in Spring boot?

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.

What is the use of JpaRepository in Spring boot?

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.

Should I use JpaRepository or CrudRepository?

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.


3 Answers

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.

like image 114
davidxxx Avatar answered Oct 12 '22 22:10

davidxxx


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

like image 41
Rick Hanton Avatar answered Oct 13 '22 00:10

Rick Hanton


You can use findById, instead of findOne, findOne wants an example object, you can look here for more

like image 26
Hakob Hakobyan Avatar answered Oct 13 '22 00:10

Hakob Hakobyan