Search code examples
hibernate-criteriacriteria-api

Criteria Specification - How to write generic class to filter a entity by any enum field of the entity


I have an entity class which has 3 enum fields. Now, I want to write a specification class to filter records based on these enum fields. I know I can write one predicate per enum class. However, I wanted to know if I can write any generic class to filter any enum. this will save code and maintenance effort.

Below is my entity class.

@Entity
@Table(name = "customer_lead")
@Where(clause = ReusableFields.SOFT_DELETED_CLAUSE)
@Audited(withModifiedFlag = true)
@Data
public class Lead extends ReusableFields implements Serializable
{
    public Lead() {
        // TODO Auto-generated constructor stub
    }

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "lead_id", updatable = false, nullable = false)
    Long leadId;
    
    @Column(name="name")
    String customerName;
    
    @Column(name="primary_mobile")
    String primaryMobile;
    
    @Column(name="secondary_mobile")
    String secondaryMobile;
    
    @Column(name="email_id")
    String emailId;
    
    @Column(name="purpose")
    String purpose;
    
    @Column(nullable=true)
    @Enumerated(EnumType.STRING)
    PropertyTypeEnum propertyType;
    
    @Column
    @Enumerated(EnumType.STRING)
    SentimentEnum sentiment;

    
    @NonNull
    @Column(name="status",nullable=false)
    @Enumerated(EnumType.STRING)
    LeadStatusEnum status; 
}

Solution

  • Yes. This can be done. Steps

    1. Create a specification builder class with two methods. One to fetch enum value based on enum class. Second one to filter enum based on enum value returned by previous method.
    @Component
    public class SpecificationsBuilder<T>
    {
    public Specification<T> whereEnumFieldEquals(String key, List<String> names, Class claz)
        {
            Specification<T> finalSpec = null;
            for (String name : names)
            {
                @SuppressWarnings("unchecked")
                Specification<T> internalSpec = (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> cb
                        .equal(root.get(key), getEnum(name, claz));
                finalSpec = specOrCondition(finalSpec, internalSpec);
            }
            return finalSpec;
        }
    
    public static <E extends Enum<E>> E getEnum(String text, Class<E> klass)
        {
            return Enum.valueOf(klass, text);
        }
    }
    
    1. In your main class. Create a instance of specification builder with type lead and call the methods by passing enum.class as parameter. Sample code below. (note this is just a sample)
    static SpecificationsBuilder<Lead> specbldr = new SpecificationsBuilder<Lead>();
    
    specbldr.whereEnumFieldEquals(Lead_.SENTIMENT, sentiment, SentimentEnum.class));