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
Related
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
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));
I've done some coding with the use of toPredicate() method now I want to refactor it so that I can use it in Java 7 also.
I've posted below some sample code which I've done so far.
EntitySpecification.java
public class EntitySpecification {
public static Specification<MyEntity> textInAllColumns(String text) {
if (!text.contains("%")) {
text = "%"+text+"%";
}
final String finalText = text;
return new Specification<MyEntity>() {
#Override
public Predicate toPredicate(Root<MyEntity> root, CriteriaQuery<?> cq, CriteriaBuilder builder) {
return builder.or(root.getModel().getDeclaredSingularAttributes().stream().filter(a-> {
if (a.getJavaType().getSimpleName().equalsIgnoreCase("string")) {
return true;
}
else {
return false;
}}).map(a -> builder.like(root.get(a.getName()), finalText)
).toArray(Predicate[]::new)
);
}
};
}
}
The lambda expressions (->) came with Java 8. In order to use the code in Java 7 you have to replace them with anonymous classes.
If you use and IDE like IntelliJ it can do the job for you. Move the cursor to the -> and then hit ALT + ENTER. A popup window should show up and there should be an option to Replace lambda with anonymous class.
.filter(a -> {
if (a.getJavaType().getSimpleName().equalsIgnoreCase("string")) {
return true;
} else {
return false;
}
})
to
.filter(new java.util.function.Predicate<SingularAttribute<MyEntity, ?>>() {
#Override
public boolean test(SingularAttribute<MyEntity, ?> a) {
if (a.getJavaType().getSimpleName().equalsIgnoreCase("string")) {
return true;
} else {
return false;
}
}
})
Also you have to get rid of everything you are using from the java.util.function package.
You can replace the .filter() with a for loop and an if statement inside of it. For the .map() you have to modify the previously filtered collection with a for loop.
new Specification<MyEntity>() {
#Override
public Predicate toPredicate(Root<MyEntity> root, CriteriaQuery<?> cq, CriteriaBuilder builder) {
List<SingularAttribute<MyEntity, ?>> tempAttributes = new ArrayList<>();
for (SingularAttribute<MyEntity, ?> attribute : root.getModel().getDeclaredSingularAttributes()) {
if (attribute.getJavaType().getSimpleName().equalsIgnoreCase("string")) {
tempAttributes.add(attribute);
}
}
final Predicate[] predicates = new Predicate[tempAttributes.size()];
for (int i = 0; i < tempAttributes.size(); i++) {
predicates[i] = builder.like(root.<MyEntity>get(tempAttributes.get(i).getName()), finalText);
}
return builder.or(predicates);
}
};
I didn't tried it myself but this should work or at least give you the first steps.
You have to replace
- stream
- filter
- map
because there is no Streaming API in Java 7.
By replace I mean you have to loop over getDeclaredSingularAttributes() and the filter the elements and map it.
Also Predicate[]::new has to be replaced by new Predicate[] because there are now method references.
As Rashin said this can be done with the IDE if you set the source level to Java 7 it will provide help.
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( ... );
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;
}
};