Using JPA with multiple AND operations

不打扰是莪最后的温柔 提交于 2020-04-28 21:22:08

问题


I'm working on a Spring app and defining various find methods on a repository:

@Repository
public interface TicketRepository extends JpaRepository<TicketEntity, Long> {

List<TicketEntity> findByTicketId(@Param("ticketId") Long ticketId);

List<TicketEntity> findByTicketIdAndState(@Param("ticketId") Long ticketId, @Param("state") String state);

List<TicketEntity> findByTicketIdAndStateAndFlagged(@Param("ticketId") Long ticketId, @Param("state") String state, @Param("flagged") String Flagged);

}

The problem is that I have 30 columns which can be optionally filtered on. This is will result in the repository methods becoming unwieldy:

List<TicketEntity> findByTicketIdAndStateAndFlaggedAndCol4AndCol5AndCol6AndCol7AndCol8AndCol9AndCol10AndCol11AndCol12AndCol13AndCol14AndCol15AndCol16AndCol17AndCol18AndCol19AndCol120....);

How should the JPA layer be designed to cater for this scenario ?

If I create an object with attributes:

public class SearchObject {
   private String attribute1; 
   //Getter and Setters
.
.
.
.
}

Can I pass SearchObject into a a find method and Spring JPA will determine which attributes to insert AND statements for depending on which attributes are Null - if the attribute is not null a corresponding AND is generated for that attribute.


回答1:


  1. Create filter object that will contain all optional columns e.g.:

@AllArgsConstructor public class TicketFilter {

private final String col1;
private final Integer col2;

public Optional<String> getCol1() {
    return Optional.ofNullable(col1);
}

public Optional<Integer> getCol2() {
    return Optional.ofNullable(col2);
}

}

  1. Extend your Respoitory with JpaSpecificationExecutor
  2. Create specification class:

    public class TicketSpecification implements Specification {

    private final TicketFilter ticketFilter;
    
    public TicketSpecification(TicketFilter ticketFilter) {
        this.ticketFilter = ticketFilter;
    }
    
    @Override
    public Predicate toPredicate(Root<Ticket> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        List<Predicate> predicates = new ArrayList<>();
        ticketFilter.getTitle().ifPresent(col1 -> predicates.add(getCol1Predicate(root, col1)));
        ticketFilter.getDescription().ifPresent(col2 -> predicates.add(getCol2Predicate(root, col2)));
        return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
    }
    

    private Predicate getCol1Predicate(Root root, String title) { return root.get("col1").in(col1); }

    }

  3. Use your repository: ticketRepository.findAll(specification);




回答2:


Use Spring Data JPA Specification

Detail Solution be patient

First create a SpecificationCriteria class to define your criterias means filtering column as key and filtering value as value

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SpecificationCriteria {
  private String key;
  private Object value;
}

Then create SpecificationCriteriaBuilder to build your Criteria

@Service
public class SpecificationCriteriaBuilder {

  public List<SpecificationCriteria> buildCriterias(String name) {
    List<SpecificationCriteria> specificationCriterias = new ArrayList<SpecificationCriteria>();
    if (!StringUtils.isEmpty(name)) {
      specificationCriterias
          .add(SpecificationCriteria.builder().key("name")
              .value(name).build());
    }
    // Here you can add other filter one by one 
    return specificationCriterias;
  }
}

Then create a SpecificationBuilder class to build your specifications. You can build from the list of filter options(Criteria) to List of specification

import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

@Service
public class SpecificationBuilder<T> {

  public Specification<T> buildSpecification(List<SpecificationCriteria> specificationCriterias) {
    if (ObjectUtils.isEmpty(specificationCriterias)) {
      return null;
    }
    Specification<T> specification = getSpecification(specificationCriterias.get(0));
    for (int index = 1; index < specificationCriterias.size(); index++) {
      SpecificationCriteria specificationCriteria = specificationCriterias.get(index);
      specification =
          Specification.where(specification).and(getSpecification(specificationCriteria));
    }
    return specification;
  }

  public Specification<T> getSpecification(SpecificationCriteria specificationCriteria) {
    Specification<T> specification = new Specification<T>() {
      private static final long serialVersionUID = 2089704018494438143L;
      @Override
      public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        return builder.equal(root.get(specificationCriteria.getKey()),
            specificationCriteria.getValue());
      }
    };
    return specification;
  }
}

In service first build criteria and then build specification using them. Then use specifications in repository call

@Service
@Transactional
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserService {

private final SpecificationCriteriaBuilder criteriaBuilder;
private final SpecificationBuilder<User> specificationBuilder;
private final UserRepository userRepository;

public List<User> getAll(String name) {
    List<SpecificationCriteria> specificationCriterias =
        criteriaBuilder.buildCriterias(name); // here you can pass other parameter as function argument 
    Specification<User> specification =
        specificationBuilder.buildSpecification(specificationCriterias);
    List<User> users = userRepository.findAll(specification);// pass the specifications
    return users;
  }

Repository extend JpaSpecificationExecutor

@Repository
public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {

}


来源:https://stackoverflow.com/questions/61062078/using-jpa-with-multiple-and-operations

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!