JPA Criteria api using IN expression with join - jpa

i have entity Request that have #ManyToMany Set<Region> regions, and Region entity have field region of type RegionEnum of enum type with constants of regions.
I need to create criteria to get requests, where its regions are in collection of RegionEnum;
In my choice:
List<RegionEnum> regs=...; // from method parameter
CriteriaBuilder cb=em.getCriteriaBuilder();
CriteriaQuery<Request> cq=cb.createQuery(Request.class);
Root<Request> from=cq.from(Request.class);
cq.where(cb.isTrue(from.join("regions").get("region").in(regs)));
return em.createQuery(cq).getResultList();
I have an java.lang.IllegalArgumentException: PREDICATE_PASSED_TO_EVALUATION (There is no English translation for this message.)
enum:
public enum RegionEnum {
CENTRAL("Центральный"),
SOUTH("Южный"),
NWEST("Северо-Западный"),
FEAST("Дальневосточный"),
SIB("Сибирский"),
URFO("Уральский"),
VOLGA("Волжский"),
NCAU("Северо-Кавказский");
private final String value;
private Region(String value) {
this.value=value;
}
public String value() {
return this.value;
}
}
is my criteria right and problem with enum? or criteria is bad?

I have faced with the same exception today so I tried to solve the problem.
Let's assume we have the following instances already initialized (as in your example):
CriteriaBuilder cb;
List<String> values;
Root<Entity> r; // where Entity has String name attribute
Now we can create a Predicate instance this way which is working using EclipseLink 2.6.2 library:
Predicate good = r.get("name").in(values);
But if we try to wrap the predicate into the cb.isTrue() method (as you did it) which require an "Expression<Boolean>" parameter like this:
Predicate wrong = cb.isTrue(r.get("name").in(values));
the mentioned exception PREDICATE_PASSED_TO_EVALUATION will be raised as you have received it even the expression has right syntax.
I think because Expression is an Ancestor of Predicate class but this just an idea. At least the exception message says something like this.
So just remove the cb.isTrue() wrapping and probably it will work for you as it works for me.

Related

Is there a way to query by superclass id using JPA Criteria, Specification and metamodel?

I have classes Car that has field Driver driver and Driver that inherites from Employee. Employee has field id.
I have to find and provide to frontend the driver's id for the car using JPA Criteria and metamodel. It turnes out difficult because id is inherited from superclass. The query should be made by:
public static <U, T, P> Specification<U> isEqualTo(SingularAtribute<U, T> joinField, SingularAtribute<T, P>, field, P param{
if (param == null){
return null;
}
return (root, query, cb) -> cb.equal(root.join(joinField).get(field), param);
}
However when I want to use it in:
static Specification<Car> hasDriverId(Long value){
return isEqualTo(Car_.driver, Driver_id, value);
}
I get error:
method isEqualTo cannot be aplied to given types;
required: SingularAtribute<U,T>, SingularAtribute<T,P>, P
found: SingularAtribute<Car, Driver>, SingularAtribute<Employee, Long>, Long
reason: inference variable T has incompatible equality constraints Employee, Driver
I know that inheritance can cause problems in ORM however I cannot change it in project. Is there way to overcome this?

Spring #QuerydslPredicate and QuerydslBinderCustomizer: is it possible to infuse default criteria into predicate generated from request params?

I am using Spring Data JPA and QueryDsl (v.4.2.2), Java 8. I can explicitly construct search predicates and pass them to the repository methods. However, I like the idea of using the #QuerydslPredicate annotation on a web/REST controller's method argument when the queried entities have more than a few properties, and I want the flexibility of filtering the search by any of them. So, something like this, generally, works very well:
#GetMapping("/accounts/summaries")
public PageDto<AccountSummaryDto> getAccountSummaries(#QuerydslPredicate(root = AccountSummary.class) Predicate accountSearchPredicate,
#RequestParam(name = "pageIndex", defaultValue = "0") int pageIndex,
#RequestParam(name = "pageSize", defaultValue = "25") int pageSize,
#RequestParam(name = "sortBy", defaultValue = "id") String sortBy,
#RequestParam(name = "sortOrder", defaultValue = "desc") String sortOrder) {
// delegating to web-agnostic service that:
// - creates Pageable pageRequest,
// - calls accountSummaryRepository.findAll(predicate, pageRequest),
// - constructs custom PageDto wrapper, etc.
return accountService.retrieveAccountSummaries(accountSearchPredicate, pageIndex, pageSize, sortBy, sortOrder);
}
My Spring Data JPA repository interface looks similar to this:
public interface AccountSummarySearchRepository
extends JpaRepository<AccountSummary, Integer>, QuerydslPredicateExecutor<AccountSummary>, QuerydslBinderCustomizer<QAccountSummary > {
#Override
default void customize(QuerydslBindings bindings, QAccountSummary acctSummary) {
bindings.bind(acctSummary.customer.firstName).first((path, value) -> path.isNull().or(path.startsWithIgnoreCase(value))) ;
bindings.bind(acctSummary.customer.lastName).first((path, value) -> path.isNull().or(path.startsWithIgnoreCase(value))) ;
// etc.
// default binding for String properties to be case insensitive "contains" match
bindings.bind(String.class).first(
(StringPath path, String value) -> path.isNull().or(path.containsIgnoreCase(value)));
}
My question:
The bindings in the customize method are set using the entity field
paths and the values of the request parameters that match those
paths. If the parameter is not specified, is there a way to bind the
path to some constant value or a value obtained dynamically?
For example, I want to always ONLY retrieve the entities where property deleted is set to false - without forcing the client to pass that as a query parameter? Similarly, I may want to set other default lookup values dynamically for each query. For example, I may want to "retrieve only those accounts where assignedTo == [current user ID available on a ThreadLocal]...
The following will not work
bindings.bind(acctSummary.deleted).first((path, value) -> path.eq(false));
because it, obviously, expects the first occurrence of the path/value pair for deleted=... in the Predicate (mapped from the incoming request params via the #QuerydslPredicate annotation. I don't want to pass that as a parameter because the requester does not even need to know about the existence of such field.
Is there a simple way to infuse the Predicate instance that is auto-populated via the #QuerydslPredicate annotation with any additional implicit/default criteria that are not explicitly passed in the web request? Could this be done in the customize method? I suppose, one (very ugly) way would be to intercept the HTTP request in a filter - before it is processed by the Spring-QueryDsl framework - and replace it with a new request with added parameters? That would be a horrible solution, and I feel there has to be a better way to do it via some hook/capability provided by the framework itself.
Unfortunately, there seem to be no comprehensive documentation for Spring QueryDsl support - other than some very simplistic examples.
Thanks for your help!
Answering my own question... I was hoping to find a hook in the framework where I could add the code to enhance the auto-generated predicate with criteria common for all my queries - before it arrives in the controller method, but wasn’t able to figure that out. Overriding QuerydslPredicateArgumentResolver doesn't seem a good or necessary option. And, quite frankly, I've come to the conclusion that this wasn't such a great idea to begin with. It seems that any modifications to the search criteria should be done in a more obvious way - in the business tier. So I decided to simply update the predicate in the service method:
public PageDto<AccountSummaryDto> retrieveByPredicate(Predicate predicate, int pageIndex, int pageSize, String sortBy, String sortOrder) {
Pageable pageRequest = PageRequest.of(pageIndex, pageSize, Sort.Direction.fromString(sortOrder), sortBy);
QAccountSummary accountSummary = QAccountSummary.accountSummary; //QueryDsl auto-generated query type for AccountSummary (path root)
// construct new enhanced search predicate w/added criteria common for all queries
// using original predicate generated by framework from request params as base
BooleanBuilder updatedPredicate = new BooleanBuilder(predicate)
.and(accountSummary.somethingNested.id.eq(SomeThreadContext.getSomethingId()))
.and(accountSummary.deleted.eq(false))
.and(accountSummary.someProperty.eq("xyz"));
Page<accountSummary> page = summarySearchRepository.findAll(updatedPredicate, pageRequest);
return toAccountSummaryPageDto(page); // custom method that converts results to page DTO w/entity dots and page stats
}
The construction of the updated predicate may be extracted into a separate private method on the service should it be desirable to use it in multiple search methods and/or if more logic is required to dynamically generate additional search criteria.

Eclipselink JPA MappingSelectionCriteria customization

According to EclipseLink/Examples/JPA/MappingSelectionCriteria I can make some filtering on OneToOne or OneToMany relationships. To do that I have to implement DescriptorCustomizer.
My question is: Can I do some conditional filtering with this technique and how? I mean, in the example of mentioned link we can write something like this
public class ConfigureBsFilter implements DescriptorCustomizer {
public void customize(ClassDescriptor descriptor) throws Exception {
OneToManyMapping mapping = (OneToManyMapping) descriptor
.getMappingForAttributeName("bs");
ExpressionBuilder eb = new ExpressionBuilder(mapping
.getReferenceClass());
Expression fkExp = eb.getField("A_ID").equal(eb.getParameter("A_ID"));
Expression activeExp = eb.get("active").equal(true);
mapping.setSelectionCriteria(fkExp.and(activeExp));
}
}
But what if in the expression
Expression activeExp = eb.get("active").equal(true);
the "active" is not always true but have to be set at runtime by some parameter. Can I do that and how?
Looking at wiki.eclipse.org/Using_Advanced_Query_API_(ELUG) you could use a query redirector on the ForeignReferenceMapping#getSelectionQuery() so that your query redirector can dynamically clone the query and add filters as required. Passing parameters to the redirector will need to be creative though, such as storing them on the thread context or in the session's properties map.

Casting issue while returning value in method in EF4.0

I'm trying to load employees using Entity Framework.
The method is supposed to return employee list.
It' s giving this error:
Cannot implicit convert....<Class names and methods>.... An Explicit conversion exists.
I think the problem is related to casting.
Please check below code.
public List<Employee> LoadEmployees()
{
try
{
EMployeeDB1Entities EE = new EMployeeDB1Entities();
var Employees = EE.Employees.Where(p => p.Name.StartsWith("T"));
return Employees;
}
catch
{
return null;
}
}
var Employees = EE.Employees.Where(p => p.Name.StartsWith("T")).ToList();
Update your code to:
return Employees.ToList();
Also do note that this is the ToList() method that actually triggers the database query.
EE.Employees.Where(....) doesn't query the database. The DB is queried when the result of the Where() is enumerated, which is what .ToList() does.
Thanks it works...one more issue, suppose if I want to bind above list
to grid then how can I bind ?
Assuming you're using WPF or Silverlight:
To bind the result of your query on a datagrid, you could expose a public property of type ObservableCollection.
This collection accepts an IEnumerable<T> object as constructor.
You can write:
var myCollection = new ObservableCollection<Employee>(this.LoadEmployees());
Then bind the ItemSource property of your datagrid to your collection.
If you have more problems using bindings, I recommend you to ask another question, because the subject is quite different.

EF 4.1 Linq to Entities Contains works with List<T> but not T[]

I'm using Entity Framework 4.1 on a SQLCE 4 database and my context is a DbContext.
The purpose of the following code is to search for a list of entries where the "ProtocolName" property is contained within a given list.
This version of the code that supplies the list of permitted values as a List works fine:
List<string> lstPossibles = new List<string>("Name1", "Name2");
query = from le in context.DaLogEvents where lstPossibles.Contains(le.ProtocolName) select le;
However, this version of the code that supplies the permitted values as an array throws an exception when the query.ToList() is invoked:
string[] aryPossibles = new string[] { "Name1", "Name2" };
query = from le in context.DaLogEvents where aryPossibles.Contains(le.ProtocolName) select le;
The reported exception is:
"LINQ to Entities does not recognize the method 'Boolean
Contains[String](System.String[], System.String)' method, and this
method cannot be translated into a store expression."
It took me a while to figure out to use List rather than string[] because I've seen a fair number of references on this site to the use of the array method.
Is this an issue with just EF 4.1?
Using an array "usually" works as well as using a list or any other IEnumerable<string> implementation - given that the extension method Contains is the standard LINQ extension method of IEnumerable<T>.
The exception you get ...
...the method 'Boolean Contains[String](System.String[], System.String)'...
... is surprising because it indicates that the compiler doesn't use the LINQ extension method. If it would use the LINQ method the exception would be something like:
...the method 'Boolean Contains[String](IEnumerable`1[String], System.String)'...
Or better: If it would use this method there wouldn't be an exception at all.
The question is: Do you have somewhere an extension method Contains for string[] - either handwritten or perhaps included from some other assembly/namespace? Something like:
public static class SomeExtensions
{
public static bool Contains<T>(this T[] ary, T value)
{
// ...
}
}
If this thing is used (your code would compile) you would indeed get the exception because LINQ to Entities does not know how to translate such a custom method into SQL (="store expression"), no matter how it is implemented.