Merge specifications of different types in Criteria Query Specifications - spring

I'm having an Activity entity which is in #ManyToOne relationship with Event entity and their corresponding metamodels - Activity_ and Event_ were generated by JPA model generator.
I've created specialized classes ActivitySpecifications and EventSpecifications. Those classes contain only static methods whose return Specification. For example:
public interface EventSpecifications {
static Specification<Event> newerThan(LocalDateTime date) {
return (root, cq, cb) -> cb.gt(Event_.date, date);
}
...
}
so when I want to build query matching multiple specifications, I can execute following statement using findAll on JpaSpecificationExecutor<Event> repository.
EventSpecifications.newerThan(date).and(EventSpecifications.somethingElse())
and ActivitySpecifications example:
static Specification<Activity> forActivityStatus(int status) { ... }
How do I use EventSpecifications from ActivitySpecifications ? I mean like merge specifications of different type. I'm sorry, but I don't even know how to ask it properly, but theres simple example:
I want to select all activities with status = :status and where activity.event.date is greater than :date
static Specification<Activity> forStatusAndNewerThan(int status, LocalDateTime date) {
return forActivityStatus(status)
.and((root, cq, cb) -> root.get(Activity_.event) ....
// use EventSpecifications.newerThan(date) somehow up there
}
Is something like this possible?
The closest thing that comes to my mind is using the following:
return forActivityStatus(status)
.and((root, cq, cb) -> cb.isTrue(EventSpecifications.newerThan(date).toPredicate(???, cq, cb));
where ??? requires Root<Event>, but I can only get Path<Event> using root.get(Activity_.event).

In its basic form, specifications are designed to be composable only if they refer to the same root.
However, it shouldn't be too difficult to introduce your own interface which is easily convertible to Specification and which allows for specifications refering to arbitrary entities to be composed.
First, you add the following interface:
#FunctionalInterface
public interface PathSpecification<T> {
default Specification<T> atRoot() {
return this::toPredicate;
}
default <S> Specification<S> atPath(final SetAttribute<S, T> pathAttribute) {
// you'll need a couple more methods like this one for all flavors of attribute types in order to make it fully workable
return (root, query, cb) -> {
return toPredicate(root.join(pathAttribute), query, cb);
};
}
#Nullable
Predicate toPredicate(Path<T> path, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}
You then rewrite the specifications as follows:
public class ActivitySpecifications {
public static PathSpecification<Activity> forActivityStatus(ActivityStatus status) {
return (path, query, cb) -> cb.equal(path.get(Activity_.status), cb.literal(status));
}
}
public class EventSpecifications {
public static PathSpecification<Event> newerThan(LocalDateTime date) {
return (path, cq, cb) -> cb.greaterThanOrEqualTo(path.get(Event_.createdDate), date);
}
}
Once you've done that, you should be able to compose specifications in the following manner:
activityRepository.findAll(
forActivityStatus(ActivityStatus.IN_PROGRESS).atRoot()
.and(newerThan(LocalDateTime.of(2019, Month.AUGUST, 1, 0, 0)).atPath(Activity_.events))
)
The above solution has the additional advantage in that specifying WHERE criteria is decoupled from specifying paths, so if you have multiple associations between Activity and Event, you can reuse Event specifications for all of them.

Consider the following :
ClassA {
id;
}
ClassB {
foreignId; //id of A
}
For combining Specification<ClassA> specA, Specification<ClassB> specB
specB = specB.and(combineSpecs(specA);
private static Specification<ClassB> combineSpecs(Specification<ClassA> specA) {
return (root_b,query,builder) {
Subquery<ClassA> sub = query.subquery(ClassA.class);
Root<ClassA> root_a = sub.from(ClassA.class);
Predicate p1 = specA.toPredicate(root_a,query,builder);
Predicate p2 = builder.equal(root_a.get("id"),root_b.get("foreignId"));
Predicate predicate = builder.and(p1,p2);
sub.select(root_a).where(predicate);
return builder.exists(sub);
};
}

Related

Project Reactor: Obtain Size of List Contained Within a Mono

I'm trying to do something again here in project reactor that I'm sure is reeeeeal simple for any of you project reactor gurus out there!
I've been searching and scratching around with this one for a while now, and feel I'm once again hitting a wall with this stuff.
All I'm trying to do is determine if a List of objects contained within a Mono is empty or not.
This is what I have so far:
private Mono<Boolean> isLastCardForAccount(String accountId) {
return cardService.getAccountCards(accountId)
.hasElement();
}
I'm thinking the above might work, but I'm having difficulty figuring out how to extract/access the 'Boolean' contained within the returned Mono. I think I have to use 'subscribe' somehow right?
I've mucked around with this stuff for a while now, but still no luck.
Here is how 'getAccountCards' is defined:
public Mono<List<Card>> getAccountCards(final String accountId) {
return cardCrudRepository.getCardsByAccountId(accountId)
.collectList();
}
From CardCrudRepository:
// #Query("SELECT * FROM card WHERE account_id = :accountId") <-Not sure if I need this
Flux<Card> getCardsByAccountId(String accountId);
And lastly, how I'm using 'isLastCardForAccount':
public Mono<Void> updateCardStatus(String accountId, String cardId, String cardStatus) {
return accountService.getAccount(accountId)
.map(Account::getClientId)
.map(clientId -> createUpdateCardStatusServiceRequestData(clientId, cardId, cardStatus))
.flatMap(requestData -> cartaClient.updateCardStatus(requestData)
.then(Mono.defer(() -> isCardBeingCancelled(cardStatus) ? allCardsCancelledForAccount(accountId) ? removeAccount(accountId) :
(isLostOrStolen(cardStatus) ? replaceCard(cardId, cardStatus).flatMap(this::updateCardNumber) : Mono.empty()) : Mono.empty())));
}
As always, any and all help and insight is tremendously appreciated!
I am not sure if this would resolve the issue but this way you can try to write your logic
return accountService.getAccount(accountId)
.map(Account::getClientId)
.map(clientId -> createUpdateCardStatusServiceRequestData(clientId, cardId, cardStatus))
.flatMap(requestData -> cartaClient.updateCardStatus(requestData)
.then(Mono.defer(() ->
Mono.zip(
Mono.just(isCardBeingCancelled(cardStatus)),
isLastCardForAccount(accountId),
Mono.just( isLostOrStolen(cardStatus) )
)
.map(tuple -> {
WRITE YOUR IF ELSE LOGIC
})
The idea is to use zip and then use the tuple for writing logic. The Tuple would be of type Tuple3 of <Boolean, Boolean ,Boolean>. I made the assumption that isLostOrStolen(cardStatus) returns Boolean.
One way of doing that is by using filterWhen operator like this:
.then(Mono.defer(() -> {
if (isCardBeingCancelled(cardStatus)) {
return Mono.just(accountId)
.filterWhen(this::allCardsCancelledForAccount)
.flatMap(this::removeAccount);
} else if (isLostOrStolen(cardStatus)) {
return replaceCard(cardId, cardStatus).flatMap(this::updateCardNumber);
}
return Mono.empty();
}))
You can use filterWhen in the case of asynchronous filtering. Check this section of Which operator do I need? reference and this How to filter Flux asynchronously.
As a side note, this is not going to work as you expect:
private Mono<Boolean> isLastCardForAccount(String accountId) {
return cardService.getAccountCards(accountId)
.hasElement();
}
public Mono<List<Card>> getAccountCards(final String accountId) {
return cardCrudRepository.getCardsByAccountId(accountId)
.collectList();
}
The collectList() will emit an empty List if there is no card. I'd use exists query instead:
public Mono<Boolean> isLastCardForAccount(final String accountId) {
return cardCrudRepository.existsByAccountId(accountId);
}

Specification OR clause, in and isEmpty/isNull

I have workers that have competences (driving licenses and such) and then there are mechanisms that require certain competences. Sometimes the mechanisms require no competences at all.
Currently I have a Specification with an in clause that works fine, but I would like it to also send out mechanisms that require no competences to operate.
public static Specification<Mechanism> hasCompetences(String searchTerm) {
return (root, query, criteriaBuilder) -> {
query.distinct(true);
List<String> list = new ArrayList<>(Arrays.asList(searchTerm.split(",")));
return root.join("competences").get("name").in(list);
};
}
If I have 3 mechanisms with competences like
Car | B-Category |
Van | C-Category |
Bicycle |(no data here) |
After requesting mechanisms?competences=B-Category it returns Car as expected, but I would like to get the Bicycle too.
Or is there a way to get all all mechanisms that don't require competences? I tried mechanisms?competences= but that returned [].
Edit:
This is where I'm at right now:
public static Specification<Mechanism> hasCompetences(List<String> list) {
return (root, query, cb) -> {
query.distinct(true);
return cb.or(
cb.isEmpty(root.join("competences")),
root.join("competences").get("name").in(list)
);
};
}
But the isEmpty is giving me this error:
java.lang.IllegalArgumentException: unknown collection expression type [org.hibernate.query.criteria.internal.path.SetAttributeJoin]
Edit2:
public static Specification<Mechanism> hasCompetences(List<String> list) {
return (root, query, cb) -> {
query.distinct(true);
Join<Mechanism, Set<Competence>> competences = root.join("competences", JoinType.LEFT);
return cb.or(
root.join("competences").get("name").in(list),
cb.isEmpty(competences)
);
};
}
Error:
unknown collection expression type [org.hibernate.query.criteria.internal.path.SetAttributeJoin];
You have 2 errors:
The criteria to match empty collection is cb.isEmpty(root.get("competences"))
You need to specify left join. root.join("competences", JoinType.LEFT)
Without the second amendment, you make an inner join, so you will never retrieve Mechanisms with empty competences.
Update
You proposed
Join<Mechanism, Set<Competence>> competences = root.join("competences", JoinType.LEFT);
return cb.or(
root.join("competences").get("name").in(list),
cb.isEmpty(competences)
);
isEmpty won't work on SetAttributeJoin (the result of root.join) - look point 1. above
Try
Join<Mechanism, Set<Competence>> competences = root.join("competences", JoinType.LEFT);
return cb.or(
competences.get("name").in(list),
cb.isEmpty(root.get("competences"))
);

spring data JPA searching ignoring words order

Using the named method dao.findByNameContains("hotel sheraton"), I can get a list of hotels whose name includes hotel sheraton. But if a hotel's name is sheraton hotel, it will not return that hotel.
So I'd like to know how to search ignoring the words order if the string consisting of a list of words.
You can't do that with a derived query (i.e. a query that is derived from the method name).
Probably the best approach is to use Specifications as described in the linked article.
You can then have a method that turns a list of Strings into a Specification that is a combination of Like-Specifications:
public MySpecifications {
public static Specification<???> nameContains(String namePart) {
return new Specification<Customer> {
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.like(root.get("name"), "%" + namePart + "%");
}
};
}
public static Specification<???> nameContainsAny(Collection<String> nameParts) {
Specification spec = Specification.where();
for (String namePart : nameParts) {
spec = spec.or(nameContains(String namePart)
}
return spec;
}
}
Note: mistakes in the code are intended as an exercise for the reader.

Linq Parsing Error when trying to create seperation of concerns

I am in the middle of a refactoring cycle where I converted some extension methods that used to look like this:
public static IQueryable<Family> FilterOnRoute(this IQueryable<Family> families, WicRoute route)
{
return families.Where(fam => fam.PODs
.Any(pod => pod.Route.RouteID == route.RouteID));
}
to a more fluent implementation like this:
public class SimplifiedFamilyLinqBuilder
{
private IQueryable<Family> _families;
public SimplifiedFamilyLinqBuilder Load(IQueryable<Family> families)
{
_families = families;
return this;
}
public SimplifiedFamilyLinqBuilder OnRoute(WicRoute route)
{
_families = _families.Where(fam => fam.PODs
.Any(pod => pod.Route.RouteID == route.RouteID));
return this;
}
public IQueryable<Family> AsQueryable()
{
return _families;
}
}
which I can call like this: (note this is using Linq-to-Nhibernate)
var families =
new SimplifiedFamilyLinqBuilder()
.Load(session.Query<Family>())
.OnRoute(new WicRoute() {RouteID = 1})
.AsQueryable()
.ToList();
this produces the following SQL which is fine with me at the moment: (of note is that the above Linq is being translated to a SQL Query)
select ... from "Family" family0_
where exists (select pods1_.PODID from "POD" pods1_
inner join Route wicroute2_ on pods1_.RouteID=wicroute2_.RouteID
where family0_.FamilyID=pods1_.FamilyID
and wicroute2_.RouteID=#p0);
#p0 = 1
my next effort in refactoring is to move the query part that deals with the child to another class like this:
public class SimplifiedPODLinqBuilder
{
private IQueryable<POD> _pods;
public SimplifiedPODLinqBuilder Load(IQueryable<POD> pods)
{
_pods = pods;
return this;
}
public SimplifiedPODLinqBuilder OnRoute(WicRoute route)
{
_pods = _pods.Where(pod => pod.Route.RouteID == route.RouteID);
return this;
}
public IQueryable<POD> AsQueryable()
{
return _pods;
}
}
with SimplifiedFamilyLinqBuilder changing to this:
public SimplifiedFamilyLinqBuilder OnRoute(WicRoute route)
{
_families = _families.Where(fam =>
_podLinqBuilder.Load(fam.PODs.AsQueryable())
.OnRoute(route)
.AsQueryable()
.Any()
);
return this;
}
only I now get this error:
Remotion.Linq.Parsing.ParserException : Cannot parse expression 'value(Wic.DataTests.LinqBuilders.SimplifiedPODLinqBuilder)' as it has an unsupported type. Only query sources (that is, expressions that implement IEnumerable) and query operators can be parsed.
I started to implement IQueryable on SimplifiedPODLinqBuilder(as that seemed more logical than implementing IEnumberable) and thought I would be clever by doing this:
public class SimplifiedPODLinqBuilder : IQueryable
{
private IQueryable<POD> _pods;
...
public IEnumerator GetEnumerator()
{
return _pods.GetEnumerator();
}
public Expression Expression
{
get { return _pods.Expression; }
}
public Type ElementType
{
get { return _pods.ElementType; }
}
public IQueryProvider Provider
{
get { return _pods.Provider; }
}
}
only to get this exception (apparently Load is not being called and _pods is null):
System.NullReferenceException : Object reference not set to an instance of an object.
is there a way for me to refactor this code out that will parse properly into an expression that will go to SQL?
The part fam => _podLinqBuilder.Load(fam.PODs.AsQueryable() is never going to work, because the linq provider will try to parse this into SQL and for that it needs mapped members of Family after the =>, or maybe a mapped user-defined function but I don't know if Linq-to-Nhibernate supports that (I never really worked with it, because I still doubt if it is production-ready).
So, what can you do?
To be honest, I like the extension methods much better. You switched to a stateful approach, which doesn't mix well with the stateless paradigm of linq. So you may consider to retrace your steps.
Another option: the expression in .Any(pod => pod.Route.RouteID == route.RouteID)); could be paremeterized (.Any(podExpression), with
OnRoute(WicRoute route, Expression<Func<POD,bool>> podExpression)
(pseudocode).
Hope this makes any sense.
You need to separate methods you intend to call from expressions you intend to translate.
This is great, you want each of those methods to run. They return an instance that implements IQueryable<Family> and operate on that instance.
var families = new SimplifiedFamilyLinqBuilder()
.Load(session.Query<Family>())
.OnRoute(new WicRoute() {RouteID = 1})
.AsQueryable()
.ToList();
This is no good. you don't want Queryable.Where to get called, you want it to be an expression tree which can be translated to SQL. But PodLinqBuilder.Load is a node in that expression tree which can't be translated to SQL!
families = _families
.Where(fam => _podLinqBuilder.Load(fam.PODs.AsQueryable())
.OnRoute(route)
.AsQueryable()
.Any();
You can't call .Load inside the Where expression (it won't translate to sql).
You can't call .Load outside the Where expression (you don't have the fam parameter).
In the name of "separation of concerns", you are mixing query construction methods with query definition expressions. LINQ, by its Integrated nature, encourages you to attempt this thing which will not work.
Consider making expression construction methods instead of query construction methods.
public static Expression<Func<Pod, bool>> GetOnRouteExpr(WicRoute route)
{
int routeId = route.RouteID;
Expression<Func<Pod, bool>> result = pod => pod.Route.RouteID == route.RouteID;
return result;
}
called by:
Expression<Func<Pod, bool>> onRoute = GetOnRouteExpr(route);
families = _families.Where(fam => fam.PODs.Any(onRoute));
With this approach, the question is now - how do I fluidly hang my ornaments from the expression tree?

Using eager loading with specification pattern

I've implemented the specification pattern with Linq as outlined here https://www.packtpub.com/article/nhibernate-3-using-linq-specifications-data-access-layer
I now want to add the ability to eager load and am unsure about the best way to go about it.
The generic repository class in the linked example:
public IEnumerable<T> FindAll(Specification<T> specification)
{
var query = GetQuery(specification);
return Transact(() => query.ToList());
}
public T FindOne(Specification<T> specification)
{
var query = GetQuery(specification);
return Transact(() => query.SingleOrDefault());
}
private IQueryable<T> GetQuery(
Specification<T> specification)
{
return session.Query<T>()
.Where(specification.IsSatisfiedBy());
}
And the specification implementation:
public class MoviesDirectedBy : Specification<Movie>
{
private readonly string _director;
public MoviesDirectedBy(string director)
{
_director = director;
}
public override
Expression<Func<Movie, bool>> IsSatisfiedBy()
{
return m => m.Director == _director;
}
}
This is working well, I now want to add the ability to be able to eager load. I understand NHibernate eager loading can be done by using Fetch on the query.
What I am looking for is whether to encapsulate the eager loading logic within the specification or to pass it into the repository, and also the Linq/expression tree syntax required to achieve this (i.e. an example of how it would be done).
A possible solution would be to extend the Specification class to add:
public virtual IEnumerable<Expression<Func<T, object>>> FetchRelated
{
get
{
return Enumerable.Empty<Expression<Func<T, object>>>();
}
}
And change GetQuery to something like:
return specification.FetchRelated.Aggregate(
session.Query<T>().Where(specification.IsSatisfiedBy()),
(current, related) => current.Fetch(related));
Now all you have to do is override FetchRelated when needed
public override IEnumerable<Expression<Func<Movie, object>>> FetchRelated
{
get
{
return new Expression<Func<Movie, object>>[]
{
m => m.RelatedEntity1,
m => m.RelatedEntity2
};
}
}
An important limitation of this implementation I just wrote is that you can only fetch entities that are directly related to the root entity.
An improvement would be to support arbitrary levels (using ThenFetch), which would require some changes in the way we work with generics (I used object to allow combining different entity types easily)
You wouldn't want to put the Fetch() call into the specification, because it's not needed. Specification is just for limiting the data that can then be shared across many different parts of your code, but those other parts could have drastically different needs in what data they want to present to the user, which is why at those points you would add your Fetch statements.

Resources