Java Criteria API: Select single column from one-to-many relationship table - java

I am trying to select a single column from a related table. I have a table (Item) with many Values. I would like to select Value.valueString.
Basically, the query is supposed to pass in a bunch of values and pull any ValueFields that contain those values. The SQL might look something like this:
select ItemValues.valueString from ItemEntity
join StockItem on ItemEntity.stockItemId = StockItem.id
join ItemValues on ItemEntity.id = ItemValues.itemId
where StockItem.vendor = vendorId
AND (ItemValues.valueString like '%test%' OR ItemValues.valueString like '%test2%'...);
Here is my code:
final CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
final CriteriaQuery<String> query = builder.createQuery(String.class);
final Root<ItemEntity> root = query.from(ItemEntity.class);
query.select(root.join("ItemValues").<String>get("ValueString"));
final List<Predicate> filters = new LinkedList<Predicate>();
filters.add(builder.equal(root.join("StockItem").get("id"), vendorNumber));
final List<Predicate> filterNamesCriteria = new LinkedList<Predicate>();
if (filenames.length > 0) {
for (String fileName : filenames) {
filterNamesCriteria.add(builder.like(root.join("ItemValues").<String>get("ValueString"), fileName));
}
filters.add(builder.or(filterNamesCriteria.toArray(new Predicate[0])));
}
query.where(filters.toArray(new Predicate[0]));
final TypedQuery<String> resolvedQuery = this.entityManager.createQuery(query);
return resolvedQuery.getResultList();
I want the result to return a List of Strings (valueString column), but it's not returning anything.
Am I doing something wrong? When I say "builder.createQuery(String.class)", is that correct?

I found the problem:
filters.add(builder.equal(root.join("StockItem").get("id"), vendorNumber));
I was joining based on the StockItem id and not the StockItem.itemNumber
I used two queries to solve the issue of joining the Itemvalues map (it was returning 32,000+ results)

Related

JPA Criteria builder for dynamic query

I have to write a method where I pass it some arguments and depending on which arguments are empty it modifies the query I need to execute at the end.
E.g. if I want to select from a table Customer that has an ID, name, surname and address the starting query would be SELECT * FROM Customer WHERE ID=myId AND name=myName AND surname=mySurname (and now we check if the address I sent is empty, if it is NOT, add it to the query).
I know how to do this in C#, by simply doing a starting string and then just adding to it and using ExecuteStoreQuery directly, but how would this look in Java? Is Criteria Builder a viable tool for this?
Thank you so much!
Using a CriteriaQuery, it is possible to do something like that (using Static JPA Metamodel Classes):
public List<Customer> findCustomer(Long myId, String myName, String mySurName, String myAddress) {
CriteriaBuilder cb = enitityManager.getCriteriaBuilder();
CriteriaQuery<Customer> query = cb.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get(Customer_.ID), myId));
predicates.add(cb.equal(root.get(Customer_.NAME), myName));
predicates.add(cb.equal(root.get(Customer_.SURNAME), mySurName));
if(address != null) {
predicates.add(cb.equal(root.get(Customer_.ADDRESS), myAddress));
}
query.select(root).where(cb.and(predicates.toArray(new Predicate[0])));
return entityManager.createQuery(query).getResultList();
}
In this case, the address will only be checked if it is not null.
Another example without using Static JPA Metamodel (not 100% sure about this):
public List<Customer> findCustomer(Long myId, String myName, String mySurName, String myAddress) {
CriteriaBuilder cb = enitityManager.getCriteriaBuilder();
CriteriaQuery<Customer> query = cb.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get("ID"), myId));
predicates.add(cb.equal(root.get("name"), myName));
predicates.add(cb.equal(root.get("surename"), mySurName));
if(address != null) {
predicates.add(cb.equal(root.get("address"), myAddress));
}
query.select(root).where(cb.and(predicates.toArray(new Predicate[0])));
return entityManager.createQuery(query).getResultList();
}

Creating Criteria Query for combine column IN query

I am stuck with this problem
I want to create Criteria Query which translates to
select * from table
where (col1,col2) in ((val1,val2),(val2,val3),....)
I understood how to create it for IN clause on single column
But I want to have IN clause on combination of two columns where values in IN clause is list of tuple of values for these two columns.
Kindly suggest.
EntityManager entityManager;
public List<YourEntity> getEntitiesByParams(Map<Field1Class, Field2Class> params) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<YourEntity> query = cb.createQuery(YourEntity.class);
Root<YourEntity> root = query.from(YourEntity.class);
final List<Predicate> predicates = new ArrayList<>();
for(Entry<Field1Class, Field2Class> entry : params.entrySet()) {
Predicate predicate1 = cb.equal(root.get("field1"), params.getKey());
Predicate predicate2 = cb.equal(root.get("field2"), params.getValue());
predicates.add(
cb.and(predicate1, predicate2)
);
}
query.select(root)
.where(
cb.or(predicates.toArray(new Predicate[predicates.size()]))
);
return entityManager.createQuery(query).getResultList();
}

Hibernate: Query to select values from multiple tables using CriteriaQuery

Let's say, I have a query like
Select a.valA, b.valB
from tableA a join tableB b on a.joinCol = b.joinCol
where a.someCol = 1.
I want to execute it using Hibernate (and Spring Data) in one query to the database. I know, I can write just
Query query = em.createQuery(...);
List<Object[]> resultRows = (List<Object[]>)query.getResultList();
But my question would be - is it possible to do it in a typesafe way, using CriteriaQuery for example? The difficulty is, that, as you see, I need to select values from different tables. Is there some way to do this?
Simple example where an Employee has many to many relation to several jobs that he may have :
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createTupleQuery();
Root<TableA> root = criteria.from(TableA.class);
Path<Long> qId = root.get("id");
Path<String> qTitle = root.get("title");
Join<TableA, TableB> tableTwo = root.join("joinColmn", JoinType.INNER);
criteria.multiselect(qId, qTitle, tableTwo);
List<Tuple> tuples = session.createQuery(criteria).getResultList();
for (Tuple tuple : tuples)
{
Long id = tuple.get(qId);
String title = tuple.get(qTitle);
TableB tableB= tuple.get(tableTwo);
}
but saw that there is an alternate answer here :
JPA Criteria API - How to add JOIN clause (as general sentence as possible)

Count distinct with multiple columns JPA2 specifications

I'm trying to find some results from my Oracle database and because of the data model, I have repeated data which I don't want to be shown.
In order to do this, I thought using DISTINCT in my CriteriaQuery should do the trick. But here I encountered the problem:
The distinct has to be made on 2 columns and this makes the code to throw the following exception: "ORA-00909: invalid number of arguments".
After some google search, found that using this things: || to separate the columns works, but I don't have a single clue to do this either.
So:
The correct way to distinct my data is like this:
SELECT DISTINCT column1, column2 FROM ...
But Hibernate is building it like this:
SELECT DISTINCT COUNT( DISTINCT column1, column2) FROM
The latter query is the one that throws the exception. Is there a way to tell Hibernate to do the first of the two queries?
Thanks in advance.
EDIT
The code involved is this:
return new Specification<ProfilingInstructionAccessEntity>() {
#Override
public Predicate toPredicate(Root<ProfilingInstructionAccessEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
final List<Predicate> predicates = new ArrayList<>();
List<FilterDTO> activeFilters = filtersService.findAllActive();
filters.forEach((k, v) -> {
if (!StringUtils.isBlank(v)) {
SpecificationHelperEnum helperEnum = SpecificationHelperEnum.getByKey(k);
SpecFilter filter = (SpecFilter) appContext.getBean(helperEnum.getFilter());
// query.select(root.get("concated")).distinct(true); <-- This line is what I tried. Also tried this: query.distinct(true);
Predicate predicate = filter.createSmartPredicate(root, cb, v, helperEnum.getFilterId(), activeFilters);
predicates.add(predicate);
}
});
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
#Override
public Predicate createSmartPredicate(Root<ProfilingInstructionAccessEntity> root, CriteriaBuilder cb, String value, Integer filterId, List<FilterDTO> activeFilters) {
List<AssetDTO> assets = service.findBySpec("origin", value);
List<Predicate> assetPredicates = new ArrayList<>();
Join<Object, Object> aux = root.join(SearchPathEnum.ACCESS.getPath()).join(SearchPathEnum.ACCESS_FILTERS.getPath());
for (AssetDTO assetDTO : assets) {
List<Predicate> filterPredicates = new ArrayList<>();
for (FilterDTO filterDTO : activeFilters) {
filterPredicates.add(buildPredicate(assetDTO, filterDTO.getMappedValue(), aux, cb));
}
assetPredicates.add(cb.or(filterPredicates.toArray(new Predicate[filterPredicates.size()])));
}
return cb.or(assetPredicates.toArray(new Predicate[assetPredicates.size()]));
}
private Predicate buildPredicate(AssetDTO assetDTO, String mappedValue, Join<Object, Object> aux, CriteriaBuilder cb) {
Predicate filterCode = cb.equal(aux.get(SearchPathEnum.FILTER.getPath()).get(SearchPathEnum.MAPPED_VALUE.getPath()), mappedValue);
String value = (String) ReflectionComponent.invokeByReflection(assetDTO, "get" + StringUtils.capitalize(mappedValue), null);
Predicate filterValue = cb.like(aux.get(SearchPathEnum.FILTER_VALUE.getPath()), "%" + value.trim() + "%");
return cb.and(filterCode, filterValue);
}
It's a bit complex but in the end it is something like a 'smart' search. I have like 20 filters and any single one of them trigger the search by the others.
If more information is needed please feel free to ask for it. Thanks!
count(distinct column1, column2) is invalid.
That would, actually, be
select count(*)
from (select distinct column1, column2 from some_table);
I don't know hibernate, but - maybe code posted above rings your bell :)
Register new function in Dialect:
registerFunction("count_distinct", new SQLFunctionTemplate(IntegerType.INSTANCE, "count(distinct ?1)"));
And concatenate:
select count_distinct(column1 || column2) from some_table

JPA Criteria Builder order by a sum column

I have the following SQL query.
select colA, sum(colB) as colB from tableA group by colA order by colB desc;
I have used jpa criteria builder to build the query dynamically.
List<String> selectCols = new ArrayList<>();
selectCols.add("colA");
List<String> sumColumns = new ArrayList<>();
sumColumns.add("colB");
List<String> groupByColumns = new ArrayList<>();
groupByColumns.add("colA");
List<String> orderingColumns = new ArrayList<>();
orderingColumns.add("colB");
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cQuery = builder.createQuery(Tuple.class);
Root<T> root = cQuery.from(getDomainClass());
List<Selection<?>> selections = new ArrayList<>();
for (String column : selectCols) {
selections.add(root.get(column).alias(column));
}
if (sumColumns != null) {
for (String sumColumn : sumColumns) {
selections.add(builder.sum(root.get(sumColumn)).alias(sumColumn));
}
}
cQuery.multiselect(selections);
List<Expression<?>> groupByExpressions = new LinkedList<>();
for (String column : groupByColumns) {
groupByExpressions.add(root.get(column));
}
cQuery.groupBy(groupByExpressions);
List<Order> orders = new ArrayList<>();
for (String orderCol : orderingColumns) {
orders.add(builder.desc(root.get(orderCol)));
}
cQuery.orderBy(orders);
entityManager.createQuery(cQuery).getResultList();
I debugged and checked the exact sql statement that is getting executed.
select colA as col_1_0, sum(colB) as col_2_0 from tableA group by colA order by colB desc;
The resultant sql statement that is getting executed doesn't have the alias for colB as colB. I am using MySql. Surprisingly, this statement gets executed and I get proper results when sql mode is set to NO_ENGINE_SUBSTITUTION. But when the sql mode is ONLY_FULL_GROUP_BY, the database throws an error saying
Expression #1 of ORDER BY clause is not in GROUP BY clause and
contains nonaggregated column 'colB' which is not functionally
dependent on columns in GROUP BY clause; this is incompatible with
sql_mode=only_full_group_by
I understand that in this mode, sql validates the query and my query fails. How do I create an alias with jpa criteria builder so that the actual statement getting executed contains a proper alias for the sum column?
Note: I use spring-data-jpa and this is a custom implementation of repository. I can manually write the query using #Query annotation but the nature of the queries are dynamic on group by, order by, where clauses and the select columns. Hence the need for a dynamic criteria builder implementation.
Try sorting over the aggregate. In my experience, column aliases usually can't be referenced in the query itself.
orders.add(builder.desc(builder.sum(sumColumn)));
i think so you have bug in orderingColumns, you schould add colA instead colB
or instead
for (String orderCol : orderingColumns) {
orders.add(builder.desc(root.get(orderCol)));
}
you can use
orders.add(builder.desc(root.get("colA")));

Categories

Resources