Filter with Spring-Data and QueryDSL on nullable reference attribute - filter

I have following issue. I am using Jquery Datatable serverside and I am now implementing the search box. But I have an issue there in special case, when a dataset has an attribute, what is "null". So the dataset will not be found although it should found cos it matches on one attibute.
The situtation in beginning is like follows. You see there is a dataset with apprentice Fabio Bartels, who has not Fachrichtung. And a dataset with Viktoria.
Now when I search for Viktoria, the filter works as expected:
When I search for Fabio, then Dataset is not found:
=====
The problem I have is, that I don't know how to handle the filter, that a attribute will only be validated against the search string when the attribute is not null.
=====
Serverside Java Classes see like follows:
QueryClass:
class ContractSearchQuery {
private static QContract contract = QContract.contract;
static BooleanExpression getPredicate(final ContractSearch filter) {
BooleanExpression predicate;
if (filter == null || filter.isEmpty()) {
// SHOW ALL PREDICATE ...
} else {
final String search = filter.getSearch();
final List<BooleanExpression> expressions = new ArrayList<BooleanExpression>();
// EXPRESSIONS CURRENTLY ONLY ON AUSZUBILDENDER AND FACHRICHTUNG
// FOR SHOWCASE
expressions.add(containsApprenticeName(search)); // AUSZUBILDENDER
expressions.add(containsSpecialisation(search)); // FACHRICHTUNG
BooleanExpression expression = expressions.get(INTEGER_ZERO);
for (int i = 1; i < expressions.size(); i++) {
expression = expression.or(expressions.get(i));
}
predicate = expression;
}
return predicate;
}
private static BooleanExpression containsApprenticeName(final String search) {
final BooleanExpression expLastName = contract.apprentice.lastName.containsIgnoreCase(search);
final BooleanExpression expFirstName = contract.apprentice.firstName.containsIgnoreCase(search);
return expLastName.or(expFirstName);
}
private static BooleanExpression containsSpecialisation(final String search) {
return contract.companyOccupationCombination.occupationCombination.specialisation.name.containsIgnoreCase(search);
}
}
Spring-Data-Repository Call:
final PageRequest pageRequest = new PageRequest(firstResult / maxResults, maxResults, orderSort);
final Page<Contract> page = contractRepository.findAll(predicate, pageRequest);
return page.getContent();
=======
Database:
By the way I recognized when I do direct request against my db with joining Specialisation Table, then I only get Fabio as record, when not joining Specialisation, I get all three persons. Maybe somethings to do with my issue:
select a.first_name, a.last_name from contract c
join company_occupation_combination coc on c.company_occupation_combination = coc.id
join occupation_combination oc on coc.occupation_combination = oc.id
join apprentice a on c.apprentice = a.id
Result:
"Fabio";"Bartels"
"Viktoria";"Kruczek"
"Lina";"Ehleiter"
With Join:
select a.first_name, a.last_name from contract c
join company_occupation_combination coc on c.company_occupation_combination = coc.id
join occupation_combination oc on coc.occupation_combination = oc.id
join specialisation s on oc.specialisation = s.id
join apprentice a on c.apprentice = a.id
Result: "Viktoria";"Kruczek"
====
EDIT:
Okay, on db site I found out (with Hibernate and JPA I start forgetting SQL-Basices ;-)), that I need a left join for the nullable relation, so my query should result to an sql like:
select a.first_name, a.last_name from contract c
join company_occupation_combination coc on c.company_occupation_combination = coc.id
join occupation_combination oc on coc.occupation_combination = oc.id
left join specialisation s on oc.specialisation = s.id
join apprentice a on c.apprentice = a.id
====
So my question is, how can I manage left Join when I have a Query-Class using QueryDSL and Spring-Data-Repository like mentioned above?

If you really need left join, you can't achieve that via predicate (instead it is possible via sub-query)
To be able to do left-join, you will need JPAQuery.
Assuming you have already configured repositories, and able to use EntitiManager, implement ContractRepositoryCustom , so that in your implementation you can have
#PersistenceContext(unitName = "unitname")
protected EntityManager entityManager;
public List<Contract> findAllContracts() {
return new JPAQuery(entityManager, HQLTemplates.DEFAULT)
.from(QContract.contract)
.join(QContract.contract.companyOccupationCombination, QCompanyOccupationCombination.companyOccupationCombination)
.join(QCompanyOccupationCombination.companyOccupationCombination.occupationCombination, QOccupationCombination.occupationCombination)
.leftJoin(QOccupationCombination.occupationCombination.specialization, QSpecialization.specialization)
.join(QSpecialization.specialization.apprentice, QApprentice.apprentice)
.list(QContract.contract);
}
And for pagination you always apply limit(maxResults) and offset(firstResult)

I really like working with Spring-Data and Query-DSL, cos it makes my code really tidy. But I am really suprised, that for the case of nullable references there seems no solution. Sure you can use another solution like #vtorosyan mentioned and thank you again for that solution, but when you project is builded up with combination of QueryDSL and Spring-Data, you really don't want to bring a second style in your application.
But I needed a solution, so I did now the follows.
The point of the issue was, that when I used data from a nullable entity, a join has been executed what hided the datasets, who had a null reference on it, see examples above. What I now did and I hope I will not get another issue then with that solution on later time of that project. I did the null references to not null and defined something like null-record.
Example I added a record for specialisation like
ID NAME
0 Keine
Instead of null I now use that record what has until now following effects:
First my table shows now "Keine" (engl. "None") for all attributes what are not set. It looks more consistent when having a textoutput then empty string.
Now I can explicitly search for "Keine", when I am interested for data records what have no specialisation set.
And my searchbox works as expected for records, which have no speciafication set. (THAT WAS MY ISSUE FROM BEGINNING WHAT I WANTED TO SOLVE):
Additional to that searchbox I use a modal dialog for filtering. Now I can explicitly filter "Keine" for "nullable" records:
If you think there is another good solution for that issue without rebuild code using Spring-Data and QueryDSL konsequently, don't hesitate to post ;-)

Related

Criteria API with ODER BY CASE expression throws SQLException: ORA-12704 "character set mismatch"

I am using criteria API to create my query. Because of special sorting algorithm I use an "order by case" expression. My Unit-Tests using in memory H2 DB and are working. In the development stage we are using Oracle DB and there I get an "SQLException: ORA-12704" when executing the query.
Assume my root entity 'Foo' has a Set of 'Bar's. Bar has an attribute 'myOrderByColumn'
public class Bar {
...
#NotBlank
#javax.validation.constraints.Size(max = 255)
#Column(name = "MYORDERBYCOL")
private java.lang.String myOrderByColumn;
...
}
Here is the code which produces the exception. It creates the Order object later used in CriteriaQuery.orderBy(..)
private Order buildOrderBy(final CriteriaBuilder cb,
final Root<Foo> rootEntity,
final List<String> somehowSpecialOrderedList) {
final Expression<String> orderByColumn =
rootEntity.join(Foo_.bars, JoinType.LEFT).get(Bars.myOrderByColumn);
CriteriaBuilder.SimpleCase<String, Integer> caseRoot = cb.selectCase(orderByColumn);
IntStream.range(0, somehowSpecialOrderedList.size())
.forEach(i -> caseRoot.when(somehowSpecialOrderedList.get(i), i));
final Expression<Integer> selectCase = caseRoot.otherwise(Integer.MAX_VALUE);
return cb.asc(selectCase);
}
I took a look into the Oracle DB. The type of the column 'myOrderByColumn' ist NVARCHAR2(255).
I guess the problem here ist that the "when" part in the SQL query must match with the type of the 'MYORDERBYCOL' database column, which is NVARCHAR2. In Java I use Strings. Probably Hibernate is not casting this correctly!?
I can produce the database ORA-12704 error by
SELECT FOO.id
FROM FOO
LEFT OUTER JOIN BAR ON FOO.id = BAR.fk_id
ORDER BY
CASE FOO.myorderbycol
WHEN '20' THEN 1
ELSE 2
END ASC;
This SQL works
SELECT FOO.id
FROM FOO
LEFT OUTER JOIN BAR ON FOO.id = BAR.fk_id
ORDER BY
CASE FOO.myorderbycol
WHEN cast('20' as NVARCHAR2(255))
THEN 1ELSE 2
END ASC;
How do I have to adjust my oder by case expression with criteria API so that the query is working with any database? (must later work with at least H2, Oracle, MS SQL, PostgreSQL)
Looks like an Oracle issue to me. Which version are you using? You can try a different approach which might work.
CriteriaBuilder.SearchedCase<Integer> caseRoot = cb.selectCase();
IntStream.range(0, somehowSpecialOrderedList.size())
.forEach(i -> caseRoot.when(cb.equal(orderByColumn, somehowSpecialOrderedList.get(i)), i));
final Expression<Integer> selectCase = caseRoot.otherwise(Integer.MAX_VALUE);

How to INNER JOIN a jsonb column with CriteriaBuilder

I've been researching a while about how can I implement an Inner Join using CriteriaBuilder, but the thing is that one of the arguments which both tables have in commons is located inside a jsonb column, so the question is:
How can I INNER JOIN 2 tables by an argument that is located inside a jsonb column called "data" with criteria?
I'll drop down the example code of what I currently have.
public final CriteriaQuery<TutenBookingMacarena> createRatedBookings(
MacarenaBookingSearchFilter filters,
Integer page,
Integer pageSize,
Security security
) {
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
final CriteriaQuery<TutenBookingMacarena> criteria = builder.createQuery(TutenBookingMacarena.class);
final Root<TutenBookingMacarena> root_Booking = criteria.from(TutenBookingMacarena.class);
Join<TutenBookingMacarena, TutenCaseMacarena> rootJoin = root_Booking.join(String.valueOf(
builder.function("jsonb_extract_path_text",
String.class,
root_Booking.<String>get("data"),
builder.literal("caseId")
)
));
...
}
But when executing those lines of code I get an error message that says the named attribute can not be found. Any idea what's wrong here?

Generic criteriaUpdate set boolean = !boolean

I want to use criteriaUpdate to create an update query like this:
UPDATE <SOME TABLE>
SET SELECTED = !SELECTED
WHERE
[DYNAMIC QUERY HERE]
The closest I could get was with the code:
public <T> Query createRevertSelectionQuery(Class<T> clazz, EntityManager em, Specification<T> s) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaUpdate<T> criteriaUpdate = cb.createCriteriaUpdate(clazz);
Root<T> root = criteriaUpdate.from(clazz);
Predicate p = cb.and(new Predicate[] {s.toPredicate(root, null, cb)});
Expression<Boolean> e =cb.not((root.get("selected").as(Boolean.class)));
Path<Boolean> selected = root.get("selected");
criteriaUpdate.set(selected, e);
criteriaUpdate.where(p);
Query q = em.createQuery(criteriaUpdate);
return q;
}
but it fails because I get the following query:
update com.redknee.suspense.mgt.model.Moc as generatedAlias0
set generatedAlias0.selected = generatedAlias0.selected <> true
where
[dynamic query]
giving me the error
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: <> near line 1, column 118
Anyone can help please?
I am not sure if this is a bug or if it is just not meant to be used this way .
In where-clause NOT and any other operands work like a charm. But, no matter what you try Hibernate query builder seems always to optimize those parenthesis away (in my opinion it might still be a good habit to always use parenthesis but its only an opinion).
One way to force parenthesis is to use JPA Subquery. See below example. Note that i have slightly altered the JPA object names by my own taste and not included the Specification because it is not relevant to this solution:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaUpdate<T> update = cb.createCriteriaUpdate(clazz);
Root<T> from = update.from(clazz);
Path<Boolean> selected = from.get("selected");
// Subquery just "joins back" to the same row and
// returns a negated boolean value of "selected" from the original row
Subquery<Boolean> subSelect = update.subquery(Boolean.class);
Root<T> subFrom = subSelect.from(clazz);
subSelect.select(cb.not(selected));
subSelect.where(cb.equal(from.get("id"), subFrom.get("id")));
update.set(selected, subSelect);

Dynamic Linq on DataTable error: no Field or Property in DataRow, c#

I have some errors using Linq on DataTable and I couldn't figure it out how to solve it. I have to admit that i am pretty new to Linq and I searched the forum and Internet and couldn't figure it out. hope you can help.
I have a DataTable called campaign with three columns: ID (int), Product (string), Channel (string). The DataTable is already filled with data. I am trying to select a subset of the campaign records which satisfied the conditions selected by the end user. For example, the user want to list only if the Product is either 'EWH' or 'HEC'. The selection criteria is dynaically determined by the end user.
I have the following C# code:
private void btnClick()
{
IEnumerable<DataRow> query =
from zz in campaign.AsEnumerable()
orderby zz.Field<string>("ID")
select zz;
string whereClause = "zz.Field<string>(\"Product\") in ('EWH','HEC')";
query = query.Where(whereClause);
DataTable sublist = query.CopyToDataTable<DataRow>();
}
But it gives me an error on line: query = query.Where(whereClause), saying
No property or field 'zz' exists in type 'DataRow'".
If I changed to:
string whereClause = "Product in ('EWH','HEC')"; it will say:
No property or field 'Product' exists in type 'DataRow'
Can anyone help me on how to solve this problem? I feel it could be a pretty simple syntax change, but I just don't know at this time.
First, this line has an error
orderby zz.Field<string>("ID")
because as you said, your ID column is of type int.
Second, you need to learn LINQ query syntax. Forget about strings, the same way you used from, orderby, select in the query, you can also use where and many other operators. Also you'll need to learn the equivalent LINQ constructs for SQL-ish things, like for instance IN (...) is mapped to Enumerable.Contains etc.
With all that being said, here is your query
var productFilter = new[] { "EWH", "HEC" };
var query =
from zz in campaign.AsEnumerable()
where productFilter.Contains(zz.Field<string>("Product"))
orderby zz.Field<int>("ID")
select zz;
Update As per your comment, if you want to make this dynamic, then you need to switch to lambda syntax. Multiple and criteria can be composed by chaining multiple Where clauses like this
List<string> productFilter = ...; // coming from outside
List<string> channelFilter = ...; // coming from outside
var query = campaign.AsEnumerable();
// Apply filters if needed
if (productFilter != null && productFilter.Count > 0)
query = query.Where(zz => productFilter.Contains(zz.Field<string>("Product")));
if (channelFilter != null && channelFilter.Count > 0)
query = query.Where(zz => channelFilter.Contains(zz.Field<string>("Channel")));
// Once finished with filtering, do the ordering
query = query.OrderBy(zz => zz.Field<int>("ID"));

spring-data subquery within a Specification

Spring-data, Oliver Gierke's excellent library, has something called a Specification (org.springframework.data.jpa.domain.Specification). With it you can generate several predicates to narrow your criteria for searching.
Can someone provide an example of using a Subquery from within a Specification?
I have an object graph and the search criteria can get pretty hairy. I would like to use a Specification to help with the narrowing of the search, but I need to use a Subquery to see if some of the sub-elements (within a collection) in the object graph meet the needs of my search.
Thanks in advance.
String projectName = "project1";
List<Employee> result = employeeRepository.findAll(
new Specification<Employee>() {
#Override
public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Subquery<Employee> sq = query.subquery(Employee.class);
Root<Project> project = sq.from(Project.class);
Join<Project, Employee> sqEmp = project.join("employees");
sq.select(sqEmp).where(cb.equal(project.get("name"),
cb.parameter(String.class, projectName)));
return cb.in(root).value(sq);
}
}
);
is the equivalent of the following jpql query:
SELECT e FROM Employee e WHERE e IN (
SELECT emp FROM Project p JOIN p.employees emp WHERE p.name = :projectName
)

Resources