I've got problem from real-life scenario. I am not expert on spring boot so sorry again if that's easy question. I am using Java 17, spring boot, jpa repository and postgresql driver.
The task is: From query paramters from url-path find all entities from db by query parameters. These entities have to have a dateOfDeletion == null.
My approach is this:
@Data
@Entity
@Table(name="x", schema = "\"xx\"")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(nullable = false)
private UUID id;
private String field1;
private String field2;
private String field3;
private Timestamp dateOfDeletion;
}
The Path is localhost:8080/myEndpoint?field1=a&field3=b .
I will setup a MyEntity like this for example ( this doesn't really matter ).
MyEntity queryExample = new MyEntity();
queryExample.setField1("a");
queryExample.setField3("b");
I need to find all substrings in field1 field2 and field3, so I created a customMatcher.
ExampleMatcher customMatcher = ExampleMatcher.matchingAll().
withMatcher("field1",ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
withMatcher("field2",ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
withMatcher("field3",ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
Example<MyEntity> example = Example.of(queryExample,customMatcher);
return repository.findAll(example,myPageable);
The result of this findAll will be all entities which containts in field1 string "a" and in field3 string "b".
But it will also show all entites which are not valid ( valid entity has dateOfDeletion == null )
The question is : what is the best approach? Thank you for all answers.
We can't use stream after completing the transaction because if we use findAll(example,pageable) it would be not valid( inconsistentcy in paging ) So this solution in not valid.
repository.findAll(example).stream().filter(ent -> ent.getDateOfDeletion() == null).collect(Collectors.toList());
So after long time of googling I have finally find my answer. The solutions is to use Specification and Example.of.
First you have to defined a repository to use JPA Specifications:
public interface MyExperiment extends JpaRepository<Yourentity, UUID> , JpaSpecificationExecutor<AdrSbj> {
@Override
List<AdrSbj> findAll(Specification<AdrSbj> spec);
}
Then implement a custom class which extracts predicates from your Example and returns them as Specifications.
public class SpecificationsFromExample<T> {
public Specification<T> get(Example<T> example) {
return (Specification<T>) (root, query, builder) -> {
List<Predicate> predicates = new ArrayList<>();
try {
Predicate examplePredicates = QueryByExamplePredicateBuilder.getPredicate(root, builder, example);
predicates.add(examplePredicates);
return builder.and(predicates.toArray(new Predicate[predicates.size()]));
}
catch (NullPointerException e){
return null;
}
};
}
}
Then create specifications only for dateOfDeletion.
Specification<T> specDateOfDeletion = (root, query, cb) -> cb.and(cb.isNull(root.get("dateOfDeletion")));
And finally use it in service. ( combine spec from example and date )
Specification<T>specFromExample = new SpecificationsFromExample<T>().get(example);
if( specFromExample != null ) {
return findAll(specDateOfDeletion.and(specFromExample);
}