How to find multiple and dynamic values using JPA and Specifications with spring boot

125 views Asked by At

I am using Sring boot and I have an entity Product that has a Map<String,String> that represent the attributes of the product. I want do a controller that permit the user to send a List of attributes with the comparision operator of each attribute, and then find the Products that meet the conditions.

I tried doing the following:

This is my Entity

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @ElementCollection
    @MapKeyColumn(name="name")
    @Column(name="value")
    private Map<String,String> attributes;
}

Then I use a searchCriteria to define of what attribute I am searching

public class SearchCriteria {
    private String key;
    private String operation;
    private String value;
}

Finally my Specification that for now only find by a single attribute, but this is the part that does not work because of the following error: java.lang.IllegalStateException: Basic paths cannot be dereferenced

public class ProductSpecification implements Specification<Product>{

    private SearchCriteria searchCriteria;

    public ProductSpecification(SearchCriteria searchCriteria) {
        this.searchCriteria = searchCriteria;
    }

    @Override
    public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        String operation = searchCriteria.getOperation();
        switch (operation) {
            case "==":
                return criteriaBuilder.equal(root.join("attributes").get(searchCriteria.getKey()),searchCriteria.getValue());      
            case "<":
                return criteriaBuilder.lessThan(root.join("attributes").get(searchCriteria.getKey()),searchCriteria.getValue());
            case ">":
                return criteriaBuilder.greaterThan(root.join("attributes").get(searchCriteria.getKey()),searchCriteria.getValue());
            default:
                return null;
        }
    }
}
@Service
public class ProductService {

    @Autowired
    ProductRepository productRepository;

    public List<Product> findBy(SearchCriteria searchCriteria){
        ProductSpecification productSpecification = new ProductSpecification(searchCriteria);
        return productRepository.findAll(productSpecification);
    }
    
@Controller
@RequestMapping("/api/product")
public class ProductController {

    @Autowired
    ProductService productService;

    @GetMapping("findBy")
    public ResponseEntity<?> findBy(@RequestBody SearchCriteria searchCriteria){
        List<Product> products = productService.findBy(searchCriteria);
        return new ResponseEntity<>(products, HttpStatus.OK);
    }
1

There are 1 answers

0
Santiago Di Sabatto On

I finally solved it in the following way.

    @Override
public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
    String operation = searchCriteria.getOperation();
    MapJoin<Product,String, String> attributesJoin = root.joinMap("attributes"); 

    switch (operation) {
        case "==":
            Predicate predicateKey = criteriaBuilder.equal(attributesJoin.key(), searchCriteria.getKey());
            Predicate predicateValue = criteriaBuilder.equal(attributesJoin.value(), searchCriteria.getValue());
            return criteriaBuilder.and(predicateKey,predicateValue);