I created the following predicate that matches a given set of string e.g. "a,b,c" against an column containing an #ElementCollection Set<String>.
I want all entries that contain "a" or "b" or "c". E.g. all these entries/set match:
a //because of a
b,d //because of b
c,f,g //because of c
I have a predicate that is working - i only need to make it case insensitive (don't want to change the data base):
private Predicate matchStringSet(Set<String> set, SetAttribute<DeliveryModeEntity, String> attribute,
CriteriaBuilder criteriaBuilder, Root<DeliveryModeEntity> deliveryMode) {
List<Predicate> matchEach = new ArrayList<>();
for (String string : set) {
matchEach.add(criteriaBuilder.isMember(string.toLowerCase(), deliveryMode.get(attribute)));
}
return criteriaBuilder.or(matchEach.toArray(new Predicate[] {}));
}
How to make deliveryMode.get(attribute) use lower case or how to make this case insensitive.
I tried to use a join as mentioned here: https://stackoverflow.com/a/10225074/447426
but this is not working within a for loop - it will create a join per string in set.
I have a similar entity, Widget, having tags:
#ElementCollection
#Column(name = "tag")
private List<String> tags = new ArrayList<>();
With what you have, to find all widgets having given tags use:
private Predicate matchTags(List<String> tags, Root<Widget> root, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<>();
for (String tag : tags) {
predicates.add(cb.isMember(tag, root.get(Widget_.tags)));
}
return PredicateReducer.OR.reduce(cb, predicates); // cb.or(predicates.toArray(new Predicate[0]));
}
To find all Widgets having given tags in a case-insensitive way, first join the tags and then perform a like search:
private Predicate matchTagsCaseInsensitive(List<String> tags, Root<Widget> root, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<>();
Join<Widget, String> joinedTag = root.join(Widget_.tags);
for (String tag : tags) {
predicates.add(cb.like(cb.lower(joinedTag), tag.toLowerCase()));
}
return PredicateReducer.OR.reduce(cb, predicates); // cb.or(predicates.toArray(new Predicate[0]));
}
Edit: Adding PredicateReducer
I find myself AND-ing or OR-ing predicates often, so wrapped those operations into an enum:
public enum PredicateReducer {
AND {
#Override
public Predicate reduce(CriteriaBuilder cb, List<Predicate> predicates) {
return cb.and(predicates.toArray(new Predicate[0]));
}
},
OR {
#Override
public Predicate reduce(CriteriaBuilder cb, List<Predicate> predicates) {
return cb.or(predicates.toArray(new Predicate[0]));
}
};
public abstract Predicate reduce(CriteriaBuilder cb, List<Predicate> predicates);
public Predicate reduce(CriteriaBuilder cb, Predicate... predicates) {
return reduce(cb, Arrays.asList(predicates));
}
}
Related
I have a pojo that contains a property name, logic operator as String and the value of property. What I want to accomplish is create a Predicate or Expression etc dynamically from the pojo data. Below are my code:
public class QueryParam {
private String property = "acctType"; //can be any property of classname
private String operator = "eqic" //can be any logic operator !=, >, <, >=, <= etc
private Object value; //will store the value of
// getters/setters here
}
public interface CustomerRepository extends JpaRepository<Customer, Long>, QueryDslPredicateExecutor<Customer>{
}
#Service("CustomerService")
class MyCustomerServiceImpl {
#Resource
private CustomerRepository custRpstry;
//if classname is Customer, property is "acctType", operator is "eqic", and value is "Corporate"
//I want my findAll below to retrieve all Customers having acctType = "Corporate"
List<Customer> findAll(List<QueryParam> qryParam) {
QCustomer qcust = QCustomer.customer;
BooleanBuilder where = new BooleanBuilder();
for(QueryParam param : qryParam) {
//within this block, i want a BooleanBuilder to resolve to:
where.and(qcust.acctType.equalsIgnoreCase("Corporate"));
something like:
where.and(param.getClassname().param.getProperty().param.getOperator().param.getValue())
}
return custRpstry.findAll(where.getValue()).getContent();
}
}
I can't figure out to formulate my BooleanBuilder especially the portion that will convert
getOperator() into .equalIgnoreCase().
Any help would be greatly appreciated.
Thanks in advance,
Mario
After combining several answers to some related questions here in so, I was able to formulate a solution that works for me.
BooleanBuilder where = new BooleanBuilder();
for(QueryParam param: qryParam) {
//create: Expressions.predicate(Operator<Boolean> opr, StringPath sp, filter value)
//create an Operator<Boolean>
Operator<Boolean> opr = OperationUtils.getOperator(param.getOperator().getValue());
//create a StringPath to a class' property
Path<User> entityPath = Expressions.path(Customer.class, "customer");
Path<String> propPath = Expressions.path(String.class, entityPath, param.getProperty());
//create Predicate expression
Predicate predicate = Expressions.predicate(opr, propPath, Expressions.constant(param.getValue()));
where.and(predicate);
}
list = repository.findAll(where.getValue(), pageReq).getContent();
My OperationUtils.java
public class OperationUtils {
public static com.mysema.query.types.Operator<Boolean> getOperator(String key) {
Map<String, com.mysema.query.types.Operator<Boolean>> operators = ImmutableMap.<String, com.mysema.query.types.Operator<Boolean>>builder()
.put(Operator.EQ.getValue() ,Ops.EQ)
.put(Operator.NE.getValue() ,Ops.NE)
.put(Operator.GT.getValue() ,Ops.GT)
.put(Operator.GTE.getValue() ,Ops.GOE)
.put(Operator.LT.getValue() ,Ops.LT)
.put(Operator.LTE.getValue() ,Ops.LOE)
.build();
return operators.get(key);
}
}
I have a JPA entity User which contains a field (entity) City. I want to select one page of, for example, 10 users but from different cities.
In SQL I would use something like:
SELECT DISTINCT ON (u.city_id) u.username ,u.email, u.city_id ....
FROM user u LIMIT 0,10 ....
but I need to do it with JPQL or JPA criteria builder. How can I achieve this?
Recently I came across same situation, found that there is no direct way using criteria query to support it.
Here is my solution -
Create custom sql function for distinct on
register function to dialect
Update dialect in properties
call it from criteria query
1) Create Custom function
public class DistinctOn implements SQLFunction {
#Override
public boolean hasArguments() {
return true;
}
#Override
public boolean hasParenthesesIfNoArguments() {
return true;
}
#Override
public Type getReturnType(Type type, Mapping mapping) throws QueryException {
return StandardBasicTypes.STRING;
}
#Override
public String render(Type type, List arguments, SessionFactoryImplementor sessionFactoryImplementor) throws QueryException {
if (arguments.size() == 0) {
throw new QueryException("distinct on should have at least one argument");
}
String commaSeparatedArgs = String.join(",",arguments);
return " DISTINCT ON( " + commaSeparatedArgs + ") " + arguments.get(0) + " ";
}
}
2) Register Function
public class CustomPostgresSqlDialect extends PostgreSQLDialect {
public CustomPostgresSqlDialect() {
super();
registerFunction("DISTINCT_ON", new DistinctOn());
}
}
3) Update Dialect :
Here pass on your class name
spring.jpa.properties.hibernate.dialect = com.harshal.common.CustomPostgresSqlDialect
4) Use it in Criteria Query
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
// SELECT DISTINCT ON (u.city_id) u.username
query.multiselect(
cb.function("DISTINCT_ON", String.class, user.get("city")),
user.get("userName")
);
return em.createQuery(query).getResultList();
You can do this by using Hibernate Criteria Query
sample code can be like this
Criteria criteria = session.createCriteria(user.class);
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.distinct(projectionList.add(Projections.property("city_id"), "cityId")));
projectionList.add(Projections.property("username"), "username");
projectionList.add(Projections.property("email"), "email");
criteria.setProjection(projectionList2);
criteria.setResultTransformer(Transformers.aliasToBean(user.class));
List list = criteria.list();
I am using Entity Framework version 4. I need to compare a large (~1 million record) SQL Server table to a longish (~2000) array of complex objects returned from a web service. Five different properties need to be compared to determine whether an instance of the complex object is already in the database.
I created a function that returns an expression for use in .Where and .Any methods. It looks like this (where A is the complex object, and tblA is the EF class):
function Expression<tblA, bool> GetSearchPredicate(A a)
{
return ta => ta.Field1.Equals(a.Field1)
&& ta.Field2.Equals(a.Field2)
&& ta.Field3.Equals(a.Field3)
&& ta.Field4.Equals(a.Field4)
&& ta.Field5.Equals(a.Field5);
}
This works. And I can compare all 2000 instances of A by doing this:
IEnumerable<A> objects = [web service call];
var result = objects.Select(a => !db.tblA.Any(GetSearchPredicate(a)));
That works, too. But it's slow. So I looked into building a utility method that could build an expression that could be transmitted down to the database directly through EF.
I used the code in this question as a basis for building that utility method. The example in that question shows comparing a single property to a series of constants, while my version would have to compare multiple properties to multiple constants. My best effort is below:
public static IQueryable<TEntity> WhereIn<TEntity>
(
this ObjectQuery<TEntity> query,
IEnumerable<Expression<Func<TEntity, bool>>> predicates
)
{
if (predicates == null) throw new ArgumentNullException("predicates");
IEnumerable<ParameterExpression> p = predicates.Select(pred => pred.Parameters.Single()).ToArray();
IEnumerable<Expression> equals = predicates.Select(value =>
(Expression)value.Body);
Expression bigEqual = equals.Aggregate((accumulate, equal) =>
Expression.Or(accumulate, equal));
var result1 = Expression.Lambda<Func<TEntity, bool>>(bigEqual, p.First());
var result = query.Where(result1);
return result;
}
This would be invoked like this:
IEnumerable<A> objects = [web service call];
var result = db.tblA.WhereIn(objects.Select(a => GetSearchPredicate(a)));
What I get is a message saying that "ta" (the placeholder for the TEntity object) is not bound. I thought this was because I had multiple expressions (the variable predicates) being combined, and maybe this message was being thrown because I was only passing the parameter from the first of the predicates IEnumerable. But this happens even if predicates is one expression long.
I am reasonably sure, based on the method I linked to, that I could build an expression comparing each of the five properties to a constant (the values of A.Field1 through A.Field5), rather than passing in the parameter predicates that already has them assembled into a series of expressions. But I would rather not, since that would require my method to know that it's working with types A and tblA, and that's the opposite of generic and general-purpose. (It'd also be complex and messy.)
I hope the examples I've shown explain what I want to do. Can it be done in a generic way?
You will need to replace the parameter in the predicate bodies with a single parameter. Something like this should work:
public static Expression<Func<T, bool>> BuildOr<T>(
IEnumerable<Expression<Func<T, bool>>> predicates)
{
Expression body = null;
ParameterExpression p = null;
Expression<Func<T, bool>> first = null;
foreach (Expression<Func<T, bool>> item in predicates)
{
if (first == null)
{
first = item;
}
else
{
if (body == null)
{
body = first.Body;
p = first.Parameters[0];
}
var toReplace = item.Parameters[0];
var itemBody = ReplacementVisitor.Transform(item, toReplace, p);
body = Expression.OrElse(body, itemBody);
}
}
if (first == null)
{
throw new ArgumentException("Sequence contains no elements.", "predicates");
}
return (body == null) ? first : Expression.Lambda<Func<T, bool>>(body, p);
}
private sealed class ReplacementVisitor : ExpressionVisitor
{
private IList<ParameterExpression> SourceParameters { get; set; }
private Expression ToFind { get; set; }
private Expression ReplaceWith { get; set; }
public static Expression Transform(
LambdaExpression source,
Expression toFind,
Expression replaceWith)
{
var visitor = new ReplacementVisitor
{
SourceParameters = source.Parameters,
ToFind = toFind,
ReplaceWith = replaceWith,
};
return visitor.Visit(source.Body);
}
private Expression ReplaceNode(Expression node)
{
return (node == ToFind) ? ReplaceWith : node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
return ReplaceNode(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
var result = ReplaceNode(node);
if (result == node) result = base.VisitBinary(node);
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (SourceParameters.Contains(node)) return ReplaceNode(node);
return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
}
}
Your WhereIn method then becomes:
public static IQueryable<TEntity> WhereIn<TEntity>(
this ObjectQuery<TEntity> query,
IEnumerable<Expression<Func<TEntity, bool>>> predicates)
{
if (predicates == null) throw new ArgumentNullException("predicates");
var predicate = BuildOr(predicates);
return query.Where(predicate);
}
I'm want to create a specification that matches a group id of a user object against a list of ids. I was thinking about using isMember (like in the code) but the method won't take the list.
public static Specification<User> matchCompanyIdsList(final List<Long> groupIds){
return new Specification<User>() {
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder){
final Path<Group> group = root.<Group> get("group");
return builder.isMember(company.<Long>get("id"), companyIds);
}
};
}
If I'm off, the how would I do it otherwise?
Do you want to create a specification that matches all users that have group id, which are in the groupsIds list?
If so, you could use something like this (Which will use SQL IN clause):
public static Specification<User> matchCompanyIdsList(final List<Long> groupIds){
return new Specification<User>() {
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder){
final Path<Group> group = root.<Group> get("group");
return group.in(groupIds);
}
};
}
lamba way
with org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor to generate the underscore classes
static Specification<User> hasRoles(List<String> roles) {
return (root, query, cb) -> {
query.distinct(true);
Join<User, Account> joinUserAccount = root.join(User_.account);
Join<Account, AccountRole> acctRolesJoin = joinUserAccount.join(Account_.accountRoles);
Join<AccountRole, Role> rolesJoin = acctRolesJoin.join(AccountRole_.role);
return rolesJoin.get(Role_.name).in(roles);
};
}
I am trying to order an IQueryable of entities by date from a passed in Expression< Func< T, object>> and am getting the error: "Unable to cast the type 'System.Nullable`1' to type 'System.Object'. LINQ to Entities only supports casting Entity Data Model primitive types." The entity has a nullable datetime property on it on which I am trying to sort:
Example: (where e.Date is a nullable DateTime)
Expression<Func<T,object>> sorter = (e) => e.Date;
IOrderedQueryable<T> sortedData = data.OrderBy(sorter);
Thanks in advance!
I wrote a simple class for ordering entities based on a lambda expression at runtime.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace DataModeling
{
public class QueryOrderer<TEntity>
where TEntity : class
{
private LambdaExpression defaultSortExpression;
private Dictionary<string, LambdaExpression> orderFieldLookup;
public QueryOrderer()
{
orderFieldLookup = new Dictionary<string, LambdaExpression>();
}
public void AddOrderMapping<TProp>(string fieldName, Expression<Func<TEntity, TProp>> selector)
{
orderFieldLookup[fieldName] = selector;
}
public void SetDefaultSortExpression<TProp>(Expression<Func<TEntity, TProp>> selector)
{
defaultSortExpression = selector;
}
public IQueryable<TEntity> GetOrderedEntries(string field, bool isDescending, IQueryable<TEntity> entries)
{
return orderEntries(entries, field, isDescending);
}
private IQueryable<TEntity> orderEntries(IQueryable<TEntity> entries, string fieldName, bool isDescending)
{
dynamic lambda = getOrderByLambda(entries, fieldName);
if (lambda == null)
{
return entries;
}
if (isDescending)
{
return Queryable.OrderByDescending(entries, lambda);
}
else
{
return Queryable.OrderBy(entries, lambda);
}
}
private dynamic getOrderByLambda(IQueryable<TEntity> entries, string fieldName)
{
if (!String.IsNullOrWhiteSpace(fieldName) && orderFieldLookup.ContainsKey(fieldName))
{
return orderFieldLookup[fieldName];
}
else
{
return defaultSortExpression;
}
}
}
}
You use this class by initially setting up all of the fields:
QueryOrderer<User> orderer = new QueryOrderer<User>();
orderer.SetDefaultSortExpression(u => u.FullName);
orderer.AddOrderMapping("UserId", u => u.UserId);
orderer.AddOrderMapping("Name", u => u.FullName);
orderer.AddOrderMapping("Email", u => u.Email);
orderer.AddOrderMapping("CreatedOn", u => u.CreatedOn);
...
var users = orderer.GetOrderedEntries("CreatedOn", isDescending: false, context.Users);
I nice feature of this code is that it handles look-up values perfectly. For instance, if you're trying to sort using the description rather than a key, you can use the outer context when building up the sort expression:
orderer.AddOrderMapping("UserType",
u => context.UserTypes
.Where(t => t.UserTypeId == u.UserTypeId)
.Select(t => t.Description)
.FirstOrDefault());
Entity Framework is smart enough to just fold the sub-query right into the outer query.
Two problem here: First you use object in your sorter, you should use DateTime. Secondly every element must have a place in the order so you have to define what should happen with elements where Date is null:
Expression<Func<T, DateTime>> sorter = (e) => e.Date ?? DateTime.MaxValue;
IOrderedQueryable<T> sortedData = data.OrderBy(sorter);
Try to reconstruct expression body
private LambdaExpression CreateLambdaPropertyGetter(Expression<Func<TEntity, object>> expression)
{
Expression body;
if (expression.Body is UnaryExpression && ((UnaryExpression)expression.Body).NodeType == ExpressionType.Convert)
body = ((UnaryExpression)expression.Body).Operand;
else
body = expression.Body;
var lambda = Expression.Lambda(body, expression.Parameters);
return lambda;
}
Try using Func delegate instead on Expression<Func>
Func<T,object> sorter = (e) => e.Date;
IOrderedEnumerable<T> sortedData = data.OrderBy(sorter);