QueryDSL: convert list of BooleanExpression to Predicate - java

I'm trying to create a query that would depend on number of boolean parameters. The function that creates the predicate looks like this:
Predicate createPredicate(Boolean param1, Boolean param2) {
List<BooleanExpression> booleanExpressions = new List<>();
if (param1)
booleanExpressions.add(/** expression 1 **/);
if (param2)
booleanExpressions.add(/** expression 2 **/);
convertExpressionsToPredicate(booleanExpressions);
}
The problem is the convertExpressionsToPredicate function. Is there any special method in querydsl to join list of expressions into one predicate using the or operator?
The solution I'm looking for should convert this:
List<BooleanExpression> booleanExpressions = List(exp1, exp2, exp3);
into:
Predicate p = exp1.or(exp2).or(exp3)

To construct complex boolean expressions, use the com.querydsl.core.BooleanBuilder class. It implements Predicate and can be used in cascaded form. For example:
public List<Customer> getCustomer(String... names) {
QCustomer customer = QCustomer.customer;
JPAQuery<Customer> query = queryFactory.selectFrom(customer);
BooleanBuilder builder = new BooleanBuilder();
for (String name : names) {
builder.or(customer.name.eq(name));
}
query.where(builder);
return query.fetch();
}
BooleanBuilder is mutable and initially represents null. After each and or or call it represents the result of the operation.

If anybody is using Spring Boot JPA and QueryDSL, you can use the following to get a Page<YourType> result (in Kotlin):
fun listCards(page: Int?, pageSize: Int?, orderColumnName: String?, orderDirection: Sort.Direction?, filter: CardFilter?): Page<Card> {
val pageRequest = PageRequest.of(page ?: 0, pageSize ?: 10, orderDirection ?: Sort.Direction.ASC, orderColumnName ?: Card::cardNumber.name)
val conditions = BooleanBuilder()
filter?.cardNumber?.let { qCard.cardNumber.contains(it) }?.let { conditions.and(it) }
filter?.cardHolderName?.let { qCard.cardHolderName.containsIgnoreCase(it) }?.let { conditions.and(it) }
filter?.phoneNumber?.let { qCard.phoneNumber.contains(it) }?.let { conditions.and(it) }
filter?.email?.let { qCard.email.contains(it) }?.let { conditions.and(it) }
filter?.locale?.let { qCard.locale.eq(it) }?.let { conditions.and(it) }
if (conditions.hasValue()) {
return cardRepository.findAll(conditions.value!!, pageRequest)
}
return cardRepository.findAll(pageRequest)
}

ExpressionUtils.allOf(ps) // use AND join conditions, ps is a list of predicate
ExpressionUtils.anyOf(ps) // use OR join conditions, ps is a list of predicate

Related

How to modify Predicate

I have a Vaadin 8 UI with several columns and each column can be filtered.
I want to extend one TextField so I can filter for multiple values.
The input would be like "ABC|XYC" and I want to build an OR predicate.
My current code for building the predicate looks like:
final BooleanBuilder booleanBuilder = new BooleanBuilder();
for (final PropertyFilter<?> propertyFilter : filterList) {
final Predicate p = propertyFilter.getPredicate();
if (p != null) {
booleanBuilder.and(p);
}
}
PropertyFilter extends Vaadins PredicateProvider and builds one predicate to search for the data.
My current approach is to get arguments from the full predicate. The original predicate would look like "name LIKE ABC|XYC", which I want to split.
for (final PropertyFilter<?> propertyFilter : filterList) {
BooleanBuilder internBuilder = new BooleanBuilder();
Predicate p = propertyFilter.getPredicate();
if (p != null) {
BooleanOperation bop = (BooleanOperation) p;
String arg = bop.getArg(1).toString();
String[] split = arg.split("\\|");
for(String s : split) {
internBuilder.or(Expressions.operation(bop.getType(), bop.getArg(0), ConstantImpl.create(s)));
}
}
if (p != null) {
booleanBuilder.and(p);
}
}
I have to issues with my approach.
First:
Expressions.operation shows me an error which I don't know how to fix it
The method operation(Class, Operator, Expression...) in the type Expressions is not applicable for the arguments (Class<capture#57-of ? extends Boolean>, Expression<capture#58-of ?>, Constant)
Second:
I have the feeling, that my approach is dirty and not the best way.
It is my first time, that I'm "digging" so deep into querydsl, so I'll gladly take any help or hint on how to get this clean and working.

How to map the Flux result to another object type with ReactiveMongoTemplate

The find API in java Spring #Repository class wants to return a Flux<AggregatedObject>, but the getReactiveMongoTemplate().find() returns a Document of different type. What could be the best way to convert the resultant Flux to AggregatedObject type?
public Flux<AggregatedObject> find() {
Criteria filterCriteria = getFilterCriteria();
Query query = new BasicQuery(filterCriteria.getCriteriaObject());
Flux flux = getReactiveMongoTemplate().find(query, Document.class, collectionName);
// how to convert Document to AggregatedObject and return?
}
Just add your AggregatedObject as parametrized type:
public Flux<AggregatedObject> find() {
Criteria filterCriteria = getFilterCriteria();
Query query = new Query(filterCriteria);
return getReactiveMongoTemplate().find(query, AggregatedObject.class, collectionName);
}
we can do it like this;
flux.map(d -> reactiveMongoTemplate.getConverter()
.read(AggregatedObject.class, d.get("value", Document.class)))

If else with java 8 Lambda

I want to replace conventional if else with lambda. Consider following highlighted code, is there some simple way to have this represented with Lambda ?
public class IfElseLambda {
public static void main(String[] args) {
String value = null;
DataObj data = new DataObj();
List<DataObj> dataObjs = data.getDataObjs();
***if (dataObjs != null) {
value = dataObjs.stream().map(dataObject -> getValue(dataObject)).filter(Objects::nonNull).findFirst().orElse(null);
} else {
value = getValue(data);
}***
}
public static String getValue(DataObj dataObj) {
return "Get value from dataObj";
}
}
class DataObj {
List<DataObj> dataObjs;
public List<DataObj> getDataObjs() {
return dataObjs;
}
public void setDataObjs(List<DataObj> dataObjs) {
this.dataObjs = dataObjs;
}
}
One thing you can do is to change the null list to something which results in the same output:
List<DataObj> dataObjs = Optional.ofNullable(data.getDataObjs()).orElse(Collections.singletonList(data));
dataObjs will now be a list with a single element in the case that data.getDataObjs() is null.
Now you don't need the if/else:
value = dataObjs.stream().map(dataObject -> getValue(dataObject)).filter(Objects::nonNull).findFirst().orElse(null);
I your aim is to isolate the logic of your if-else, and potentially allowing it to be replaced, maybe you could do the following :
Your lambda take as input your data list, and gives you back a String value. Therefore, you can use a java.util.Function interface, like this:
Function<List<DataObj>, String> extractor = dataList
-> dataList == null? Stream.of(DEFAULT_DATA_OBJ) : dataList.stream()
.map(dataObject -> getValue(dataObject))
.filter(Objects::nonNull)
.findFirst()
.orElse(null)
Note, you still have a ternary operator (Do not see how you could do without it, because if your list can be null, you cannot even use Stream.concat to protect from empty-list). However, with that construct, the logic of your ternary operator is replaceable if you make the extractor function replaceable in your code.
Exemple:
public static void main(String... args) {
final List<DataObj> dataList = ...;
final DataObj defaultValue = ...;
Function<List<DataObj>, String> extractor = dataList
-> dataList == null? Stream.of(defaultValue) : dataList.stream()
.map(dataObject -> getValue(dataObject))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
doStuff(dataList, extractor);
// Now, if you want to change your extraction logic, do
doStuff(dataList, whatever -> "Return a constant title");
}
public static void doStuff(final List<DataObj> dataList, final Function<List<DataObj, String> titleExtractor) {
// Do stuff here
}

Rewrite existing code with Java 8 Lambda

I have one object, two conditions and need to convert it into a list.
SelectItem has getLabel and getValue
Casting problem
Expect Result -> List of Employees
Object refer to class (Example: Employees.class)
The code looks like:
public static final List<Employees> onFilterObjectFromSelectItems(final String query,final List<SelectItem> selectItemList) {
final List <Employees>result = new ArrayList<>();
for (SelectItem sl : selectItemList) {
Employees x = (Employees) sl.getValue();
if (x.getCode.contains(query) || x.getName.contains(query)) {
result.add(x);
}
}
return result;
}
Try following code.
List<Employees> EmployeeList = selectItemList.stream()
.filter(x-> x.getCode().contains(query) || x.getName().contains(query))
.map(e->(Employees)e.getValue())
.collect(Collectors.toList());

spring-data query method with a list as arguments

We have to query data from database where we need to find entities matching a list of key value pairs. We thought it would be a nice idea to use Spring Data JPA as we need also pagination.
The tables we created are like below:
terminal(ID,NUMBER,NAME);
terminal_properties(ID,KEY,VALUE,TERMINAL_FK);
Is it possible to define a query method to fetch all terminals with properties containing given key/value pairs ?
Something like this: List<Terminal> findByPropertiesKeyAndValue(List<Property>);
I didn't execute the code, but given the correct import statements, this at least compiles. Depending on your entity definition, some properties may need to be adapted and in any case, you should get an idea of how to approach this.
My criteria query is based on the following SQL:
SELECT * FROM TERMINAL
WHERE ID IN (
SELECT TERMINAL_FK FROM TERMINAL_PROPERTIES
WHERE (KEY = 'key1' AND VALUE = 'value1')
OR (KEY = 'key2' AND VALUE = 'value2')
...
GROUP BY TERMINAL_FK
HAVING COUNT(*) = 42
)
Where you list each name/value pair and 42 simply represents the number of name/value pairs.
So I assume you defined a repository like this:
public interface TerminalRepository extends CrudRepository<Terminal, Long>, JpaSpecificationExecutor {
}
It's important to extend JpaSpecificationExecutor in order to make use of the criteria API.
Then you can build a criteria query like this:
public class TerminalService {
private static Specification<Terminal> hasProperties(final Map<String, String> properties) {
return new Specification<Terminal>() {
#Override
public Predicate toPredicate(Root<Terminal> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
// SELECT TERMINAL_FK FROM TERMINAL_PROPERTIES
Subquery<TerminalProperty> subQuery = query.subquery(TerminalProperty.class);
Root propertyRoot = subQuery.from(TerminalProperty.class);
subQuery.select(propertyRoot.get("terminal.id"));
Predicate whereClause = null;
for (Map.Entry<String, String> entry : properties.entrySet()) {
// (KEY = 'key1' AND VALUE = 'value1')
Predicate predicate = builder.and(builder.equal(propertyRoot.get("key"),
entry.getKey()), builder.equal(propertyRoot.get("value"), entry.getValue()));
if (whereClause == null) {
whereClause = predicate;
} else {
// (...) OR (...)
whereClause = builder.or(whereClause, predicate);
}
}
subQuery.where(whereClause);
// GROUP BY TERMINAL_FK
subQuery.groupBy(propertyRoot.get("terminal.id"));
// HAVING COUNT(*) = 42
subQuery.having(builder.equal(builder.count(propertyRoot), properties.size()));
// WHERE ID IN (...)
return query.where(builder.in(root.get("id")).value(subQuery)).getRestriction();
}
};
}
#Autowired
private TerminalRepository terminalRepository;
public Iterable<Terminal> findTerminalsWith(Map<String, String> properties) {
// this works only because our repository implements JpaSpecificationExecutor
return terminalRepository.findAll(hasProperties(properties));
}
}
You can obviously replace Map<String, String> with Iterable<TerminalProperty>, although that would feel odd because they seem to be bound to a specific Terminal.

Categories

Resources