Обработка необязательных параметров в QueryDSL
Я использую QueryDSL с SpringData.
У меня есть таблица, Employee
и я создал класс сущности, EmployeeEntity
Я написал следующий метод обслуживания
public EmployeeEntity getEmployees(String firstName, String lastName)
{
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression query = null;
if(firstName != null)
{
query = employee.firstName.eq(firstName);
}
if(lastName != null)
{
query = query.and(employee.lastName.eq(lastName)); // NPException if firstName is null as query will be NULL
}
return empployeeDAO.findAll(query);
}
Как и в предыдущем, я прокомментировал NPException
. Как использовать QueryDSL для необязательных параметров в QueryDSL с помощью Spring данных?
Спасибо:)
Ответы
Ответ 1
BooleanBuilder
может использоваться как динамический построитель для булевых выражений:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanBuilder where = new BooleanBuilder();
if (firstName != null) {
where.and(employee.firstName.eq(firstName));
}
if (lastName != null) {
where.and(employee.lastName.eq(lastName));
}
return empployeeDAO.findAll(where);
}
Ответ 2
BooleanBuilder - это хорошо. Вы можете также обернуть его и добавить "необязательные" методы, чтобы избежать условий if:
Например, для "и" вы можете написать: (используются ядро lambdas Java 8)
public class WhereClauseBuilder implements Predicate, Cloneable
{
private BooleanBuilder delegate;
public WhereClauseBuilder()
{
this.delegate = new BooleanBuilder();
}
public WhereClauseBuilder(Predicate pPredicate)
{
this.delegate = new BooleanBuilder(pPredicate);
}
public WhereClauseBuilder and(Predicate right)
{
return new WhereClauseBuilder(delegate.and(right));
}
public <V> WhereClauseBuilder optionalAnd(@Nullable V pValue, LazyBooleanExpression pBooleanExpression)
{
return applyIfNotNull(pValue, this::and, pBooleanExpression);
}
private <V> WhereClauseBuilder applyIfNotNull(@Nullable V pValue, Function<Predicate, WhereClauseBuilder> pFunction, LazyBooleanExpression pBooleanExpression)
{
if (pValue != null)
{
return new WhereClauseBuilder(pFunction.apply(pBooleanExpression.get()));
}
return this;
}
}
@FunctionalInterface
public interface LazyBooleanExpression
{
BooleanExpression get();
}
И тогда использование будет намного чище:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
return empployeeDAO.findAll
(
new WhereClauseBuilder()
.optionalAnd(firstName, () -> employee.firstName.eq(firstName))
.optionalAnd(lastName, () -> employee.lastName.eq(lastName))
);
}
Можно также использовать jdk Необязательный класс
Ответ 3
Это Java 101 на самом деле: проверьте null
и инициализируйте запрос вместо конкатенации предикатов. Таким образом, вспомогательный метод, подобный этому, может сделать трюк:
private BooleanExpression createOrAnd(BooleanExpression left, BooleanExpression right) {
return left == null ? right : left.and(right);
}
Тогда вы можете просто сделать:
BooleanExpression query = null;
if (firstName != null) {
query = createOrAnd(query, employee.firstName.eq(firstName));
}
if (lastName != null) {
query = createOrAnd(query, employee.lastName.eq(lastName));
}
…
Обратите внимание, что я использую createOrAnd(…)
даже в первом предложении просто для согласованности и не должен адаптировать этот код, если вы решили добавить новое предложение еще до того, как оно было для firstName
.
Ответ 4
Я столкнулся с той же проблемой, и вот еще одна версия Тимо Весткэмпера
принял ответ, используя Optional
.
default Optional<Correlation> findOne(
@Nonnull final String value, @Nullable final String environment,
@Nullable final String application, @Nullable final String service) {
final QSome Some = QSome.some;
final BooleanBuilder builder = new BooleanBuilder();
ofNullable(service).map(some.service::eq).map(builder::and);
ofNullable(application).map(some.application::eq).map(builder::and);
ofNullable(environment).map(some.environment::eq).map(builder::and);
builder.and(some.value.eq(value));
return findOne(builder);
}
Ответ 5
если вы проверили реализацию QueryDSL null
:
public BooleanExpression and(@Nullable Predicate right) {
right = (Predicate) ExpressionUtils.extract(right);
if (right != null) {
return BooleanOperation.create(Ops.AND, mixin, right);
} else {
return this;
}
}
который, предположительно, вы хотите.
Ответ 6
Основываясь на том, что вам нужно, я бы сделал это
public List<EmployeeEntity> getEmployees(Optional<String> firstName, Optional<String> lastName)
{
BooleanExpression queryPredicate = QEmployeeEntity.employeeEntity.firstName.containsIgnoreCase(firstName.orElse("")).and(QEmployeeEntity.employeeEntity.lastName.containsIgnoreCase(lastName.orElse("")));
return empployeeDAO.findAll(queryPredicate);
}
Прежде всего, вы должны вернуть List
из EmployeeEntity
. Во-вторых, лучше использовать опцию, чем проверку, если ее null
, и вы можете передать значения Java 8 Optional
, полученные из необязательных RequestParam
таких, как это:
@RequestMapping(value = "/query", method = RequestMethod.GET)
public ModelAndView queryEmployee(@RequestParam(value = "firstName", required = false) Optional<String> firstName, @RequestParam(value = "lastName", required = false) Optional<String> lastName)
{
List<EmployeeEntity> result = getEmployees(firstName, lastName);
....
}
И очень важно использовать функцию containsIgnoreCase
в предикате: ее лучше, чем типичный like
, вызывают нечувствительность к регистру.
По-моему, вы должны использовать такой подход:
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,
Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
посмотрите здесь.
Ответ 7
Это очень простой способ работы с необязательными параметрами, я использую его в своем проекте:
public List<ResultEntity> findByOptionalsParams(String param1, Integer param2) {
QResultEntity qResultEntity = QResultEntity.resultEntity;
final JPQLQuery<ResultEntity> query = from(qResultEntity);
if (!StringUtils.isEmpty(param1)) {
query.where(qResultEntity.field1.like(Expressions.asString("%").concat(param1).concat("%")));
}
if (param2 != null) {
query.where(qResultEntity.field2.eq(param2));
}
return query.fetch();
}
Ответ 8
Существует еще один способ использования Optional
без BooleanBuilder
, хотя результирующий запрос может быть немного многословным:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression where = ofNullable(firstName).map(employee.firstName::eq).orElse(Expressions.TRUE)
.and(ofNullable(lastName).map(employee.lastName::eq).orElse(Expressions.TRUE));
return empployeeDAO.findAll(where);
}
Эта идея и добавление вспомогательной функции улучшают читабельность:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression where = optionalExpression(firstName, employee.firstName::eq)
.and(optionalExpression(lastName, employee.lastName::eq));
return empployeeDAO.findAll(where);
}
public static <T> BooleanExpression optionalExpression(T arg, Function<T, BooleanExpression> expressionFunction) {
if (arg == null) {
return Expressions.TRUE;
}
return expressionFunction.apply(arg);
}