Как использовать один класс спецификации JPA и методы для нескольких сущностей

Я создаю приложение Spring Boot, имеющее такие объекты, как Product, Category, Machinery, UsageLocation и т. Д. Общим для всех этих объектов является то, что все они имеют атрибут String с именем name и могут быть отфильтрованы из пользовательского интерфейса с помощью name. Я написал спецификацию продукта для фильтрации по имени, и он работает. Ниже приведен код

public final class ProductSpecifications 
{

    public static Specification<Product> whereNameContains(String name)
    {
        Specification<Product> finalSpec = (Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder cb)
            -> cb.like(root.get(Product_.PRODUCT_NAME), "%"+name+"%");
        return finalSpec;
    }

    public static Specification<Product> whereNameEqauls(String name)
    {
        Specification<Product> finalSpec = (Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder cb)
            -> cb.equal(root.get(Product_.PRODUCT_NAME), name);
        return finalSpec;
    }
}

Теперь проблема в том, что мне нужно снова написать тот же код для фильтрации других сущностей, с той лишь разницей, что это имя класса (Product), имя поля (PRODUCT_NAME) и возвращаемый тип метода. Могу ли я создать общий класс и метод, которому я могу передать имя класса и имя поля в качестве параметров, и он возвращает спецификацию соответствующего возвращаемого типа.


person Sridhar Patnaik    schedule 16.04.2020    source источник
comment
Ты пробовал это?   -  person Jens Schauder    schedule 16.04.2020
comment
Я не понял этого вопроса. Если вы спросите меня, пробовал ли я какое-либо решение, прежде чем отправлять вопрос - да. Я пробовал почти 2 дня, а затем опубликовал этот вопрос.   -  person Sridhar Patnaik    schedule 16.04.2020


Ответы (2)


Сначала сделайте свой SpecificationsBuilder общий

@Service
public final class SpecificationsBuilder<T>
{

    public static Specification<T> whereNameContains(String key,String name)
    {
        Specification<T> finalSpec = (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb)
            -> cb.like(root.get(key), "%"+name+"%");
        return finalSpec;
    }
}

Затем в контроллере @Autowire SpecificationsBuilder

@Autowire
private final SpecificationBuilder<Product> specificationBuilder;

public List<Product> getAll(String name) {
    Specification<Product> specification =
        specificationBuilder.whereNameContains(Product_.PRODUCT_NAME, name);
    List<Product> products = productRepo.findAll(specification);// pass the specifications
    return products;
  }

Вы можете создать свою собственную универсальную библиотеку для SpecificationsBuilder, она у меня есть. Подробности можно найти здесь

person Eklavya    schedule 16.04.2020
comment
да. Это произошло после небольшой настройки. Большое спасибо за ваше время и поддержку. - person Sridhar Patnaik; 17.04.2020
comment
Конечно. Я новичок в стеке. Итак, я не знал, что я должен принять. Я просто пометил это как полезное. В любом случае, сейчас я пометил его как принятый. Еще раз спасибо за ваше время. :) - person Sridhar Patnaik; 17.04.2020
comment
Я новичок в stackoverflow? Я не знал, что это имеет какое-то влияние. Увидев ваш комментарий, я понял. Я пометил свой ответ зеленой галочкой, потому что это было точное решение заданной мной проблемы. в любом случае, я вернул его сейчас. - person Sridhar Patnaik; 27.04.2020

Я смог решить эту проблему, используя ответ Абинаша. Ниже приведен рабочий код для многоразового класса спецификации.

import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import com.ec.application.model.Product;
import com.ec.common.Filters.FilterAttributeData;
import com.ec.common.Filters.FilterDataList;

public class SpecificationsBuilder<T>
{

      //#######################################/#################//
     //      Level 0 - If the field that you want to query is parent entity               //
    //########################################################//    

    public Specification<T> whereDirectFieldContains(String key,List<String> names)
    {
        Specification<T> finalSpec = null;
        for(String name:names)
        {
            Specification<T> internalSpec = (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb)
                    -> cb.like(root.get(key), "%"+ name  +"%");
            finalSpec  = specOrCondition(finalSpec,internalSpec); // to append specifications as I am filtering based on list of strings
        }
        return finalSpec;
    }

      //#######################################//
     //     Level 1 - If you want to query a child entity.          //                //
    //#######################################//

    public Specification<T> whereChildFieldContains(String childTable, String childFiledName,
            List<String> names) 
    {
        Specification<T> finalSpec = null;
        for(String name:names)
        {
            Specification<T> internalSpec = (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb)
                    -> cb.like(root.get(childTable).get(childFiledName), "%"+ name  +"%"); 
            finalSpec  = specOrCondition(finalSpec,internalSpec);  // to append specifications as I am filtering based on list of strings
        }
        return finalSpec;
    }

      //#######################################//
     //     Reusable Spec Setter to handle  NULLs.               //
    //#######################################//

    public Specification<T> specAndCondition(Specification<T> finalSpec, Specification<T> internalSpec) 
    {
        if(finalSpec == null) return internalSpec;
        else return finalSpec.and(internalSpec);
    }

    public Specification<T> specOrCondition(Specification<T> finalSpec, Specification<T> internalSpec) 
    {
        if(finalSpec == null) return internalSpec;
        else return finalSpec.or(internalSpec);
    } 
}

А это код для класса спецификации сущности


public static Specification<Product> getSpecification(FilterDataList filterDataList)
    {
        List<String> productNames = specbldr.fetchValueFromFilterList(filterDataList,"product");
        List<String> categoryNames = specbldr.fetchValueFromFilterList(filterDataList,"category");
        Specification<Product> finalSpec = null;
        if(productNames != null && productNames.size()>0)
            finalSpec = specbldr.specAndCondition(finalSpec, specbldr.whereDirectFieldContains(Product_.PRODUCT_NAME, productNames));

        if(categoryNames != null && categoryNames.size()>0)
        {
            finalSpec = specbldr.specAndCondition(finalSpec,
                    specbldr.whereChildFieldContains(Product_.CATEGORY,Category_.CATEGORY_NAME, categoryNames));
        }   
        return finalSpec;   
    }


person Sridhar Patnaik    schedule 17.04.2020