javajpaspring-dataspecificationspredicate

Specification/Predicate to Search Nested Objects


I'm using Spring Boot with Spring JPA and Specification Executor. I have my Specification/Predicate combo successfully searching the simple attributes within my class. However, I am having difficulties searching the objects within. Do they need a separate Specification? I have a class that has 2 Many To One mapping classes within and would like to search those fields from within the same class.

Predicate Implementation

public Specification<User> getSpecification(SpecificationField field, Object searchCriteria){
    return new Specification<User>() {
    @Override
    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

    if(searchCriteria instanceof String){
        searchCriteria.toString().trim().toLowerCase();
    }

    switch(field){
    case USER_GROUP:    
        return cb.equal(cb.lower(root.<String> get("group").get("name")), searchCriteria);
    case USER_ROLE: 
        return cb.equal(cb.lower(root.<String> get("role").get("name")), searchCriteria);
    case USER_EMAIL: 
        return cb.equal(cb.lower(root.<String> get("email")), searchCriteria);
    case USER_FIRSTNAME: 
        return cb.equal(cb.lower(root.<String> get("firstName")), searchCriteria);
    case USER_LASTNAME: 
        return cb.equal(cb.lower(root.<String> get("lastName")), searchCriteria);
    case USER_USERNAME: 
        return cb.equal(cb.lower(root.<String> get("username")), searchCriteria);
    default:
        assert true;
        return null;
    }

User Class

@Id
@GeneratedValue
@Column(name = "USER_ID")
private Long id;

@Column(name = "USER_FIRSTNAME", nullable = false)
private String firstName;

@Column(name = "USER_LASTNAME", nullable = false)
private String lastName;

@Column(name = "USER_EMAIL", nullable = false, unique = true)
private String email;

@Column(name = "USER_USERNAME", nullable = false, unique = true)
private String username;

@Column(name = "USER_PASSWORD", nullable = false)
private String encryptedPassword;

@ManyToOne
@JoinColumn(name = "USER_ROLE", nullable = false)
private Role role;

@ManyToOne
@JoinColumn(name = "USER_GROUP", nullable = false)
private Group group;

Role Class

@Id
@GeneratedValue
@Column(name = "ROLE_ID")
private Long id;

@Column(name = "ROLE_NAME", nullable = false, unique = true)
private String name;

Group Class

@Id
@GeneratedValue
@Column(name = "GROUP_ID")
private Long id;

@Column(name = "GROUP_NAME", nullable = false, unique = true)
private String name;

@Column(name = "GROUP_TOKEN", nullable = false, unique = true)
private String token;

--------------------------------------------------------------------------

EDIT:

Came up with this Predicate implementation for the nested objects.

Root<Group> group = query.from(Group.class);
Root<Role> role = query.from(Role.class);

switch(field){
case USER_GROUP:
    return cb.equal(cb.lower(group.<String> get("name")), searchCriteria);
case USER_ROLE: 
    return cb.equal(cb.lower(role.<String> get("name")), searchCriteria);

This is working better, but not great. I have the possible group name values of DEV, UAT, or PROD. If I give any of these I get all the Users in my Database. If I don't I don't get any Users back. How can I do a text search on the name of this nested object?

Thanks.


Solution

  • I found the answer myself, but wanted to share with everybody so that it may help other people with the same issue.

    @Override
    public Predicate toPredicate(Root<User> userRoot, CriteriaQuery<?> query, CriteriaBuilder cb) {
    
        switch(field){
        case USER_GROUP:
            Join<User, Group> groupJoin = userRoot.join("group");
            return cb.equal(cb.lower(groupJoin.<String> get("name")), searchCriteria);
        case USER_ROLE: 
            Join<User, Role> roleJoin = userRoot.join("role");
            return cb.equal(cb.lower(roleJoin.<String> get("name")), searchCriteria);
    

    Using the Join class from the javax.persistence library I could search the nested objects within my class with the same Specification.