Is there any way to add a abstract method for JPA repository : findByEnumContaining(String enum) [enum is subString for possible ENUM values]

Viewed 31

This is my JPA @Repository, here we can get list<Person> with findByFullNameContaining(String query) - by providing just substring of fullName in query

@Repository
public interface PersonRepository extends CrudRepository<Person,String> {
    Optional<Person> findByFullName(String fullName);
    List<Person> findByDepartment(Department department);
    List<Person> findByFullNameContaining(String query);
    
}

Similarly, Can we perform something for ENUM values - by providing sub-string value of ENUM? How?

Eg.

public enum Department {
    NORTH,
    SOUTH,
    EAST,
    WEST
}

List<Person> findByDepartmentContaining(String query);

JPA @Entity Person:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "Person")
public class Person {

    @Id 
    @NotNull(message = "Mobile number is required")
    @Size(min = 10, max = 10, message = "Mobile no. must be 10 digits")
    @Column(name = "person_id", unique = true, length = 10)
    private String mobileNum;

    @Transient
    @NotNull(message = "Password is required")
    @Size(min = 1, message = "Password cannot be empty")
    private String password="****";

    @NotNull(message = "Name cannot be empty")
    @Size(min = 1, max = 255, message = "fullName must be 1-255 characters long")
    @Column(name = "full_name")
    private String fullName;

    @Column(name = "department")
    @Enumerated(EnumType.STRING)
    @NotNull(message = "Department must be specified")
    private Department department = Department.UNKNOWN;

    public Person() {
    }

    public Person(String mobileNum, String fullName, String password, Department department) {
        this.mobileNum = mobileNum;
        this.password = password;
        this.fullName = fullName;
        this.department = department;
    }

    public String getMobileNum() {
        return mobileNum;
    }

    public void setMobileNum(String mobileNum) {
        this.mobileNum = mobileNum;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    @Override
    public String toString() {
        return "Person [fullName=" + fullName + ", mobileNum=" + mobileNum + ", password=" + password + ", department=" + department + "]";
    }

}

1 Answers

The question does not specify what exactly the issue is when declaring such a method.

But having tried with spring boot 2.7 with PostgreSql Database the application throws the following runtime error:

java.lang.IllegalArgumentException: Parameter value [%some value%] did not match expected type [com.Department (n/a)]
    at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:54) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]

The issue at least with hibernate is that it will expect a Department field in an entity to be always passed as parameter in it's own object type which is Department.

I don't think there is a way to avoid this, as this is an out of the box functionality for Hibernate.

I think however that the correct approach would not be to define this type of method but the following. Department already exists in application code so it is known at the time the query needs to be invoked. So I think that the following solution would be considered a better practice:

//Repository method to be used in PersonRepository
List<Person> findByDepartmentIn(List<Department> departments); 

And then the repository could be invoked in the following way from the service layer.

//Service method to be used
public List<Person> findByDepartmentIn(String searchBy) {

    List<Department> departments = Arrays.stream(Department.values()).filter(dep -> dep.getName().contains(searchBy)).collect(Collectors.toList());

    return personRepository.findDepartmentIn(departments);
}
Related