Spring JPA Specifications Not In Query - spring

Here the IN criteria query using Spring JPA Specification is working fine. but I don't know how to use "NOT IN"..
So How can i use the NOT IN criteria query using Spring JPA Specifications.
SearchSpecification<User> spec = new CommonSpecification<Case>(new SearchCriteria("client.id", Operator.NOT_IN, clientIdList));
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Predicate predicate = null;
switch (searchCriteria.getOperator()) {
case IN:
if (searchCriteria.getValue() instanceof List<?>) {
predicate = getFieldPath(searchCriteria.getKey(), root)
.in(((List<?>) searchCriteria.getValue()).toArray());
}
break;
case NOT_IN:
//What to do???
break;
default:
break;
}
return predicate;
}
private Path<Object> getFieldPath(String key, Root<T> root) {
Path<Object> fieldPath = null;
if (key.contains(".")) {
String[] fields = key.split("\\.");
for (String field : fields) {
if (fieldPath == null) {
fieldPath = root.get(field);
} else {
fieldPath = fieldPath.get(field);
}
}
} else {
fieldPath = root.get(key);
}
return fieldPath;
}

Use the Predicate.not() method; the code is the same as in the IN case, just add .not():
case NOT_IN:
if (searchCriteria.getValue() instanceof List<?>) {
predicate = getFieldPath(searchCriteria.getKey(), root)
.in(((List<?>) searchCriteria.getValue()).toArray())
.not();
}
break;

This functionality also works fine..
case NOT_IN:
if (searchCriteria.getValue() instanceof List<?>) {
predicate = criteriaBuilder.not(getFieldPath(searchCriteria.getKey(), root).in(((List<?>) searchCriteria.getValue()).toArray()));
}
break;

Related

How to append specification based on condition in spring data jpa

I am creating a spring boot project and working on spring data jpa and currently I am using custom query to fetch data from db based on users selection and this is my page where user can select option based on their condition https://i.imgur.com/coO3BCJ.png
So, I googled it and found we can use specification but as I am very new to specification, so, I used specification and I want that based on users choice, it should keep adding specification, so, this is my conditional specification...
Specification<UserDetails> specification =
Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage()));
if(!customSearch.getManglik().isBlank()) {
specification.and(UserDetailsSpecification.isManglik
(customSearch.getManglik()));
}
if(!customSearch.getMaritalStatus().isBlank()) {
specification.and(UserDetailsSpecification
.hasMaritalStatus(customSearch.getMaritalStatus()));
}
if(!customSearch.getReligion().isBlank()) {
specification.and(UserDetailsSpecification.hasReligion
(customSearch.getReligion()));
}
if(!customSearch.getCaste().isBlank()) {
specification.and(UserDetailsSpecification.hasCaste
(customSearch.getCaste()));
}
if(!customSearch.getLocation().isBlank()) {
specification.and(UserDetailsSpecification.hasLocation
(customSearch.getLocation()));
}
listOfCustomSearch=userDetailsRepository
.findAll(specification, pageable);
List<UserDetails> listOfAllSearchedUsers = listOfCustomSearch.getContent();
but it is not appending the specification and just filtering the data based on only
Specification<UserDetails> specification = Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage()));
so, based on users selection, so, my final query should be something like this(If user has selected all fields):
Specification<UserDetails> specification = Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage())
.and(UserDetailsSpecification.isManglik(customSearch.getManglik()) .and(UserDetailsSpecification.hasMaritalStatus(customSearch.getMaritalStatus())) .and(UserDetailsSpecification.hasReligion(customSearch.getReligion()))
.and(UserDetailsSpecification.hasCaste(customSearch.getCaste()))
.and(UserDetailsSpecification.hasLocation(customSearch.getLocation()))))
But suppose if user has selected only let suppose 3 or 4 fields, so, my final specification should be something like below:(This specification should be completely depends upon user selection)
Specification<UserDetails> specification = Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage())
.and(UserDetailsSpecification.isManglik(customSearch.getManglik())
.and(UserDetailsSpecification.hasLocation(customSearch.getLocation()))))
Currently it is not appending specification based on users selection, so, please help me in adding specification based on users selections
Finally, I found the solution and we can create customized specification like this below:
public static Specification<UserDetails> getSpecs(String gender, int fromAge, int toAge, String manglikStatus, String maritalStatus, String religion, String caste, String location){
Specification<UserDetails> spec = null;
Specification<UserDetails> temp = null;
if(!gender.isBlank() && !gender.isEmpty() && gender!=null && !gender.contains("$")) {
spec = getSpecsForGenderDetails(gender);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(fromAge!=0 || toAge!=0) {
spec = isAgeBetween(fromAge, toAge);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!manglikStatus.isBlank() && !manglikStatus.isEmpty() && manglikStatus!=null && !manglikStatus.contains("$")) {
spec = isManglik(manglikStatus);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!maritalStatus.isBlank() && !maritalStatus.isEmpty() && maritalStatus!=null && !maritalStatus.contains("$")) {
spec = hasMaritalStatus(maritalStatus);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!religion.isBlank() && !religion.isEmpty() && religion!=null && !religion.contains("$")) {
spec = hasReligion(religion);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!caste.isBlank() && !caste.isEmpty() && caste!=null && !caste.equalsIgnoreCase("select") && !caste.contains("$")) {
spec = hasCaste(caste);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!location.isBlank() && !location.isEmpty() && location!=null && !location.contains("$")) {
spec = hasLocation(location);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
return temp;
}
And based on that we can define our method like this:
private static Specification<UserDetails> getSpecsForGenderDetails(String gender) {
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.equal(root.get("gender"),gender);
});
}
private static Specification<UserDetails> isAgeBetween(int fromAge, int toAge){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.between(root.get("age"), fromAge, toAge);
});
}
private static Specification<UserDetails> isManglik(String manglikStatus){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("manglikStatus")),"%" +manglikStatus.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasMaritalStatus(String maritalStatus){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("maritalStatus")),"%" +maritalStatus.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasReligion(String religion){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("religion")),"%" +religion.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasCaste(String caste){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("caste")),"%" +caste.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasLocation(String presentState){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("presentState")),"%" +presentState.toLowerCase() +"%");
});
}

Replacing Hibernate's Criteria API by JPA Criteria API

I have some legacy code written with an old Hibernate 3 version which I'd like to upgrade to 5.3. I am currently working on transitioning from Hibernate Criteria to JPA CriteriaBuilder.
I have something like this:
public List<ENTITY> findByCriteria(final Map<String, Object> criteriaMap, final List<String> fields, final Class<ENTITY> entityClass) {
Session session = this.sessionFactory.getSessionFactory().getCurrentSession();
final Criteria criteria = session.createCriteria(entityClass);
final Set<String> keys = criteriaMap.keySet();
Object object;
CriteriaValue criteriaValue;
CriteriaValue.Operator operator;
for (String key:keys) {
object = criteriaMap.get(key);
if (object instanceof SimpleExpression) {
criteria.add((SimpleExpression)object);
} else if (object instanceof LogicalExpression) {
criteria.add((LogicalExpression)object);
} else if (object instanceof Criterion) {
criteria.add((Criterion)object);
} else if (!isDefaultCriteria(key)) {
if (!(object instanceof CriteriaValue)) {
if (object instanceof String && ((String)object).contains(SYSTEMWILDCARD)) {
criteriaMap.put(key, new CriteriaValue(object, CriteriaValue.Operator.iLIKE));
} else {
criteriaMap.put(key, new CriteriaValue(object, CriteriaValue.Operator.EQ));
}
object = criteriaMap.get(key);
}
operator = ((CriteriaValue)object).getOperator();
criteriaValue = (CriteriaValue)object;
if (criteriaValue != null) {
Object value = criteriaValue.getValue();
switch (operator) {
case EQ:
criteria.add(value == null ? Restrictions.isNull(key) : Restrictions.eq(key, value));
break;
case iLIKE:
criteria.add(Restrictions.ilike(key, cleanWildcards(value)));
break;
case LIKE:
criteria.add(Restrictions.like(key, cleanWildcards(value)));
break;
case LE:
criteria.add(Restrictions.le(key, value));
break;
case GE:
criteria.add(Restrictions.ge(key, value));
break;
case LT:
criteria.add(Restrictions.lt(key, value));
break;
case GT:
criteria.add(Restrictions.gt(key, value));
break;
case OR:
criteria.add(getOrRestrictions(key, value));
break;
case AND:
criteria.add(getAndRestrictions(key, value));
break;
case IN:
criteria.add(Restrictions.in(key, (Object[])value));
break;
case NE:
criteria.add(value == null ? Restrictions.isNotNull(key): Restrictions.ne(key, value));
break;
}
}
}
}
....
}
Which I would rewrite like that:
CriteriaBuilder cb = sessionFactory.getCriteriaBuilder();
CriteriaQuery<?> cq = null;
try {
cq = cb.createQuery(Class.forName(entityClass.getName()));
cq.from(Class.forName(entityClass.getName()));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
List<?> books = sessionFactory.getCurrentSession().createQuery(cq).getResultList();
But my problem comes with the part where the Expressions and Restrictions are added to the criteria, as a corresponding .add() method is not available. How do I rewrite this segment?
You build a list of javax.persistence.criteria.Predicate and in the end use cb.and( predicateList ) to build a final predicate which you can add to the query by passing it to cq.where()

Spring Boot & JPA: CriterialBuilder check if String contains Expression<String>

1.) I want to get an Expression using an Expression and a String Pattern.
It should become true if the Expression is included in the String Pattern not the other way.
The string pattern can look like "MFD" or "MF" or any other combination of the three letters M,F,D. The Expression is either M, F or D.
So far I have the following code, but i think there is a better solution.
public class AthleteSpecification implements Specification<Athlete> {
private String gender;
public AthleteSpecification(String gender) {
super();
this.gender = gender;
}
#Override
public Predicate toPredicate(Root<Athlete> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate p = cb.disjunction();
if (this.gender == null) {
return cb.conjunction();
} else {
Expression<Boolean> isMaleOrFemaleorDivers;
if (this.gender != null) {
// This is a working solution but not nice
Expression<Boolean> isMale = cb.equal(root.get("gender"), gender.contains("M") ? "M" : "");
Expression<Boolean> isMaleOrFemale = cb.or(isMale,
cb.equal(root.get("gender"), gender.contains("F") ? "F" : ""));
isMaleOrFemaleorDivers = cb.or(isMaleOrFemale,
cb.equal(root.get("gender"), gender.contains("D") ? "D" : ""));
// This is not a solution because i think it only checks if the String is in the Expression<String> not the other way
// Expression<Integer> test = cb.locate(root.get("gender"), gender);
// isMaleOrFemaleorDivers = cb.greaterThan(test, 0);
} else {
isMaleOrFemaleorDivers = cb.conjunction();
}
p.getExpressions().add(cb.and(isNameMatching, isMaleOrFemaleorDivers));
return p;
}
}
}

Sitecore ContentSearch LINQ Unsupported expression node type: Parameter

I get the following exception message when trying to run the ToList method for the Sitecore ContentSearch LINQ query in the following code block:
Unsupported expression node type: Parameter. This could be due to ordering of
expression statements. For example filtering after a select expression like this :
queryable.Select(i => new { i.Id, i.Title }).Where(i => d.Title > "A"
public virtual List<SearchResultItem> RunQuery(SearchParam param, bool showAllVersions, bool firstLoad)
{
Assert.ArgumentNotNull(Index, "Sitecore.SharedSource.Search");
var resultCollection = new List<SearchResultItem>();
try
{
using (var context = this.Index.CreateSearchContext())
{
var result = context.GetQueryable<SearchResultItem>()
.Where(x => HasFullText(x, param.FullTextQuery) &&
HasLanguage(x, param.Language) &&
HasRelation(x, param.RelatedIds) &&
HasTemplate(x, param.TemplateIds) &&
HasLocation(x, param.LocationIds)
);
resultCollection = result.ToList();
}
}
catch (Exception exception)
{
Log.Error(exception.StackTrace, this);
throw;
}
return resultCollection;
}
I can't figure out what causes this issue and I can't seem to reproduce the issue with standard .NET LINQ queries, in a standard Console Application (source code at the end).
Here is the source code for the HasLanguage, HasRelation, HasTemplate and HasLocation functions. I expect it has something to do with those because when I remove them and replace them with their implementation (where possible) I get no errors. However, when left inside the query they are not even accessed (tried debugging):
protected bool HasRefinements(SearchResultItem pseudoResult, SafeDictionary<string> refinements)
{
if (refinements.Count <= 0) return false;
foreach (var refinement in refinements)
{
var fieldName = refinement.Key.ToLowerInvariant();
var fieldValue = refinement.Value;
if (pseudoResult.GetField(fieldName).Value.Equals(IdHelper.ProcessGUIDs(fieldValue)))
{
return true;
}
}
return false;
}
protected bool HasLanguage(SearchResultItem pseudoResult, string language)
{
if (String.IsNullOrEmpty(language)) return false;
return pseudoResult.GetField(BuiltinFields.Language).Equals(language.ToLowerInvariant());
}
protected bool HasFullText(SearchResultItem pseudoResult, string searchText)
{
if (String.IsNullOrEmpty(searchText)) return false;
return pseudoResult.Content.Contains(searchText);
}
protected bool HasId(SearchResultItem pseudoResult, string fieldName, string filter)
{
if (String.IsNullOrEmpty(fieldName) || String.IsNullOrEmpty(filter)) return false;
var values = IdHelper.ParseId(filter);
foreach (var value in values.Where(ID.IsID))
{
if (pseudoResult.GetField(fieldName).Value.Equals(IdHelper.ProcessGUIDs(value)))
{
return true;
}
}
return false;
}
protected bool HasTemplate(SearchResultItem pseudoResult, string templateIds)
{
if (String.IsNullOrEmpty(templateIds)) return false;
templateIds = IdHelper.NormalizeGuid(templateIds);
return pseudoResult.TemplateId.ToString().Equals(templateIds);
}
protected bool HasLocation(SearchResultItem pseudoResult, string locationIds)
{
return HasId(pseudoResult, BuiltinFields.Path, locationIds);
}
protected bool HasRelation(SearchResultItem pseudoResult, string ids)
{
return HasId(pseudoResult, BuiltinFields.Links, ids);
}
And here is the source code for my test application using regular LINQ queries:
static void Main(string[] args)
{
Program p = new Program();
p.Process();
}
public void Process()
{
List<Boolean> flags = new List<Boolean>();
flags.Add(true);
flags.Add(false);
flags.Add(false);
flags.Add(true);
flags.Add(false);
bool b = true;
try
{
List<Boolean> trueFlags = flags
.Where<Boolean>(x => IsTrue(x, b))
.ToList();
Console.WriteLine(trueFlags.ToString());
Console.ReadKey();
}
catch (Exception e)
{
Console.WriteLine(e.StackTrace);
}
}
public bool IsTrue(bool x, bool b)
{
return x ^ b;
}
I can't seem to find anything on this exception message on the internet.
Sitecore LINQ isn't really like normal LINQ to objects in .NET. Like LINQ to SQL isnt like normal LINQ.
What actually happens is that Sitecore parses the expression tree and "converts/translates" your conditions to a Search Query. By default this is to a Lucene query expression - but using a SearchProvider it could also translate to a SOLR expression or Coveo expression.
In the same way LINQ to SQL translates to a SQL expression.
You can see that the LINQ is actually an IQueryable and not IEnumerable.
When working on the IQuerable Sitecore must know how to translate it to the search expression. Sitecore doesn't know how to translate your properties and methods in the LINQ expression and that is why you get the error.
You should change your expression to only hold something that can be translated or create a predicate. You should look into the PredicateBuilder

How do I apply a default IComparable<T> in a Linq OrderBy clause

I have a type which has a default sort order as it implements IComparable<T> and IComparable. I'm not getting the results I expect from LINQ , basically it looks as if the IComparable<T> which the type implements is not being applied.
I thought I would get the result I want with an expression in the form:
var result = MyEnumerable<T>.OrderBy(r => r);
where T itself implements IComparable<T>. It's not happening.
I can see related questions where specific IComparable<T> classes are specified for the sort, but I can't find one which uses the default IComparable<T> implemented by T itself.
My syntax is clearly incorrect. What is the correct syntax please?
Thanks in advance.
OrderBy uses the default comparer Comparer<T>.Default which in turn will default to use the IComparable<T> implementation for T, or the non-generic IComparable if the former does not exist.
This code works:
public class Program
{
static void Main(string[] args)
{
var list = new List<Stuff>
{
new Stuff("one"),
new Stuff("two"),
new Stuff("three"),
new Stuff("four")
};
var sorted = list.OrderBy(x => x);
foreach (var stuff in sorted)
{
Console.Out.WriteLine(stuff.Name);
}
}
}
public class Stuff : IComparable<Stuff>
{
public string Name { get; set; }
public Stuff(string name)
{
Name = name;
}
public int CompareTo(Stuff other)
{
return String.CompareOrdinal(Name, other.Name);
}
}
public static class GenericSorter
{
public static IOrderedEnumerable<T> Sort<T>(IEnumerable<T> toSort, Dictionary<string, SortingOrder> sortOptions)
{
IOrderedEnumerable<T> orderedList = null;
foreach (KeyValuePair<string, SortingOrder> entry in sortOptions)
{
if (orderedList != null)
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenBy");
}
else
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenByDescending");
}
}
else
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderBy");
}
else
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderByDescending");
}
}
}
return orderedList;
}
private static IOrderedEnumerable<T> ApplyOrder<T>(this IEnumerable<T> source, string property, string methodName)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
Expression expr = param;
foreach (string prop in property.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), expr.Type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
MethodInfo mi = typeof(Enumerable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), expr.Type);
return (IOrderedEnumerable<T>)mi.Invoke(null, new object[] { source, lambda.Compile() });
}
}

Resources