Wrong Pagination info using JpaSpecifications - spring-boot

I am using spring boot 2.2.6
I am implementing an API that has the option of filtering data and it must be returned paginated
so I used specifications (JpaSepecificationExecutor) and the specification has a join with another table
and this join is mandatory as a customerId is always given and added to predicate list
the problem is that the paginated info returned is wrong mainly (totalElements) and (totalPages)
this is the specifications class
`
#Data
#AllArgsConstructor
#NoArgsConstructor
public class CustomerSalesOrderSpecification implements Specification<SalesOrder> {
#NonNull
private Integer customerId;
private Integer orderId;
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate purchasedDateFrom;
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate purchasedDateTo;
private String customerFirstname;
private BigDecimal baseGrandTotalFrom;
private BigDecimal baseGrandTotalTo;
private String storeName;
#Override
public Predicate toPredicate(#NonNull Root<SalesOrder> root, #NonNull CriteriaQuery<?> query,
#NonNull CriteriaBuilder builder) {
List<Predicate> predicateList = new ArrayList<>();
predicateList.add(customerId(customerId).toPredicate(root, query, builder));
if (Objects.nonNull(orderId)) {
predicateList.add(orderId(orderId).toPredicate(root, query, builder));
}
if (Objects.nonNull(purchasedDateFrom)) {
predicateList.add(purchaseDateFrom(purchasedDateFrom).toPredicate(root, query, builder));
}
if (Objects.nonNull(purchasedDateTo)) {
predicateList.add(purchaseDateTo(purchasedDateTo).toPredicate(root, query, builder));
}
if (Objects.nonNull(customerFirstname)) {
predicateList.add(customerFirstname(customerFirstname).toPredicate(root, query, builder));
}
if (Objects.nonNull(baseGrandTotalFrom)) {
predicateList.add(baseGrandTotalFrom(baseGrandTotalFrom).toPredicate(root, query, builder));
}
if (Objects.nonNull(baseGrandTotalTo)) {
predicateList.add(baseGrandTotalTo(baseGrandTotalTo).toPredicate(root, query, builder));
}
if (Objects.nonNull(storeName)) {
predicateList.add(storeName(storeName).toPredicate(root, query, builder));
}
return builder.and(predicateList.toArray(new Predicate[]{}));
}
public Specification<SalesOrder> customerId(Integer customerId) {
return (root, query, builder) -> {
Join<SalesOrder, CustomerEntity> salesOrderCustomer = root.join("customerEntity");
return builder.equal(salesOrderCustomer.get("entityId"), customerId);
};
}
public Specification<SalesOrder> orderId(Integer orderId) {
return ((root, query, builder) -> builder.equal(root.get("entityId"), orderId));
}
public Specification<SalesOrder> purchaseDateFrom(LocalDate purchasedDateFrom) {
return (root,query,builder) -> builder.or(
builder.greaterThan(root.get("createdAt"),Timestamp.valueOf(purchasedDateFrom.atStartOfDay())),
builder.between(root.get("createdAt"),Timestamp.valueOf(purchasedDateFrom.atStartOfDay()),
Timestamp.valueOf(purchasedDateFrom.atTime(23,59))));
}
public Specification<SalesOrder> purchaseDateTo(LocalDate purchasedDateTo) {
return (root,query,builder) -> builder.or(
builder.lessThan(root.get("createdAt"),Timestamp.valueOf(purchasedDateTo.atStartOfDay())),
builder.between(root.get("createdAt"),Timestamp.valueOf(purchasedDateTo.atStartOfDay()),
Timestamp.valueOf(purchasedDateTo.atTime(23,59))));
}
public Specification<SalesOrder> customerFirstname(String customerFirstname) {
return ((root, query, builder) -> builder.like(root.get("customerFirstname"), "%" + customerFirstname + "%"));
}
public Specification<SalesOrder> baseGrandTotalFrom(BigDecimal baseGrandTotalFrom) {
return ((root, query, builder) -> builder.greaterThanOrEqualTo(root.get("baseGrandTotal"), baseGrandTotalFrom));
}
public Specification<SalesOrder> baseGrandTotalTo(BigDecimal baseGrandTotalTo) {
return ((root, query, builder) -> builder.lessThanOrEqualTo(root.get("baseGrandTotal"), baseGrandTotalTo));
}
public Specification<SalesOrder> storeName(String storeName) {
return ((root, query, builder) -> builder.like(root.get("storeName"), "%" + storeName + "%"));
}
}
`
for example if I filter based on id and name I get totalElements 20 when the real number is 377
and totalPages is 1 which is wrong of course
I tried writing the whole query in JPQL and it worked correctly so the issue is only I use specifications
with pagination and a join
I checked similar questions, but they were related to an error when using join fetch, I am using a normal join and there is no error just wrong pagination info in response
what might be the reason ?

Related

Spring Jpa Specification with list of long where in query

I want to run a where in query with Spring JPA Specification and criteria builder. I am having issue where I will receive a List ids from request and run specification query but could't find any way to do so.
This is what I have done so far.
public class DistributorMasterDataSpecification implements Specification<DistributorMasterData> {
#Override
public Predicate toPredicate(Root<DistributorMasterData> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
return
codeSpec()
.and(idSpec())
.toPredicate(root, query, criteriaBuilder);
}
private Specification<DistributorMasterData> idSpec() {
return ((root, query, criteriaBuilder) ->
Objects.isNull(filterDto.getDistributorIds()) ?
null : root.get(DistributorMasterData_.ID).in(filterDto.getDistributorIds())
);
}
}
I am unable to figure out how to prepare where in query with specification. With current implementation I facing the error below
org.springframework.dao.InvalidDataAccessApiUsageException: literal value cannot be null; nested exception is java.lang.IllegalArgumentException: literal value cannot be null
You can create a custom repository and after you do the implementation
public interface EditeurRepositoryCustom {
Page<User> search(UserSearch search, Pageable page);
}
#Repository
public class UserRepositoryCustomImpl extends SimpleJpaRepository<User,Long> implements EditeurRepositoryCustom {
#Autowired
private EntityManager entityManager;
public EditeurRepositoryCustomImpl(EntityManager em) {
super(User.class, em);
}
public Page<User> search(UserSearch search, Pageable page) {
Specification<User> hasExternalOrder = (Root<User> mainRoot, CriteriaQuery<?> mainCq, CriteriaBuilder mainCb) -> {
return mainRoot.get("externalOrder").isNotNull();
};
Specification<User> isPrincipal = (Root<User> mainRoot, CriteriaQuery<?> mainCq, CriteriaBuilder mainCb) -> {
if(!search.principal()){
return mainCb.and();
}
return mainCb.isTrue(mainRoot.get("main"));
};
};
return findAll(hasNameContain.and(hasExternalOrder).and(isPrincipal), page);
}
You can combine multiple spec

Getting Null Values in Spring Native Query Project

This is my Data Repository file and i used native query to retrieve all data address(Locations) of Data. I called the function using Postman and I got null outputs of locations. This my first time of using Native query and its really impossible to solve these errors
DataRepository
public interface DataRepository extends JpaRepository<Data, Long> {
#Query(value = "SELECT dataAddress FROM Data")
List<DataProject> getDataAddress();
}
DataServiceImpl
public List<DataProject> getDataAddress() {
return dataRepository.getDataAddress();
}
DataService
List<DataProject> getDataAddress();
DataModel
#Entity
#Table(name = "CCCData")
public class Data {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long dataId;
#Column(name = "DATA_NAME")
private String dataName;
#Column(name ="DATA_ADDRESS")
private String dataAddress;
#Column(name = "DATA_DESC")
private String dataDesc;
#CreationTimestamp
private Date dateOfCreated;
#CreationTimestamp
private Date dateOfUpdated;
public long getDataId() {
return dataId;
}
public void setDataId(long dataId) {
this.dataId = dataId;
}
public String getDataName() {
return dataName;
}
public void setDataName(String dataName) {
this.dataName = dataName;
}
public String getDataAddress() {
return dataAddress;
}
public void setDataAddress(String dataAddress) {
this.dataAddress = dataAddress;
}
public String getDataDesc() {
return dataDesc;
}
public void setDataDesc(String dataDesc) {
this.dataDesc = dataDesc;
}
public Date getDateOfCreated() {
return dateOfCreated;
}
public void setDateOfCreated(Date dateOfCreated) {
this.dateOfCreated = dateOfCreated;
}
public Date getDateOfUpdated() {
return dateOfUpdated;
}
public void setDateOfUpdated(Date dateOfUpdated) {
this.dateOfUpdated = dateOfUpdated;
}
DataProjection
public interface DataProject {
String getDataAddress();
}
DataController
#GetMapping("/data/locations")
public List<DataProject> getDataAddress() {
return dataService.getDataAddress();
}
Postman Output
[
{
"dataAddress": null
},
{
"dataAddress": null
},
{
"dataAddress": null
},
{
"dataAddress": null
}
]
Spring won't return you only address using below query. It still return you DATA object
public interface DataRepository extends JpaRepository<Data, Long> {
#Query(value = "SELECT dataAddress FROM Data")
List<DataProject> getDataAddress();
}
for fetching only DataAddress you need to create a constructor inside Data model for DataAddress only
public Data(String dataAddress) {
this.dataAddress = dataAddress;
}
and your query will look like this:
public interface DataRepository extends JpaRepository<Data, Long> {
#Query(value = "SELECT new Data(dataAddress) FROM Data")
List<DataProject> getDataAddress();
}
Update 1 :
if you need this for other fields with same datatype and then above 'constructor' based method fails. There are some other alternatives:
You can fetch DATA object and use java stream map function to extract only 1 field. data.stream().map((data) -> data.getDataAddress()).collect(Collectors.toList())
You can use native SQL query to fetch only required fields.
#Query(value = "SELECT d.data_address FROM CCCData d", nativeQuery=true)

Criteria Builder In query for list of string

I am passing following json from front end :
{names: 'ABC MKL-56-2,ABC MKL-56-3'};
In service layer,I am trying to run in query with the help of criteria builder as follows :
public List<APDetails> getWP(String names) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<APDetails> query = builder.createQuery(APDetails.class);
Root<APDetails> root = query.from(APDetails.class);
Predicate hasA = builder.in(root.get(APDetails_.names).in(Arrays.asList(names.split(","))));
query.where(builder.and(hasA));
List<APDetails> APs = em.createQuery(query.select(root)).getResultList();
return APs;
}
I am getting following error :
Error message: org.hibernate.hql.internal.ast.QuerySyntaxException:
unexpected token: in near line 1, column 163 [select generatedAlias0
from com.app.ow.APDetails as generatedAlias0 where generatedAlias0.names in (:param0, :param1) in ()]
First of all, if you're using springboot, I suggest you extend the JpaSpecificationExecutor class (check here, here, and here for more information) from your APDetailsRepository (I believe you're using them somewhere...):
public interface APDetailsRepository extends JpaRepository<APDetails, Long>, JpaSpecificationExecutor<APDetails> {
Then, try this:
#Autowired
public APDetailsRepository apDetailsRepository;
........
public List<APDetails> getWP(String names) {
List<String> namesAsList = Arrays.asList(names.split(","));
List<APDetails> listAPDetails = this.apDetailsRepository.findAll(createSpecification(namesAsList));
return listAPDetails;
}
public Specification<APDetails> createSpecification(List<String> names) {
return new Specification<APDetails>() {
private static final long serialVersionUID = 1L;
#Override
public Predicate toPredicate(Root<APDetails> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<Predicate>();
if (names!= null && !names.isEmpty()) {
List<Predicate> predicatesNames = new ArrayList<Predicate>();
for (String name : names) {
predicatesNames.add(builder.equal(root.<String>get("names"), name));
//I believe that the "APDetails_.names" attribute is a String...
}
predicates.add(builder.or(predicatesNames.toArray(new Predicate[] {})));
}
return builder.and(predicates.toArray(new Predicate[] {}));
}
};
}

spring jpa query with pageable, sort and filter and return projection

I am using Spring Data Rest with org.springframework.boot 1.5.2 with hibernate 5.2.9. What i am trying to achieve is a way to use JPA to query with sort, filter, pageable that can return a subset of the entity or return a projection.
Below is the code that uses:
(1) Specification for filtering
(2) Projection and Excerpts to apply projection in collection
(3) The controller that tries to return Page,
but it only works if the return type is Page.
where Student is the entity, StudentLite is the projection
Question is:
(1) How to have a query+sort+filter that returns Page projection
(2) Possible to apply the Excerpts to just that query?
(3) Any way to use #JsonView in #RepositoryRestController to solve?
StudentRepository class
#RepositoryRestResource(excerptProjection = StudentLite.class)
public interface StudentRepository extends PagingAndSortingRepository<Student,Long>,
JpaSpecificationExecutor<Student> {}
and
StudentSpecification class
public class StudentSpecification {
public static Specification<Student> filteredStudentList(StudentSearch c) {
final StudentSearch criteria = c;
return new Specification<Student>() {
#Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Join<Student, Contact> joinContact = root.join(Student_.contact);
Path<Contact> contact = root.get(Student_.contact);
Path<String> officialId = root.get(Student_.officialId);
Path<String> name = root.get(Student_.name);
Path<String> email = contact.get(Contact_.email);
Path<String> phoneMobile = contact.get(Contact_.phoneMobile);
final List<Predicate> predicates = new ArrayList<Predicate>();
if(criteria.getOfficialId()!=null) {
predicates.add(cb.like(officialId, "%" + criteria.getOfficialId() + "%"));
System.out.println("==not null...criteria.getOfficialId()="+criteria.getOfficialId()+" :officialId="+officialId.toString());
}
if(criteria.getName()!=null) {
predicates.add(cb.like(name, "%"+criteria.getName()+"%"));
}
if(criteria.getEmail()!=null) {
predicates.add(cb.like(email, "%"+criteria.getEmail()+"%"));
}
if(criteria.getPhoneMobile()!=null) {
predicates.add(cb.like(phoneMobile, "%"+criteria.getPhoneMobile()+"%"));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
}
}
and the controller where the class is annotated with #ExposesResourceFor(Student.class) and #RepositoryRestController :
#RequestMapping(method=RequestMethod.GET)
public #ResponseBody Page<StudentLite> getStudentList(Pageable pageable, #RequestParam Map<String,String> criteria) {
StudentSearch ss = new StudentSearch(criteria);
// Below statement fail, as findAll(...) is suppose to return Page<Student>
Page<StudentLite> pagedStudentLite = studentRep.findAll( StudentSpecification.filteredStudentList(ss), pageable);
return pagedStudentLite;
}

Query from combined spring data specification has multiple joins on same table

Sorry if my terminology isn't correct.
We are using spring data, JpaRepositories and criteria queries as our method to query data from our database.
I have a problem that when I combine two specifications such as I do with hasTimeZone and hasCity in hasCityAndTimeZone in the code example below it does a join on the same table twice, so the query below will look something like
select * from Staff, Location, Location
Is there any way to have the two specifications use the same join instead of each defining their own join that is essentially the same?
Sorry the code probably isn't complete I was just trying to show a quick example.
class Staff {
private Integer id;
private Location location;
}
class Location {
private Integer id;
private Integer timeZone;
private Integer city;
}
class StaffSpecs {
public static Specification<Staff> hasTimeZone(Integer timeZone) {
return new Specification<Staff>() {
#Override
public Predicate toPredicate(Root<Staff> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<Integer> timeZonePath = root.join(Staff_.location).get(Location_.timeZone);
return cb.equal(timeZonePath, timeZone);
}
}
}
public static Specification<Staff> hasCity(Integer city) {
return new Specification<Staff>() {
#Override
public Predicate toPredicate(Root<Staff> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<Integer> cityPath = root.join(Staff_.location).get(Location_.city);
return cb.equal(cityPath, city);
}
}
}
public static Specification<Staff> hasCityAndTimeZone(Integer city, Integer timeZone) {
return where(hasCity(city)).and(hasTimeZone(timeZone));
}
}
There's no out of the box way unfortunately. Spring Data internally uses some reuse of joins within QueryUtils.getOrCreateJoin(…). You could find out about potentially already existing joins on the root and reuse them where appropriate:
private static Join<?, ?> getOrCreateJoin(From<?, ?> from, String attribute) {
for (Join<?, ?> join : from.getJoins()) {
boolean sameName = join.getAttribute().getName().equals(attribute);
if (sameName && join.getJoinType().equals(JoinType.LEFT)) {
return join;
}
}
return from.join(attribute, JoinType.LEFT);
}
Note, that this only works as we effectively know which joins we add ourselves. When using Specifications you should also do, but I just want to make sure nobody considers this a general solution for all cases.
Based on #Oliver answer I created an extension to Specification interface
JoinableSpecification.java
public interface JoinableSpecification<T> extends Specification<T>{
/**
* Allow reuse of join when possible
* #param <K>
* #param <Z>
* #param query
* #return
*/
#SuppressWarnings("unchecked")
public default <K, Z> ListJoin<K, Z> joinList(From<?, K> from, ListAttribute<K,Z> attribute,JoinType joinType) {
for (Join<K, ?> join : from.getJoins()) {
boolean sameName = join.getAttribute().getName().equals(attribute.getName());
if (sameName && join.getJoinType().equals(joinType)) {
return (ListJoin<K, Z>) join; //TODO verify Z type it should be of Z after all its ListAttribute<K,Z>
}
}
return from.join(attribute, joinType);
}
/**
* Allow reuse of join when possible
* #param <K>
* #param <Z>
* #param query
* #return
*/
#SuppressWarnings("unchecked")
public default <K, Z> SetJoin<K, Z> joinList(From<?, K> from, SetAttribute<K,Z> attribute,JoinType joinType) {
for (Join<K, ?> join : from.getJoins()) {
boolean sameName = join.getAttribute().getName().equals(attribute.getName());
if (sameName && join.getJoinType().equals(joinType)) {
return (SetJoin<K, Z>) join; //TODO verify Z type it should be of Z after all its ListAttribute<K,Z>
}
}
return from.join(attribute, joinType);
}
/**
* Allow reuse of join when possible
* #param <K>
* #param <Z>
* #param query
* #return
*/
#SuppressWarnings("unchecked")
public default <K, Z> Join<K, Z> joinList(From<?, K> from, SingularAttribute<K,Z> attribute,JoinType joinType) {
for (Join<K, ?> join : from.getJoins()) {
boolean sameName = join.getAttribute().getName().equals(attribute.getName());
if (sameName && join.getJoinType().equals(joinType)) {
return (Join<K, Z>) join; //TODO verify Z type it should be of Z after all its ListAttribute<K,Z>
}
}
return from.join(attribute, joinType);
}
}
How to use
class StaffSpecs {
public static Specification<Staff> hasTimeZone(Integer timeZone) {
return new JoinableSpecification<Staff>() {
#Override
public Predicate toPredicate(Root<Staff> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<Integer> timeZonePath = this.joinList(root,Staff_.location,JoinType.INNER).get(Location_.timeZone);
return cb.equal(timeZonePath, timeZone);
}
}
}
public static Specification<Staff> hasCity(Integer city) {
return new JoinableSpecification<Staff>() {
#Override
public Predicate toPredicate(Root<Staff> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<Integer> cityPath = this.joinList(root,Staff_.location,JoinType.INNER).get(Location_.city);
return cb.equal(cityPath, city);
}
}
}
private static Join<?, ?> getOrCreateJoin(From<?, ?> from, String attribute) {
for (Join<?, ?> join : from.getJoins()) {
boolean sameName = join.getAttribute().getName().equals(attribute);
if (sameName && join.getJoinType().equals(JoinType.LEFT)) {
return join;
}
}
return from.join(attribute, JoinType.LEFT);
}
And in CustomSpecification
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
query.distinct(true);
String[] parts = criteria.getKey().split("\\.");
Path<?> path = root;
for (String part : parts) {
if(path.get(part).getJavaType() == Set.class){
path = getOrCreateJoin(root, part);
}else{
path = path.get(part);
}
}
}
....
if (path.getJavaType() == String.class) {
return builder.like(path.as(String.class), "%" + criteria.getValue().toString() + "%");
....
This is old question but i wrote this answer for people who having this problem.
I implemented a generic method to search a join alias if there is no join with this alias then it creates the join with given pathFunction.
As Oliver say you can use defined joins but if you have multiple joins for an entity you need to know alias of your defined join.
//Get or create join with name of alias
protected <F extends From<FF, FR>, FF, FR, J extends Join<JF, JR>, JF, JR>J getOrCreateCriteriaJoin(F from, String alias, BiFunction<F, CriteriaBuilder, J> pathFunction) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
Set<Join<FR, ?>> joins = from.getJoins();
Optional<J> optionalJoin = findJoin((Set) joins, alias);
return optionalJoin.orElseGet(() -> {
J join = pathFunction.apply(from, criteriaBuilder);
join.alias(alias);
return join;
}
);
}
//Recursively searches for 'alias' named join
protected Optional<Join> findJoin(Set<Join> joins, String alias) {
List<Join> joinList = new ArrayList<>(joins);
for (Join j : joinList) {
if (j.getAlias() != null && j.getAlias().equals(alias)) {
return Optional.of(j);
}
}
// Breadth first search
for (Join j : joinList) {
Optional<Join> res = findJoin(j.getJoins(), alias);
if (res.isPresent()) {
return res;
}
}
return Optional.empty();
}
Example Usage;
private Join<E, ExampleEntity> getOrCreateExampleEntityJoin(Root<E> mainRoot, String alias) {
return getOrCreateCriteriaJoin(mainRoot, alias, (root, cb) -> root.join(ExampleEntity_.someFieldName));
}
specification = (root, query, criteriaBuilder) -> criteriaBuilder.equal(getOrCreateExampleEntityJoin(root, "exampleAlias").get(ExampleEntity_.someAnOtherField), "ExampleData");
I have slightly modified the implementation so that there is no need to copy-paste aliases and functions
abstract class ReusableJoinSpecification<T> implements Specification<T> {
protected <F extends From<FF, FR>, FF, FR, J extends Join<JF, JR>, JF, JR> J getOrCreateJoin(F from,
JoinData<F, J> joinData) {
Set<Join<FR, ?>> joins = from.getJoins();
//noinspection unchecked
Optional<J> optionalJoin = (Optional<J>) findJoin(joins, joinData.getAlias());
return optionalJoin.orElseGet(() -> {
J join = joinData.getCreationFunction().apply(from);
join.alias(joinData.getAlias());
return join;
}
);
}
private Optional<Join<?, ?>> findJoin(#NotNull Set<? extends Join<?, ?>> joins, #NotNull String alias) {
List<Join<?, ?>> joinList = new ArrayList<>(joins);
for (Join<?, ?> join : joinList) {
if (alias.equals(join.getAlias())) {
return Optional.of(join);
}
}
for (Join<?, ?> j : joinList) {
Optional<Join<?, ?>> res = findJoin(j.getJoins(), alias);
if (res.isPresent()) {
return res;
}
}
return Optional.empty();
}
JoinData:
#Data
class JoinData<F extends From<?, ?>, J extends Join<?, ?>> {
#NotNull
private final String alias;
#NotNull
private final Function<F, J> creationFunction;
}
Usage:
private final JoinData<Root<Project>, Join<Project, Contractor>> contractorJoinData =
new JoinData<>("contractor", root -> root.join(Project_.contractor));
private final JoinData<Join<Project, Contractor>, Join<Contractor, Address>> contractorLegalAddressJoinData =
new JoinData<>("contractorLegalAddress", root -> root.join(Contractor_.legalAddress));
public Specification<Project> contractorLegalAddressCityLike(String address) {
if (address == null)
return null;
return new ReusableJoinSpecification<>() {
#Override
public Predicate toPredicate(Root<Project> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
Join<Project, Contractor> contractorJoin = getOrCreateJoin(root, contractorJoinData);
Join<Contractor, Address> contractorAddressJoin = getOrCreateJoin(contractorJoin, contractorLegalAddressJoinData);
return criteriaBuilder.like(contractorAddressJoin.get(Address_.city), simpleLikePattern(address));
}
};
}
Update:
I made a small tool to make it easier to reuse joins, get rid of the "if" when checking filter-dto fields for null, and use a type checks:
https://github.com/koval666/springspecwrapper

Resources