Spring Data JPA Specification - distinct by value - java

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

Related

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

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

How to translate dynamic query from QueryDSL into JOOQ?

I use QueryDSL just for dynamic queries in Spring Boot 2+ with Spring Data JPA applications in the following manner:
#Override
public Iterable<Books> search(String bookTitle, String bookAuthor, String bookGenre) {
BooleanBuilder where = dynamicWhere(bookTitle, bookAuthor, bookGenre);
return booksRepository.findAll(where, orderByTitle());
}
public BooleanBuilder dynamicWhere(String bookTitle, String bookAuthor, String bookGenre) {
QBooks qBooks = QBooks.books;
BooleanBuilder where = new BooleanBuilder();
if (bookTitle != null) {
where.and(qBooks.title.equalsIgnoreCase(bookTitle));
}
if (bookAuthor!= null) {
where.and(qBooks.author.eq(bookAuthor));
}
if (bookGenre!= null) {
where.and(qBooks.genre.eq(bookGenre));
}
return where;
}
I want to use JOOQ in a similar transparent way, but I do not know how to do it elegantly. I also like that in JOOQ there isn't QBooks-like generated constructs, although I think JOOQ also generates some tables. Anyhow, I am confused and I couldn't find an answer online, so I am asking can it be done and how.
Thanks
jOOQ doesn't have a specific "builder" to construct your predicates. All the "building" API is located directly on the predicate type, i.e. the Condition
#Override
public Iterable<Books> search(String bookTitle, String bookAuthor, String bookGenre) {
Condition where = dynamicWhere(bookTitle, bookAuthor, bookGenre);
return dslContext.selectFrom(BOOKS)
.where(where)
.orderBy(BOOKS.TITLE)
.fetchInto(Books.class);
}
public Condition dynamicWhere(String bookTitle, String bookAuthor, String bookGenre) {
Condition where = DSL.noCondition();
if (bookTitle != null) {
where = where.and(BOOKS.TITLE.equalsIgnoreCase(bookTitle));
}
if (bookAuthor!= null) {
where = where.and(BOOKS.AUTHOR.eq(bookAuthor));
}
if (bookGenre!= null) {
where = where.and(BOOKS.GENRE.eq(bookGenre));
}
return where;
}
This is assuming:
1) That you have injected a properly configured DSLContext
2) That you're using jOOQ's code generator
For more information, see also https://www.jooq.org/doc/latest/manual/sql-building/dynamic-sql/

How to initialize Specification of Spring Data JPA?

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( ... );

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