Add WHERE IN clause to JPA Specification - spring-boot

I'm trying to implement search functionality limited by IN clause:
I want to implement search implementation with filter limitation:
#GetMapping("find")
public Page<MerchantUserDTO> getAllBySpecification(
#And({
#Spec(path = "name", spec = LikeIgnoreCase.class),
#Spec(path = "login", spec = LikeIgnoreCase.class),
#Spec(path = "email", spec = LikeIgnoreCase.class),
}) Specification<Users> specification,
#SortDefault(sort = "login", direction = Sort.Direction.DESC) Pageable pageable
) {
return merchantUserService.getAllBySpecification(specification, pageable)
.map(g -> MerchantUserDTO.builder()
.id(g.getId())
.login(g.getLogin())
.build()
);
}
#Override
public Page<Users> getAllBySpecification(Specification<Users> specification, Pageable pageable) {
return dao.findAllByTypeIn(specification, pageable, "MerchantUser");
}
Repository:
#Repository
public interface MerchantUserRepository extends JpaRepository<Users, Integer>, JpaSpecificationExecutor<Users> {
Page<Users> findAllByTypeIn(Pageable page, String... types);
Page<Users> findAllByTypeIn(Specification<Users> specification, Pageable pageable, String... types);
}
What is the proper way to extend the specification with IN clause?
specification.and(path.in(types)) path is a attribute but how to implement it properly?

Generally this can be achieved this way:
1) Create specification implementation
public class MerchantUserSpecification implements Specification<Users> {
private final List<String> types;
public MerchantUserSpecification(List<String> types) {
this.types = types;
}
#Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
if (types != null && !types.isEmpty()) {
return root.get(Users_.type).in(types);
} else {
// always-true predicate, means that no filtering would be applied
return cb.and();
}
}
2) Use method Page findAll(#Nullable Specification spec, Pageable pageable); inherited from JpaSpecificationExecutor interface instead of using your custom findAllByTypeIn(Specification<Users> specification....)
#Override
public Page<Users> getAllBySpecification(Specification<Users> specification, Pageable pageable) {
// combine original specification (passed from outside) and filter-by-types specification
Specification<Users> finalSpec = specification
.and(new MerchantUserSpecification(Arrays.asList("MerchantUser")))
return dao.findAll(finalSpec, pageable)
}
P.S.
With Java 8+ and for simple cases (like yours) the code may be reduced even more. Instead of implementing Specification<T> in separate class you can just create a method
private Specification<Users> typeIn(List<String> types) {
return (root, query, cb) -> {
if (types != null && !types.isEmpty()) {
return root.get(Users_.type).in(types);
} else {
// always-true predicate, means that no filtering would be applied
return cb.and();
}
}
}
#Override
public Page<Users> getAllBySpecification(Specification<Users> specification, Pageable pageable) {
// combine original specification (passed from outside) and filter-by-types specification
Specification<Users> finalSpec = specification
.and(typeIn(Arrays.asList("MerchantUser")))
return dao.findAll(finalSpec, pageable)
}
UPDATE: Shortest way
#Override
public Page<Users> getAllBySpecification(Specification<Users> specification, Pageable pageable) {
// combine original specification (passed from outside) and filter-by-types specification
Specification<Users> finalSpec = specification
.and((root, query, cb) -> root.get(Users_.type).in(Arrays.asList("MerchantUser"))
return dao.findAll(finalSpec, pageable)
}

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

No converter found capable of converting from type

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.skywarelabs.runwayreporting.api.modules.asset.Asset] to type [com.skywarelabs.runwayreporting.api.modules.asset.AssetPageView]
I have two repository methods which do essentialy the same thing.
I'm expecting them to return an object of <Page>.
If I call findPageByAerodrome, this works correctly.
However, If I call findPageByFilter, I get the error below.
..the reason I want to use findPageByFilter is that I wish to extend this with additional filtering parameters.
'''
public interface AssetRepository extends PageCrudRepository<Asset, Long> {
#Query("SELECT a FROM Asset a WHERE " +
"(a.aerodrome = :aerodrome)")
<T> Page<T> findPageByFilter(#Param("aerodrome") Aerodrome aerodrome,
Pageable pageable, Class<T> type);
<T> Page<T> findPageByAerodrome(#Param("aerodrome") Aerodrome aerodrome,
Pageable pageable, Class<T> type);
,,,
Use this instead
<T> Page<T> findAll(Specification s, Pageable pageable, Class<T> type);
and pass a custom Specification where you create your equality filter like this:
<T> Page<T> findPageByFilter(#Param("aerodrome") Aerodrome aerodrome,
Pageable pageable, Class<T> type) {
return findAll(
new Specification() {
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.equal(root.get("aerodrome"), aerodrome);
}
},
pageable,
type
);
}
Figured it out. I need to add an entity specific constructor.
'''
public AssetPageView(Asset asset) {
this ( asset.getId(), asset.getAssetType(), asset.getAerodrome(), asset.getName(),
asset.getAreaOfOperationsLoc(), asset.getDisplayOrder() );
}
public AssetPageView(Long id, AssetType assetType, Aerodrome aerodrome, String name,
String areaOfOperationsLoc, Long displayOrder) {
this.id = id;
this.assetType = assetType.getName();
this.aerodrome = aerodrome.getCallSign();
this.name = name;
this.areaOfOperationsLoc = areaOfOperationsLoc;
this.displayOrder = displayOrder;
}
'''

Spring InvalidDataAccessApiUsageException: Parameter value element did not match expected type

I already answered this question but got no feedback. So I'll try to ask the question different.
I want to use Specifications for my search requests. But it seems like the specification is not accessible or something, because it tells me:
javax.el.ELException: org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [com.auth0.samples.bootfaces.TelefonbuchSpecifications$1#20079547] did not match expected type [java.lang.String (n/a)];
This: [com.auth0.samples.bootfaces.TelefonbuchSpecifications$1#20079547]
with an #... does not seem to be right.
I just tried to follow this official Spring Tutorial: https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/ or the one from DZone is the same https://dzone.com/articles/using-spring-data-jpa-specification
My classes:
public interface TelefonbuchRepository extends JpaRepository<Telefonbuch, Long>, JpaSpecificationExecutor<Telefonbuch> {
public List<Telefonbuch> findByVornameOrNachname(String vorname, String nachname);
public List<Telefonbuch> findByVorname(Specification<Telefonbuch> spec);
}
TelefonbuchSpecifications:
public class TelefonbuchSpecifications implements Specification<Telefonbuch> { //implements was a try
public static Specification<Telefonbuch> hasVorname(String vorname) {
return (root, query, cb) -> {
//return cb.equal(root.get(Telefonbuch_.vorname), "%"+vorname.toLowerCase()+"%");
Predicate equalPredicate = cb.like(cb.lower(root.get(Telefonbuch_.vorname)), "%"+vorname.toLowerCase()+"%");
return equalPredicate;
};
}
#Override
public Predicate toPredicate(Root<Telefonbuch> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
// TODO Auto-generated method stub
return null;
}
Method in SearchController:
public void search(String vorname, String nachname, String telefonnummer, String handynummer) {
List<Telefonbuch> list = telefonbuchRepository.findByVorname(TelefonbuchSpecifications.hasVorname(vorname));
vorname is a String defined in my Telefonbuch Model. If you need more classes or information please tell me. I'm very frustrated. Here also a picture of the project:
Thanks to #M.Deinum
the solution was a bit bigger.
Delete the Classes Specification & TelefonbuchSpecifications.
And just insert the TelefonbuchSpecifications as helper class in TelefonbuchRepository
public interface TelefonbuchRepository extends JpaRepository<Telefonbuch, Long>, JpaSpecificationExecutor<Telefonbuch> {
public class TelefonbuchSpecifications {
public static Specification<Telefonbuch> hasVorname(String vorname) {
return (root, query, cb) -> {
return cb.like(cb.lower(root.get(Telefonbuch_.vorname)), "%" + vorname.toLowerCase() + "%");
};
}
public static Specification<Telefonbuch> hasNachname(String nachname) {
return (root, query, cb) -> {
return cb.like(cb.lower(root.get(Telefonbuch_.nachname)), "%" + nachname.toLowerCase() + "%");
};
}
}
}
For the method search in SucheController then:
List<Telefonbuch> list = telefonbuchRepository.findAll(TelefonbuchSpecifications.hasVorname(vorname));

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

Passing array of reg expressions to spring based mongo #Query

I'm using Spring boot with mongodb. I've extended PagingAndSortingRepository repository and added the following function
#Query("{'title':{ $nin: [?0]}}")
List<Item> findItem(String[] exclude);
I want to be able to pass it an array of regular expressions such as /dog/,/cat/,/horse/ to exclude any item that may have one of these in it's title.
The above function does not work because the exclude is converted to a string. How can I pass an array of regular expressions to be able to do the above?
You can work it out by using a Querydsl predicate in one of your controller method.
Add something like this to your controller:
#RequestMapping(value="/search/findByNameRegexNotIn", method = RequestMethod.GET)
#ResponseBody
public List<Item> findByNameRegexNotIn(#RequestParam(name = "name") List<String> names) {
// build a query predicate
BooleanBuilder predicate = new BooleanBuilder(); // comes from the Querydsl library
for (String name : names) {
predicate.and(QItem.item.name.contains(name).not()); // the QItem class is generated by Querydsl
}
List<Item> items = (List<Item>)repository.findAll(predicate);
return items;
}
You can of course add a Pageable parameter and return a Page<Item> instead of a List.
Edit: another solution if you use Querydsl for this sole purpose is to override the default bindings of your query parameter.
public interface ItemRepository extends CrudRepository<Item, String>,
QueryDslPredicateExecutor<Item>, QuerydslBinderCustomizer<QItem> {
#Override
default public void customize(QuerydslBindings bindings, QItem item) {
bindings.bind(item.name).all(
(path, values) -> path.matches(StringUtils.collectionToDelimitedString(values, "|")).not());
// disable query on all parameters but the item name
bindings.including(item.name);
bindings.excludeUnlistedProperties(true);
}
}
The controller method:
#RequestMapping(value="/search/query", method = RequestMethod.GET)
#ResponseBody
public List<Item> queryItems(
#QuerydslPredicate(root = Item.class) Predicate predicate) {
List<Item> items = (List<Item>)repository.findAll(predicate);
return items;
}
Edit: if you don't wan't to override the default QuerydslBinderCustomizer#customize, you can also implement your own binder and specify it in the controller method.
public interface ItemRepository extends CrudRepository<Item, String>,
QueryDslPredicateExecutor<Item> {
...
}
The controller method:
#RequestMapping(value="/search/query", method = RequestMethod.GET)
#ResponseBody
public List<Item> queryItems(
#QuerydslPredicate(root = Item.class, bindings = ItemBinder.class) Predicate predicate) {
List<Item> items = (List<Item>)repository.findAll(predicate);
return items;
}
The binder class:
class ItemBinder implements QuerydslBinderCustomizer<QItem> {
#Override
public void customize(QuerydslBindings bindings, QItem item) {
bindings.bind(item.name).all(
(path, values) -> path.matches(StringUtils.collectionToDelimitedString(values, "|")).not()
);
bindings.including(item.name);
bindings.excludeUnlistedProperties(true);
}
}
Edit: for the sake of exhaustivity and those who don't want to hear about Querysl. Using the solution proposed in Spring Data Mongodb Reference.
Define a custom repository interface:
interface ItemRepositoryCustom {
public Page<Item> findByNameRegexIn(Collection<String> names, Pageable page);
}
Define an custom repository implementation (Impl postfix required!):
public class ItemRepositoryImpl implements ItemRepositoryCustom {
#Autowired
private MongoOperations operations;
#Override
public Page<Item> findByNameRegexNotIn(Collection<String> names, Pageable pageable) {
String pattern = StringUtils.collectionToDelimitedString(names, "|");
// this time we use org.springframework.data.mongodb.core.query.Query instead of Querydsl predicates
Query query = Query.query(where("name").regex(pattern).not()).with(pageable);
List<Item> items = operations.find(query, Item.class);
Page<Item> page = new PageImpl<>(items, pageable, items.size());
return page;
}
}
Now simply extend ItemRepositoryCustom:
public interface ItemRepository extends MongoRepository<Item, String>, ItemRepositoryCustom {
...
}
And you're done!
You can pass a java.util.regex.Pattern[] to the method. This will be converted to regex array under the hood:
#Query("{'title':{ $nin: ?0}}")
List<Item> findItem(Pattern[] exclude);

Resources