How to initialize Specification of Spring Data JPA? - java

I have a method that does a search with filters, so I'm using Specification to build a dynamic query:
public Page<Foo> searchFoo(#NotNull Foo probe, #NotNull Pageable pageable) {
Specification<Foo> spec = Specification.where(null); // is this ok?
if(probe.getName() != null) {
spec.and(FooSpecs.containsName(probe.getName()));
}
if(probe.getState() != null) {
spec.and(FooSpecs.hasState(probe.getState()));
}
//and so on...
return fooRepo.findAll(spec, pageable);
}
There is the possibility that there are no filters specified, so I would list everything without filtering. So having that in mind, how I should initialize spec ? Right now, the code above doesn't work as it always returns me the same result: all the registers of the table, no filtering have been aplied althought and operations have been made.
FooSpecs:
public class PrescriptionSpecs {
public static Specification<Prescription> containsCode(String code) {
return (root, criteriaQuery, criteriaBuilder) ->
criteriaBuilder.like(root.get(Prescription_.code), "%" + code + "%");
}
// some methods matching objects...
public static Specification<Prescription> hasContractor(Contractor contractor) {
return (root, criteriaQuery, criteriaBuilder) ->
criteriaBuilder.equal(root.get(Prescription_.contractor), contractor);
}
//... also some methods that access nested objects, not sure about this
public static Specification<Prescription> containsUserCode(String userCode) {
return (root, criteriaQuery, criteriaBuilder) ->
criteriaBuilder.like(root.get(Prescription_.user).get(User_.code), "%" + userCode + "%");
}
}

Specification.where(null) works just fine.
It is annotated with #Nullable and the implementation handles null values as it should.
The problem is that you are using the and method as if it would modify the Specification, but it creates a new one. So you should use
spec = spec.and( ... );

Related

Spring Data JPA Specification - distinct by value

I'm not an expert in Data JPA Specs
but I want to build a specification that allow me to filter with my criteria and then make a distinct by on a field.
I build my specifcation like this
public static Specification<Mission> withCriteria(MissionCriteria criteria) {
Specification<Mission> specs = criteria(criteria);
if (criteria.getClient() != null) {
specs = specs.and(clientMethod(criteria.getClient()));
}
if (criteria.getProjet() != null) {
specs = specs.and(projetMethod(criteria.getProjet()));
}
with these functions called to apply my criteria to the query
private static Specification<Mission> client(Long client) {
return (root, query, builder) -> {
return builder.equal(root.get("client"), client);
};
}
private static Specification<Mission> projet(Long projet) {
return (root, query, builder) -> {
return builder.equal(root.get("projet").get("id"), projet);
};
}
And at the end of my withCriteria method, I want my query to make a distinct by on an external field of my initial table.
I have this
private static Specification<Mission> distinctByCollaboratorId(){
return (root, query, builder) -> {
query.distinct(true);
return builder.and(root.get("collaborator").get("id").isNotNull());
};
}
But that doesn't work and I litteraly have no idea how to build it clean with my root query builder :-) help appreciated

Dynamic combination of multiple PredicateSpecification with JpaSpecificationExecutor with Micronuat data for MongoDB

How can I append all the PredicateSpecification or QuerySpecification using JpaSpecificationExecutor in MongoDB with Micronaut data?
Predicate
public class DiscountSpecification {
final static BeanIntrospection<Discount> introspection = BeanIntrospection.getIntrospection(Discount.class);
static String[] discountProperty = introspection.getPropertyNames();
public static PredicateSpecification<Discount> DiscountIdEquals(ObjectId DiscountId) {
return (root, criteriaBuilder) -> criteriaBuilder.equal(root.get(discountProperty[0]), DiscountId);
}
public static PredicateSpecification<Discount> DiscountCodeEquals(String DiscountCode) {
return (root, criteriaBuilder) -> criteriaBuilder.equal(root.get(discountProperty[1]), DiscountCode);
}
}
Now based on the condition I want to append all the predicate
#Introspected
public record QueryBuilderSpecification() {
public static PredicateSpecification<Discount> QueryBuilder(DiscountFilterModel discountFilterModel){
PredicateSpecification<Discount> predicateSpecification = null;
if (discountFilterModel.getId() != null){
predicateSpecification = DiscountIdEquals(new ObjectId(discountFilterModel.getId()));
}
if (discountFilterModel.getDiscountCode() != null){
predicateSpecification = DiscountCodeEquals(discountFilterModel.getDiscountCode());
}
return predicateSpecification;
}
}
The above code doesn't append for both the condition. Quite not sure how to combine both if both are present.
It should be possible to use DiscountIdEquals(...).and(DiscountCodeEquals(..)).
Your example can be used with PredicateSpecification.ALL as the initial state and then have:
predicateSpecification = predicateSpecification.and(DiscountIdEquals(...)).
There are some examples in the test apps:
https://github.com/micronaut-projects/micronaut-data/blob/master/doc-examples/mongo-example-java/src/test/java/example/PersonRepositorySpec.java#L50
Based on the code PredicateSpecification you can and & or specifications together. As demonstrated in the Micronaut Data User Guide

Predicate alternative in Kotlin

I try to create lambda in Kotlin.
I has following Java interface:
public interface Specification<T> extends Serializable {
#Nullable
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder);
}
And in Java I can return new Specification from method like:
private Specification<Product> nameLike(String name){
return new Specification<Product>() {
#Override
public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.like(root.get(Product_.NAME), "%"+name+"%");
}
};
}
And with Java 8 I can cut it to labda like:
private Specification<Product> nameLike(String name) {
return (root, query, criteriaBuilder)
-> criteriaBuilder.like(root.get(Product_.NAME), "%"+name+"%");
}
How to do it in Kotlin with lamba? I already tried many options, but they do not compile. help please.
Update:
Last option in Kotlin:
class ProductSpecification {
fun nameLike(name: String): (Root<Product>, CriteriaQuery<Product>, CriteriaBuilder) -> Predicate = {
root, query, builder -> builder.like(root.get("name"), "%$name%")
}
}
It compiles, but when I pass it in function with argument Specification, I have error None of the following functions can be called with the arguments supplied.. Code example of invoking:
repository.findAll(ProductSpecification().nameLike("fff"))
I found solution! When I started to implement roughly option like of the second code example in my question, IDE suggests to do like:
fun nameLike(name: String) = Specification { root: Root<Product>, query: CriteriaQuery<*>, builder: CriteriaBuilder ->
builder.like(root.get("name"), "%$name%")
}
That's fine for me

How do you make a spring data custom query method that has the following signature '(x OR y) AND z'

I have the following repository with 2 custom query methods:
#Repository
public interface CropVarietyNameDao extends JpaRepository<CropVarietyName, Long> {
//https://stackoverflow.com/questions/25362540/like-query-in-spring-jparepository
Set<CropVarietyName> findAllByNameIgnoreCaseContainingOrScientificNameIgnoreCaseContaining(String varietyName, String scientificName);
Set<CropVarietyName> findAllByNameIgnoreCaseStartsWithOrScientificNameIgnoreCaseStartsWith(String varietyName, String scientificName);
}
I need to add an additional condtion, namely parent entity called crop deleted = false.
If i change a method to the following:
findAllByNameIgnoreCaseContainingOrScientificNameIgnoreCaseContainingAndCrop_deletedIsFalse(String varietyName, String scientificName);
Then most likely it will interpret the query as (Name containing OR (scientificNameContaining AND crop_deleted = false), but thats not how i want it.
It needs to be (NameContaining OR scientificNameContaining) AND crop_deleted = false)
My guess is that I have to add the AND crop_deleted part to both parts, but that seems inefficient. How can i write a method thats essentially (NameContaining OR scientificNameContaining) AND crop_deleted = false) without having to use #Query?
You could try JPA Specification -
#Repository
public interface CropVarietyNameDao extends JpaRepository<CropVarietyName, Long>,
JpaSpecificationExecutor<CropVarietyName> {
}
public class CropVarietySpecs {
public static Specification<CropVarietyName> cropPredicate(String varietyName, String sciName, boolean cropStatus) {
return new Specification<CropVarietyName>() {
#Override
public Predicate toPredicate(Root<CropVarietyName> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
Predicate varietyContainingIgnoreCasePredicate = criteriaBuilder.like(criteriaBuilder.lower(root.get("<column_name>")), varietyName.toLowerCase());
Predicate scientificContainingIgnoreCasePredicate = criteriaBuilder.like(criteriaBuilder.lower(root.get("<column_name>")), sciName.toLowerCase());
Predicate cropStatusPredicate = criteriaBuilder.equal(root.get("<column_name>"), cropStatus);
predicates.add(varietyContainingIgnoreCasePredicate);
predicates.add(scientificContainingIgnoreCasePredicate);
criteriaBuilder.or(predicates.toArray(new Predicate[predicates.size()]));
return criteriaBuilder.and(cropStatusPredicate);
}
};
}
}
Then you can call the findAll() method of your repository like -
List<CropVarietyName> entities = cropVarietyNameDao.findAll(CropVarietySpecs.cropPredicate("varietyName", "sciName", false));

JPA/Criteria Like percent character

I am trying to build dynamic query (with like condition). When I try it with raw sql into the mysql , it worked very well. Here is an example:
select * from dbiwant.want_activity m where m.want like "%bike%"
Then, I've tried it with the CriteriaBuilder but I couldn't achieve to get the same results in Java. Just empty result. Here is my code:
Specifications specifications = Specifications.where(specificationService.isLike("want", search));
public Specification<WantActivity> isLike(String param, String search) {
return new Specification<WantActivity>() {
public javax.persistence.criteria.Predicate toPredicate(Root<WantActivity> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
return builder.like(root.get(param) ,"%" + search + "%");
}
};
}
What's the problem?
try this:
public Specification<WantActivity> isLike(String param, String search) {
return new Specification<WantActivity>() {
public javax.persistence.criteria.Predicate toPredicate(Root<WantActivity> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
final Predicate like = query.where(builder.like(root.<String> get(param), search)).getRestriction();
return like;
}
};

Categories

Resources