Unit Test JPA Specification's content - spring

We have implemented filtering for a repository by using JPA's Specification as follows:
public Page<CustomerDTO> searchCustomers(SearchDto searchDto) {
var queryFilters = this.getQueryFitlers(searchDto);
var paginationAndSorting = this.getPaginationAndSorting(searchDto.getPageNumber(),
searchDto.getPageSize());
return customerRepository.findAll(queryFilters, paginationAndSorting)
.map(entity -> {
CustomerDTO dto = new CustomerDTO();
copyProperties(entity, dto);
return dto;
})
}
And here is the queryFilters() method which uses Specifications:
private Specification<Customer> getQueryFitlers(CustomerSearchSpecDTO filteringValues) {
Specification<Customer> specification = Specification.where(null);
if (isNotBlank(filteringValues.getLastname())) {
specification = specification.and(CustomerRepository.hasLastname(filteringValues.getLastname()));
}
if (isNotBlank(filteringValues.getTaxId())) {
specification = specification.and(CustomerRepository.hasTaxId(filteringValues.getTaxId()));
}
// several more fields with the same approach
return specification;
}
Since these query filters are optional depending upon if the searchField is empty or not, what we would like to do is verify that the specification contains the proper "filters".
For example, if the searchDto input contains only taxId not blank, then I want to check that the returned specification contains such a "filter / specification".
Note: grabbing a reference to the result of this.getQueryFilters() (which is the Specifications) is not a problem, we already achieved that.

Related

MapStruct Spring Page to custom object conversion includes check

I am using MapStruct to convert a Page object to a custom object of my application. I am using this mapping in order to convert the content field of the Page object to a list of custom objects found in my data model:
#Mapping(target = "journeys", source = "content")
While this works OK and does convert the elements when content is present, this does not work correctly in case of no Page content. Taking a look at the code seems to show that the following check is added in the generated mapper class:
if ( page.hasContent() ) {
List<JourneyDateViewResponseDto> list = page.getContent();
journeyDateViewPageResponseDto.setJourneys( new ArrayList<JourneyDateViewResponseDto>( list ) );
}
When this is added the mapping action of the inner objects is omitted, meaning that I end up with a null list. I am not really sure as to why and how this check is added but I would like to find a way of disabling it and simply end up with an empty list of elements. Is there a way this can be done using MapStruct?
MapStruct has the concept of presence checkers (methods that have the pattern hasXXX). This is used to decide if a source property needs to be mapped.
In case you want to have a default value in your object I would suggest making sure that your object is instantiated with an empty collection or provide an #ObjectFactory for your object in which you are going to set the empty collection.
e.g.
Default value in class
public class JourneyDateViewPageResponseDto {
protected List<JourneyDateViewResponseDto> journeys = new ArrayList<>();
//...
}
Using #ObjectFactory
#Mapper
public interface MyMapper {
JourneyDateViewPageResponseDto map(Page< JourneyDateViewResponseDto> page);
#ObjectFactory
default JourneyDateViewPageResponseDto createDto() {
JourneyDateViewPageResponseDto dto = new JourneyDateViewPageResponseDto();
dto.setJourneys(new ArrayList<>());
return dto;
}
}
#Mapping(target = "journeys", source = "content", defaultExpression = "java(java.util.List.of())")

Get current classifications from custom classifier

I'm trying to create a classifier VS extension to colorize "TODO" comments. For that, in my IClassifier.GetClassificationSpans method I want to get the current classification spans, and only process those classified as "comment".
In my classifier provider I added an IClassifierAggregatorService field, and used its GetClassifier method to pass the result to my classifier, which can use it to get the classifications. However, this generates a StackOverflowException, since my GetClassifier method is called again and again in a loop:
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;
namespace TestTasksClassifier
{
[Export(typeof(IClassifierProvider))]
[ContentType("code")]
internal class EditorClassifier1Provider : IClassifierProvider
{
[Import] private IClassificationTypeRegistryService classificationRegistry;
[Import] private IClassifierAggregatorService aggregatorService;
public IClassifier GetClassifier(ITextBuffer buffer)
{
return buffer.Properties.GetOrCreateSingletonProperty<EditorClassifier1>(creator: () =>
new EditorClassifier1(classificationRegistry, aggregatorService.GetClassifier(buffer)));
}
}
}
To solve the StackOverflowException, in GetClassifier I've ended using ITextBuffer.Properties to store my classifier instance, and if it already exists I just return a null classifier to stop the loop. It works in my limited tests, but I have no idea whether this is a valid and safe approach:
public IClassifier GetClassifier(ITextBuffer buffer)
{
if (buffer.Properties.TryGetProperty<EditorClassifier1>(typeof(EditorClassifier1), out var _))
{
return null;
}
else
{
EditorClassifier1 editorClassifier1 = new EditorClassifier1(classificationRegistry);
buffer.Properties.AddProperty(typeof(EditorClassifier1), editorClassifier1);
// Now I can call "aggregatorService.GetClassifier", since "editorClassifier1" is already added to "buffer.Properties",
// and this method will return null when called again.
editorClassifier1.Classifier = aggregatorService.GetClassifier(buffer);
return editorClassifier1;
}
}
What would be the best approach to retrieve the current classifications from my custom classifier?

Merge specifications of different types in Criteria Query Specifications

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);
};
}

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.

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