AndreaNobili
AndreaNobili

Reputation: 42967

Why this Spring Data JPA query by name method gives me this error and Eclipse forces me to return an Optional<User> instead a simple User object?

I am working on a Spring Boot project using Spring Data JPA and Hibernate mapping. In my repository classes I am using a query by method name approach and I have the following question. I have this User entity class:

@Entity
@Table(name = "portal_user")
@Getter
@Setter
public class User implements Serializable {
     
    private static final long serialVersionUID = 5062673109048808267L;
    
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    
    @Column(name = "first_name")
    @NotNull(message = "{NotNull.User.firstName.Validation}")
    private String firstName;
    
    @Column(name = "middle_name")
    private String middleName;
    
    @Column(name = "surname")
    @NotNull(message = "{NotNull.User.surname.Validation}")
    private String surname;
    
    @Column(name = "sex")
    @NotNull(message = "{NotNull.User.sex.Validation}")
    private char sex;
    
    @Column(name = "birthdate")
    @NotNull(message = "{NotNull.User.birthdate.Validation}")
    private Date birthdate;
    
    @Column(name = "tax_code")
    @NotNull(message = "{NotNull.User.taxCode.Validation}")
    private String taxCode;
    
    @Column(name = "e_mail")
    @NotNull(message = "{NotNull.User.email.Validation}")
    private String email;
    
    @Column(name = "pswd")
    @NotNull(message = "{NotNull.User.pswd.Validation}")
    private String pswd;
    
    @Column(name = "contact_number")
    @NotNull(message = "{NotNull.User.contactNumber.Validation}")
    private String contactNumber;
    
    @Temporal(TemporalType.DATE)
    @Column(name = "created_at")
    private Date createdAt;
    
    @Column(name = "is_active")
    private boolean is_active;
    
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
    @JsonManagedReference
    private Set<Address> addressesList = new HashSet<>();
    
    @ManyToMany(cascade = { CascadeType.MERGE })
    @JoinTable(
        name = "portal_user_user_type", 
        joinColumns = { @JoinColumn(name = "portal_user_id_fk") }, 
        inverseJoinColumns = { @JoinColumn(name = "user_type_id_fk") }
    )
    Set<UserType> userTypes;


    @ManyToOne(fetch = FetchType.EAGER)
    @JsonProperty("subagent")
    private User parent;

    public User() {
        super();
        // TODO Auto-generated constructor stub
    }


    public User(String firstName, String middleName, String surname, char sex, Date birthdate, String taxCode,
            String email, String pswd, String contactNumber, Date createdAt, boolean is_active) {
        super();
        this.firstName = firstName;
        this.middleName = middleName;
        this.surname = surname;
        this.sex = sex;
        this.birthdate = birthdate;
        this.taxCode = taxCode;
        this.email = email;
        this.pswd = pswd;
        this.contactNumber = contactNumber;
        this.createdAt = createdAt;
        this.is_active = is_active;
    }
    

}

Then I have this repository interface:

public interface UsersRepository extends JpaRepository<User, Integer> {
    
    /**
     *  Retrieve an user by its e-mail address:
     * @param email is the e-mail of the user
     * @return the user related to the specified e-mail
     */
    User findByemail(String email);
    
    /**
     * Retrieve the list of users belonging to a specific user type
     * @param typeName is the name of the user type
     * @return the list of users belonging to a specific user type
     */
    List<User> findByUserTypes_TypeName(String typeName);
    
    /**
     *  Retrieve the list of users children of an user. 
     *  Generally used to retrieve the client users of a specific sub-agent user
     * @param id is the id of the parent user
     * @return the list of children of a specific user (generally used to retrieve the client users of a specific sub-agent user)
     */
    List<User> findByParent_Id(Integer id);
    
    
    /**
     * Retrieve an user starting from is id
     * @param id of the user which you want to retrieve
     * @return the retrieved object
     */
    Optional<User> findById(Integer id);

}

Om my doubt is on the last method (intended to retrieve an User object by its own id field value, this one:

Optional<User> findById(Integer id);

Originally I tried to define it simply as:

User findById(Integer id);

But doing in this way Eclipse\STS give me the following error:

Multiple markers at this line
    - implements org.springframework.data.repository.CrudRepository<com.easydefi.users.entity.User,java.lang.Integer>.
     findById
    - The return type is incompatible with CrudRepository<User,Integer>.findById(Integer)
    - The return type is incompatible with CrudRepository<User,Integer>.findById(Integer)

Why this error? and why Eclipse force me to return Optional<User> as returned type? What is this Optional? And why in the findByemail() allow me to return a simple User type?

Upvotes: 0

Views: 2003

Answers (3)

Jens Schauder
Jens Schauder

Reputation: 81950

Alexy and Raul already gave good explanations why this doesn't work. But the options the offer for avoiding the problem is rather limited. Here are the most relevant options you have:

  1. omit your findById method and just use the one offered by the CrudRepository. Works but it seems you don't want the Optional so this isn't really satisfying.

  2. You can modify the method name by using an alternative for find, e.g. search or query. See https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-keywords for a list of possibly words to use.

  3. You can also add anything between find and By. findUserById would work just as well.

  4. You can decide not to inherit from CrudRepository. This of course will remove all the other methods of the CrudRepository as well. But if you copy their declaration into your repository interface the will get their proper implementation. You may also use this variant to prune methods that you possibly don't want to have in your repository anyway.

Upvotes: 1

Raul Lopes
Raul Lopes

Reputation: 161

Your repository extends JpaRepository. JpaRepository extends PagingAndSortingRepository that extends CrudRepository. CrudRepository has the method: Optional<T> findById(ID var1);. https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html

When you define a method with this signature you are overriding the CrudRepository method. But if you define a different return type for this method created in your repository, your code won't compile.

Optional was created in Java 8, in resume, is used to indicate that the return data of method may be null. Is a good practice to use Optional for these scenarios. https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html

About another methods that you mentioned, they are just methods of your UsersRepository that you can create as you want.

Upvotes: 1

Alex Veleshko
Alex Veleshko

Reputation: 1242

As you can see in the JavaDoc of CrudRepository interface which JpaRepository extends, there is already a findById method with the same signature.

Java overriding rules do not allow you to have a method with the same name and parameters and a return type that is not consistent with the parent class definition (in this case, Optional<User>).

If you somehow change the method name, Spring Data JPA will be able to use a plain reference. In this case, null will be returned if the entity hasn't been found. For you case however, you should just drop your findById method.

Upvotes: 1

Related Questions