Использование JPA с несколькими операциями AND - PullRequest
0 голосов
/ 06 апреля 2020

Я работаю над приложением Spring и определяю различные методы поиска в репозитории:

@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);

}

Проблема в том, что у меня есть 30 столбцов, которые могут быть дополнительно отфильтрованы. Это приведет к тому, что методы хранилища станут громоздкими:

List<TicketEntity> findByTicketIdAndStateAndFlaggedAndCol4AndCol5AndCol6AndCol7AndCol8AndCol9AndCol10AndCol11AndCol12AndCol13AndCol14AndCol15AndCol16AndCol17AndCol18AndCol19AndCol120....);

Как должен быть разработан слой JPA для обслуживания этого сценария?

Если я создаю объект с атрибутами:

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

Могу ли я передать SearchObject в метод поиска, и Spring JPA определит, какие атрибуты для вставки операторов AND зависят от того, какие атрибуты имеют значение Null - если атрибут не равен NULL, для этого атрибута создается соответствующий AND.

Ответы [ 2 ]

2 голосов
/ 06 апреля 2020
  1. Создание объекта фильтра, который будет содержать все необязательные столбцы, например:

@ AllArgsConstructor publi c 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);
}

}

Расширение вашего Respoitory с помощью JpaSpecificationExecutor

Создание класса спецификации:

publi c Класс TicketSpecification реализует спецификацию {

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]));
}

приватный предикат getCol1Predicate (Root root, заголовок строки) {return root .get ("col1"). In (col1); }

}

Используйте свой репозиторий: ticketRepository.findAll(specification);

1 голос
/ 06 апреля 2020

Использование Spring Data JPA Спецификация

Подробно Решение наберитесь терпения

Сначала создайте класс SpecificationCriteria, чтобы определить критерии, которые означают фильтрацию столбца в качестве ключа и фильтрацию значения в качестве значения

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

Затем создайте SpecificationCriteriaBuilder для построения ваших критериев

@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;
  }
}

Затем создайте класс SpecificationBuilder для построения ваших спецификаций. Вы можете построить из списка параметров фильтра (Критерии) список спецификаций

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;
  }
}

При обслуживании сначала соберите критерии, а затем соберите спецификацию, используя их. Затем используйте спецификации в вызове хранилища

@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;
  }

Расширение хранилища JpaSpecificationExecutor

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

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...