Spring Data JPA - именованный запрос, игнорирующий нулевые параметры - PullRequest
3 голосов
/ 16 февраля 2020

У меня есть следующий репозиторий:

@Repository
public interface EntityRepository extends JpaRepository<Entity, Long> {

    List<Entity> findAllByFirstId(Long firstId);
    List<Entity> findAllBySecondId(Long secondId);
    List<Entity> findAllByFirstIdAndSecondId(Long firstId, Long secondId);
}

Конструктор, реализующий интерфейс, сгенерированный с помощью io.swagger:swagger-codegen-maven-plugin, использует Optional<Long> в качестве необязательных параметров запроса (базовый сервис использует также те же параметры):

ResponseEntity<List<Entity>> entities(Optional<Long> firstId, Optional<Long> secondId);

Я хотел бы отфильтровать сущности на основе параметров firstId и secondId, которые никогда не равны null s в базе данных, но могут быть переданы через конструктор (параметр для поиска является необязательным).

Проблема возникает с именованными запросами, когда null передается в качестве необязательного параметра, JpaReposotory использует null в качестве критерия поиска в базе данных. Это то, что я не хочу - я хочу игнорировать фильтрацию на основе этого параметра, пока он null.

Мое обходное решение на основе Optional:

public List<Entity> entities(Optional<Long> firstId, Optional<Long> secondId) {

    return firstId
        .or(() -> secondId)
        .map(value -> {
            if (firstId.isEmpty()) {
                return entityRepository.findAllBySecondId(value);
            }
            if (secondId.isEmpty()) {
                return entityRepository.findAllByFirstId(value);
            }
            return entityRepository.findAllByFirstIdAndSecondId(
            firstId.get(), secondId.get());
        })
        .orElse(entityRepository.findAll())
        .stream()
        .map(...)     // Mapping between DTO and entity. For sake of brevity
                      // I used the same onject Entity for both controler and repository 
                      // as long as it not related to the question   

        .collect(Collectors.toList());
}

Эта проблема уже была задана: Spring Data - игнорировать параметр, если он имеет нулевое значение и создан тикет DATAJPA-209 .

Пока Вопросу уже почти 3 года, а билет относится к 2012 году. Я хотел бы спросить, существует ли более удобный и универсальный способ избежать накладных расходов на обработку Optional и дублирование методов репозитория. Решение для 2 таких параметров выглядит приемлемым, однако я бы хотел применить ту же самую фильтрацию для 4-5 параметров.

Ответы [ 3 ]

3 голосов
/ 16 февраля 2020

Вам нужен Specification служебный класс, подобный этому

public class EntitySpecifications {
    public static Specification<Entity> firstIdEquals(Optional<Long> firstId) {// or Long firstId. It is better to avoid Optional method parameters.
        return (root, query, builder) -> 
            firstId.isPresent() ? // or firstId != null if you use Long method parameter
            builder.equal(root.get("firstId"), firstId.get()) :
            builder.conjunction(); // to ignore this clause
    }

    public static Specification<Entity> secondIdEquals(Optional<Long> secondId) {
        return (root, query, builder) -> 
            secondId.isPresent() ? 
            builder.equal(root.get("secondId"), secondId.get()) :
            builder.conjunction(); // to ignore this clause
    }
}

Тогда ваш EntityRepository должен быть расширен JpaSpecificationExecutor

@Repository
public interface EntityRepository 
    extends JpaRepository<Entity, Long>, JpaSpecificationExecutor<Entity> {

}

Использование:

@Service
public class EntityService {    

    @Autowired
    EntityRepository repository;

    public List<Entity> getEntities(Optional<Long> firstId, Optional<Long> secondId) {
        Specification<Entity> spec = 
            Specifications.where(EntitySpecifications.firstIdEquals(firstId)) //Spring Data JPA 2.0: use Specification.where
                          .and(EntitySpecifications.secondIdEquals(secondId));

        return repository.findAll(spec);        
    }
}
2 голосов
/ 16 февраля 2020

io.swagger:swagger-codegen-maven-plugin генерирует их как Optional, так как я запрашиваю их как необязательные (required: false по умолчанию). Я мог бы генерировать их как коробочные типы, такие как Long,…

Возможно, это отчасти дело вкуса. Если бы я и я могли, я бы go за версию без Optional. Я не думаю, что они вносят здесь что-либо полезное.

public List<Entity> entities(Long firstId, Long secondId) {

    List<Dto> dtos;
    if (firstId == null) {
        if (secondId == null) {
            dtos = entityRepository.findAll();
        } else {
            dtos = entityRepository.findAllBySecondId(secondId);
        }
    } else {
        if (secondId == null) {
            dtos = entityRepository.findAllByFirstId(firstId);
        } else {
            dtos = entityRepository.findAllByFirstIdAndSecondId(firstId, secondId);
        }
    }

    return dtos.stream()
        .map(...)
        .collect(Collectors.toList());
}

Класс Optional был разработан для использования с возвращаемыми значениями, которые могут отсутствовать, на самом деле ни для чего другого, поэтому я прочитал. Я думаю, что бывают редкие ситуации, когда я бы использовал их для чего-то другого, но это не одна из них.

0 голосов
/ 16 февраля 2020

Я бы посоветовал вам использовать спецификации . Смотрите документацию и примеры здесь .

Вкратце, идея заключается в следующем. Для каждого атрибута вы определяете спецификацию. Затем проверьте каждый атрибут в ваших критериях поиска и, если он не равен нулю, добавьте соответствующую спецификацию в «сцепленную» спецификацию. Затем вы выполняете поиск, используя эту «сцепленную» спецификацию.

...