QueryDSL filter all elements in collection - java

#Getter
#Setter
#ToString
#ApiModelProperty("contains ignore case")
private String name;
#ApiModelProperty("equals")
private String department;
#ApiModelProperty("in equals")
private List<String> position;
#ApiModelProperty("greater or equals")
private Integer salaryFrom;
#ApiModelProperty("lower or equals")
private Integer salaryTo;
#ApiModelProperty("contains ignore case")
private String email;
#ApiModelProperty("at least one with priority higher")
private Integer oneHigher;
#ApiModelProperty("all priorities higher")
private Integer allHigher;
BooleanBuilder conditions = new BooleanBuilder();
ofNullable(salaryFrom).map(employee.salary::goe).ifPresent(conditions::and);
ofNullable(salaryTo).map(employee.salary::loe).ifPresent(conditions::and);
ofNullable(email).map(employee.email::containsIgnoreCase).ifPresent(conditions::and);
ofNullable(oneHigher).map(employee.tasks.any().priority::goe).ifPresent(conditions::and);
return conditions;
I have Employee entity that has set of Task, and I want to be able to query only for employees that have all tasks greater than certain priority, I was able to do that but when at least one task has priority greater than some value using any(), but I don't see a way to do that for all tasks because I don't see any way to iterate through tasks in employee.tasks

I don't know how to model this with QueryDSL, but usually in HQL this can be modeled by using an exists subquery i.e. something like this:
from Employee e
where not exists (
select 1
from e.tasks t
where t.priority < :priority
)
another way to formulate this is through using quantifiers
from Employee e
where :priority > all (
select t.priority
from e.tasks t
)

Related

Select only specific columns from joined tables (Many-to-Many) in Spring Data JPA

The purpose is to select columns from joined tables (Many-to-Many).
The problem i have is to select two columns from a joined Many-to-Many table.
I'am using Springboot 2.3 and Spring data Jpa.
I have this data model, and what i want to fetch are the blue boxed fields
So the native query could look like this (if i am right ...)
SELECT bg.id, bg.name, p.name, c.name, c.short_desc FROM boardgame as bg
JOIN boardgame_category bgc on bg.id = bgc.fk_game
JOIN publisher p on bg.fk_publisher = p.id
JOIN category c on bgc.fk_category = c.id
WHERE bg.id = :id
I first tried to work with dto in JPQL statment
public class BoardgameDto {
private long id;
private String name;
private String publisherName;
private Set<CatregoryDto> categoryDto;
// setter, getter etc...
}
public class CategoryDto {
private String name;
private String shortDesc;
// setter, getter etc...
}
The JQPL query could look like this , but it doesn't work (IDE shows errors on CategoryDto)
/* THIS DOESN'T WORK */
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name,
new org.moto.tryingstuff.dto.CategoryDto(c.name, c.short_desc)) FROM Boardgame as bg, Publisher as p, Category as c
Well, I think the problem I have with this way of doing is that the dto's contructor can't receive a collection as written here, and i think neither another contructor in parameter.
Then i started looking at Criteria Queries, especialy multiselect, Tuple, Dto, but it look like i had same kind of problems so i didn't dive deeper into it.
Finally i used a JpaRepository and it's findById() method like this
public interface BoardgameRepository extends JpaRepository<Boardgame, Long> {
}
// In a test or service method
Boardgame game = repository.findById(long id);
Then i filter the fields i need to keep through mappings in Service or Controller layer. So the front only received need datas.
But it feel a bit overkill,
Am I missing something, any part of the framework that would allow me to select only specific columns?
As you wrote, you can't use a collection as the parameter of a constructor expression. That's because the expression gets applied to each record in the result set. These records are a flat data structure. They don't contain any collections. Your database returns a new record for each element in that collection instead.
But your constructor expression fails for a different reason. You're trying to combine 2 constructor expressions, and that's not supported. You need to remove the 2nd expression and perform that operation within the constructor of your DTO.
So, your query should look like this:
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name, c.name, c.short_desc) FROM Boardgame as bg <Your JOIN CLAUSES HERE>
And the constructor of your BoardgameDto like this:
public class BoardgameDto {
public BoardgameDto(Long id, String gameName, String publisherName, String categoryName, String description) {
this.id = id;
this.name = gameName;
this.publisherName = publisherName;
this.category = new Category(categoryName, description);
}
...
}

How to sort on a field in indexedEmbedded

I have an indexedEmbedded object with #OneToMany relation, inside a class, and want to sort with a field containded in that object.
When i call my method for search i got this exception:
"Unexpected docvalues type NONE for field 'employees.id_forSort' (expected=NUMERIC). Use UninvertingReader or index with docvalues"
Thanks in advance!
Here is my code:
public Company {
....
#IndexedEmbedded(prefix = "students.", includeEmbeddedObjectId = true)
#OneToMany(mappedBy = "company")
private Set<Employee> employees= new HashSet<>();
}
public Employee{
....
#SortableField(forField = "id_forSort")
#Field(name = "id_forSort", analyze = Analyze.NO)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
public class CompanySearchRepository{
....
public List<Company> searchCompany(
DataTablePagination pagination, Long id, SearchCriteria searchCriteria) {
FilterField[] filterFields = validateFilterFields(searchCriteria.getFilterFields());
// Sorting
String sortField = "employees.id";
Sort sort = getSort(sortField, pagination.getSortDirection());
Query query;
if (filterFields.length > 0) {
query = getFilterableColumnsQuery(filterFields);
} else {
// global search
query = getGlobalSearchQuery(searchableFields, searchCriteria.getSearchTerm());
}
FullTextQuery fullTextQuery = getFullTextQuery(query);
initPagination(
fullTextQuery,
sort,
pagination.getPage(),
pagination.getPageSize());
List<Company> data = fullTextQuery.getResultList();
}
Sort getSort(String sortField, SortDirection sortDirection) {
SortFieldContext sortFieldContext =
getQueryBuilder().sort().byField(sortField.concat("_forSort"));
return sortDirection.equals(SortDirection.ASC)
? sortFieldContext.asc().createSort()
: sortFieldContext.desc().createSort();
}
}
You named your sortable field employees.id_forSort, but when searching, you're using another field for sorts: employees.id. Use the field that is intended for sorts. Replace String sortField = "employees.id"; with String sortField = "employees.id_forSort"; My bad, I didn't see the weird code that adds a suffix to the field name in the getSort method. Then the message is strange. Would your index be empty, by any chance?
Sorts on multi-valued fields are not supported. You will likely get different results from one execution to the other, since the search engine has to select a value to use for sorts, and which value is selected is undefined.
Regardless of the technical aspect, I'm not sure what you're trying to achieve. You're getting Company instances as a result, and want them to be sorted by Employee ID, of which there are many for each Company. What does it mean? You want the company with the oldest employee first? Something else? If you're just trying to stabilize the order of hits, I'd recommend using the company ID instead.

Stream filter criteria for list of params

today I met an interesting problem with stream API filter.
Namely, the problem is as follows: We have a list of parameters to filter as a function argument. Using the streams we want to filter all the records and return the ones that match the parameters we gave as the function argument. For example [Car, 2012, 50000, Audi]. I realize that in this situation I could use Criteria API or Full Search Text but I would like to do it without additional API only using Java streams.
Customer.class
public class Customer {
#Id
#GeneratedValue
private Long id;
private String name;
private Integer age;
#OneToMany(mappedBy = "customer")
List<CustomerOrder> customerOrders;
}
Product.class
public class Product {
#Id
#GeneratedValue
private Long id;
private String name;
private BigDecimal price;
#ToString.Exclude
#EqualsAndHashCode.Exclude
#OneToMany(mappedBy = "product")
List<CustomerOrder> customerOrders;
}
CustomerOrder.class
public class CustomerOrder {
#Id
#GeneratedValue
private Long id;
private String description;
private Integer quantity;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "customer_id")
private Customer customer;
#ManyToOne()
private Product product;
}
With full Lombok utility.
The problem is that the list of arguments may vary. We can blend an argument order to something like [50000, Audi, Car, 2012] and we suppose to receive the same result. Otherwise, assuming that we would have a list of parameters that would be previously validated by us for correctness, we could program it on a filtering basis after the next parameters, but in this case, we are not so sure.
Problem clarification:
We give as an argument a list of parameters we want to search for: [50,000, Audi, Car, 2012]. In normal case, assuming that the order will always be correct, so we first specify the price, then the brand, type of goods and year, we could filter the records one by one using a standard filter with the stream API, i.e. first search at the price, then by the brand and so on. The problem occurs when the order is changed and we give first the yearbook then the brand then the price and type of goods. In this situation, our filter will not work because it will not find anything because we changed the order of items that we gave as an argument for our filtering function. In this situation, the part of our filter responsible for the search by parameter: the price will receive the year of the goods, which for obvious reasons will not return decent results assuming. I hope that this time I summarized the problem as best as possible.
Thank you so much for pieces of advice.

JPA Criteria with Join against self with primitive types

I have an entity that looks like
#Entity
#Table(name = "log_entry")
public class LogItem {
#Id
#Column(name = "id")
private long id;
#Column(name = "request_id", nullable = false)
private String requestId;
#Column(name = "request_type")
#Enumerated(EnumType.STRING)
private RequestType requestType;
#Column(name = "entry_type")
#Enumerated(EnumType.STRING)
private LogType logType;
#Column(name = "operation_name")
private String operationName;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "time_stamp", nullable = false)
private Date timeStamp;
#Column(name = "attr_1")
private String attr1;
#Column(name = "attr_2")
private String attr2;
// ...
#Column(name = "attr_10")
private String attr10;
}
This maps to a table that holds audit records, typically there would be one root row and a bunch of rows inserted with the same requestId with varying logType, requestType and operationName values. The attrX columns hold different values depending on the types/operations involved.
I need to implement a generic query tool that can find say find the root request and one or more specific child element depending on the values of attrs. However, I'm struggling to simply join the log table to itself.
The bit I'm stuck at is trying to emulate the following:
SELECT l1.*, l2.* FROM log_entry l1
JOIN log_entry l2
ON l2.request_id = l1.request_id
AND l2.entry_type = 'Something'
AND l2.operation_name = 'someOperation'
WHERE l2.operation_name = 'someOtherOperation'
This is my code
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<LogItem[]> criteria = cb.createQuery(LogItem[].class);
Root<LogItem> root = criteria.from(LogItem.class);
Join<LogItem, LogItem> responseJoin =
root.join(LogItem.requestId.getName(), JoinType.INNER);
ArrayList<Predicate> predicates = new ArrayList<Predicate>();
// Simply adds preciates for log type etc...
getCommonPredicates(filter, cb, root, predicates);
// Add predicates for attr values
addAttributePredicates(cb, predicates, filter.getAttributeFilters());
criteria.where(predicates.toArray(new Predicate[predicates.size()]));
TypedQuery<LogItem[]> query = entityManager.createQuery(criteria);
List<LogItem[]> resultsList = query.getResultList();
This however gives me the error:
org.hibernate.jpa.criteria.BasicPathUsageException: Cannot join to attribute of basic type
I understand I could get around this by adding LogItem references to the entity class but I can't modify the table structure. Is there a way to do this without reverting to JBDC? The SQL is trivial I just can't see how to transform it into a criteria. I'm using hibernate, while I would prefer a JPA solution I'm not adverse to using hibernate specific code.
You can only join via a 1-1/1-N/M-N/N-1 relation, since JPA tries to keep to the spirit of O-O. Your alternatives here are
Add an extra from clause and specify a where clause to accompany
it. This likely will create a CROSS JOIN.
Add a relation to your model so then you can use join() and define INNER or LEFT OUTER.
Clearly some of these may not be an option for your situation.

Map hibernate projections result to java POJO model

I've been using spring and hibernate for this past few weeks and I've always been learning something new there.
Right now I've got a problem that I want to solve with Projections in Hibernate.
Suppose there is a model Person and that model has many Car. The following are how the class definitions roughly gonna look like:
public class Person implements java.io.Serializable {
private Integer id;
private String name;
private List<Car> cars;
private Integer minYear; // Transient
private Integer maxYear; // Transient
}
public class Car implements java.io.Serializable {
private Integer id;
private Integer year;
}
The problem here is I want to get the minYear (maxYear) of each Person to be filled by the earliest year (latest year) of the cars they have.
Later I found a solution to use Projections but I stumbled upon org.hibernate.QueryException: could not resolve property: minYear of: model.Person and here is the code of the db operation:
Criteria criteria = sessionFactory.getCurrentSession().createCriteria("model.Person");
criteria.add(create(personInstance));
criteria.createAlias("minYear", "minYear");
criteria.setProjection(Projections.min("cars.year").as("minYear"));
Is there anyway to store the aggregation value in transient method using Projections because I just want to avoid using plain SQL and HQL as much as possible.
Never mind, I've found the solution.
First we need to create alias of the associated object like so
Criteria criteria = sessionFactory.getCurrentSession().createCriteria("model.Person");
criteria.createAlias("cars", "cars");
Select the needed using Hibernate Projections
ProjectionList projections = Projections.projectionList();
projections.add(Projections.property("id").as("id"));
projections.add(Projections.property("name").as("name"));
projections.add(Projections.property("cars").as("cars"));
Group the result based on the root entity (in this case using its id, Person.id), this is needed especially when used with aggregation to group the aggregation
projections.add(Projections.groupProperty("id"));
Use the aggregate function
projections.add(Projections.min("cars.year").as("minYear"));
projections.add(Projections.max("cars.year").as("maxYear"));
Set the projection
criteria.setProjection(projections);
Use result transformer AliasToBeanResultTransformer to map the result fields (as specified in step 2 & 4) to the POJO
criteria.setResultTransformer(new AliasToBeanResultTransformer(Person.class));
Get the result
List<Person> results = (List<Person>) criteria.list();

Categories

Resources