написать get запрос с возможностью фильтрации по полям

В поле запроса получаю данные по @RequestParam,

    @GetMapping(value = "/get_transactions_by_comment_and_amount")
    public List<TransactionDto> getTransactionsByCommentAndAmount(@RequestParam(value = "comment", required = false) String comment,
                                                                  @RequestParam(value = "amount", required = false) Long amount) {
        return transactionService.getTransactionsFilteredByCommentAndAmount(comment, amount);
    }

Интересует сам запрос в БД


public interface TransactionRepository extends JpaRepository<Transaction, Long> {
        List<Transaction> findTransactionsByCommentAndAmount(String comment, Long amount);
}

В моем случе запрос с AND или OR не подходит. При наличии обоих параметров, фильтрация должна производиться по обоим. При наличии одного параметра - фильтрация по одному, при отсутствии входящих параметров - получение всех записей.


Ответы (3 шт):

Автор решения: Olegus Testerovichus

Тебе нужно добавить отдельный метод с кастомным @Query

public interface TransactionRepository extends 
JpaRepository<Transaction, Long> {

    @Query("""
           SELECT t FROM Transaction t WHERE
              (:comment is null or t.comment = :comment) and
              (:amount is null or t.amount = :amount)
           """)
    List<Transaction> findAllByFilter(String comment, Long amount);
}
→ Ссылка
Автор решения: ulxanxv

Нужно написать свою собственную реализацию метода и самому проверять пришедшие поля на null

Интерфейс

public interface TransactionRepository extends JpaRepository<Transaction, Long>, TransactionRepositoryCustom {
}

Свой собственный интерфейс

public interface TransactionRepositoryCustom {

    List<Transaction> findTransactionsByCommentAndAmount(String comment, Long amount);

}

Реализация

@Repository
@RequiredArgsConstructor
public class TransactionRepositoryImpl implements TransactionRepositoryCustom {

    private final static String COMMENT_FIELD_NAME = "comment";
    private final static String AMOUNT_FIELD_NAME = "amount";

    private final EntityManager entityManager;

    @Override
    public List<Transaction> findTransactionsByCommentAndAmount(String comment, Long amount) {
        final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        final CriteriaQuery<Transaction> cq = cb.createQuery(Transaction.class);

        Root<Transaction> root = cq.from(Transaction.class);
        addEqualPredicate(cb, root, COMMENT_FIELD_NAME, comment);
        addEqualPredicate(cb, root, AMOUNT_FIELD_NAME, amount);

        final TypedQuery<Transaction> query = entityManager.createQuery(cq);
        return query.getResultList();
    }

    private void addEqualPredicate(CriteriaBuilder cb, Root<Transaction> root, String name, Object value) {
        if (value != null) {
            cb.equal(root.get(name), value);
        }
    }

}

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

@Autowired
private TransactionRepository transactionRepository;
→ Ссылка
Автор решения: Roman-Stop RU aggression in UA

Еще один вариант, это использовать спецификации. Они позволяют динамически конструировать предикаты.

Сначала расширяем наш репозиторий от JpaSpecificationExecutor:

public interface TransactionRepository
   extends JpaRepository<Transaction, Long>, JpaSpecificationExecutor<Transaction> { }

Далее определяем базовые предикаты (это можно сделать в любом классе или интерфейсе, в том же TransactionRepository):

import org.springframework.data.jpa.domain.Specification;


...
static Specification<Transaction> hasComment(String comment) {
    return (transaction, cq, cb) -> { 
        if (comment == null) {
            return cb.isTrue(cb.literal(true));
        } else {
            return cb.equal(transaction.get("comment"), comment);
        }
    }
}

static Specification<Transaction> hasAmount(Long amount) {
    return (transaction, cq, cb) -> { 
        if (amount == null) {
            return cb.isTrue(cb.literal(true));
        } else {
            return cb.equal(transaction.get("amount"), amount);
        }
    } 
}

Теперь в месте вызова можно делать так:

import static org.springframework.data.jpa.domain.Specification.where;
...
transactionRepository.findAll(where(hasComment(comment)));
// или
transactionRepository.findAll(where(hasComment(comment)).and(hasAmount(amount)));

В данном случае, логика обработки null находится внутри hasComment и hasAmount, но ее можно вынести и наружу, чтоб этим занимался клиент transactionRepository и строил спецификацию динамически:

static Specification<Transaction> hasComment(String comment) {
    return (transaction, cq, cb) -> cb.equal(transaction.get("comment"), comment);
}

static Specification<Transaction> hasAmount(Long amount) {
    return (transaction, cq, cb) -> cb.equal(transaction.get("amount"), amount);
        }
    } 
}

...
Specification<Transaction> txFilter = someInitialFilter;
if (comment != null) {
   txFilter = txFilter.and(hasComment(comment));
}
if (amount != null) {
   txFilter = txFilter.and(hasAmount(amount));
}
transactionRepository.findAll(txFilter);

→ Ссылка