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/
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
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
I want to dynamic search with Criteria API in Java.
In the code I wrote, we need to write each entity in the url bar in JSON. I don't want to write "plaka".
The URL : <localhost:8080/api/city/query?city=Ankara&plaka=> I want to only "city" or "plaka"
Here we need to write each entity, even if we are going to search with only 1 entity. Type Entity and it should be empty.
My code is as below. Suppose there is more than one entity, what I want to do is to search using a single entity it wants to search. As you can see in the photo, I don't want to write an entity that I don't need. can you help me what should I do?
My code in Repository
public interface CityRepository extends JpaRepository<City, Integer> , JpaSpecificationExecutor<City> {
}
My code in Service
#Service
public class CityServiceImp implements CityService{
private static final String CITY = "city";
private static final String PLAKA = "plaka";
#Override
public List<City> findCityByNameAndPlaka(String cityName, int plaka) {
GenericSpecification genericSpecification = new GenericSpecification<City>();
if (!cityName.equals("_"))
genericSpecification.add(new SearchCriteria(CITY,cityName, SearchOperation.EQUAL));
if (plaka != -1)
genericSpecification.add(new SearchCriteria(PLAKA,plaka, SearchOperation.EQUAL));
return cityDao.findAll(genericSpecification);
}
#Autowired
CityRepository cityDao;
My code in Controller
#RestController
#RequestMapping("api/city")
public class CityController {
#Autowired
private final CityService cityService;
public CityController(CityService cityService) {
this.cityService = cityService;
#GetMapping("/query")
public List<City> query(#RequestParam String city, #RequestParam String plaka){
String c = city;
int p;
if (city.length() == 0)
c = "_";
if (plaka.length() == 0) {
p = -1;
}
else
p = Integer.parseInt(plaka);
return cityService.findCityByNameAndPlaka(c,p);
}
My code in SearchCriteria
public class SearchCriteria {
private String key;
private Object value;
private SearchOperation operation;
public SearchCriteria(String key, Object value, SearchOperation operation) {
this.key = key;
this.value = value;
this.operation = operation;
}
public String getKey() {
return key;
}
public Object getValue() {
return value;
}
public SearchOperation getOperation() {
return operation;
}
My code in GenericSpecification
public class GenericSpecification<T> implements Specification<T> {
private List<SearchCriteria> list;
public GenericSpecification() {
this.list = new ArrayList<>();
}
public void add(SearchCriteria criteria){
list.add(criteria);
}
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
for (SearchCriteria criteria : list) {
if (criteria.getOperation().equals(SearchOperation.GREATER_THAN)) {
predicates.add(builder.greaterThan(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.LESS_THAN)) {
predicates.add(builder.lessThan(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.GREATER_THAN_EQUAL)) {
predicates.add(builder.greaterThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.LESS_THAN_EQUAL)) {
predicates.add(builder.lessThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.NOT_EQUAL)) {
predicates.add(builder.notEqual(
root.get(criteria.getKey()), criteria.getValue()));
} else if (criteria.getOperation().equals(SearchOperation.EQUAL)) {
predicates.add(builder.equal(
root.get(criteria.getKey()), criteria.getValue()));
} else if (criteria.getOperation().equals(SearchOperation.MATCH)) {
predicates.add(builder.like(
builder.lower(root.get(criteria.getKey())),
"%" + criteria.getValue().toString().toLowerCase() + "%"));
} else if (criteria.getOperation().equals(SearchOperation.MATCH_END)) {
predicates.add(builder.like(
builder.lower(root.get(criteria.getKey())),
criteria.getValue().toString().toLowerCase() + "%"));
}
}
return builder.and(predicates.toArray(new Predicate[0]));
}
My code in SearchOperation
public enum SearchOperation {
GREATER_THAN,
LESS_THAN,
GREATER_THAN_EQUAL,
LESS_THAN_EQUAL,
NOT_EQUAL,
EQUAL,
MATCH,
MATCH_END,
}
The good thing about the Criteria API is that you can use the CriteriaBuilder to build complex SQL statements based on the fields that you have. You can combine multiple criteria fields using and and or statements with ease.
How I approached something similar int he past is using a GenericDao class that takes a Filter that has builders for the most common operations (equals, qualsIgnoreCase, lessThan, greaterThan and so on). I actually have something similar in an open-source project I started: https://gitlab.com/pazvanti/logaritmical/-/blob/master/app/data/dao/GenericDao.java
https://gitlab.com/pazvanti/logaritmical/-/blob/master/app/data/filter/JPAFilter.java
Next, the implicit DAO class extends this GenericDao and when I want to do an operation (ex: find a user with the provided username) and there I create a Filter.
Now, the magic is in the filter. This is the one that creates the Predicate.
In your request, you will receive something like this: field1=something&field2=somethingElse and so on. The value can be preceded by the '<' or '>' if you want smaller or greater and you initialize your filter with the values. If you can retrieve the parameters as a Map<String, String>, even better.
Now, for each field in the request, you create a predicate using the helper methods from the JPAFilter class and return he resulted Predicate. In the example below I assume that you don't have it as a Map, but as individual fields (it is easy to adapt the code for a Map):
public class SearchFilter extends JPAFilter {
private Optional<String> field1 = Optional.empty();
private Optional<String> field2 = Optional.empty();
#Override
public Predicate getPredicate(CriteriaBuilder criteriaBuilder, Root root) {
Predicate predicateField1 = field1.map(f -> equals(criteriaBuilder, root, "field1", f)).orElse(null);
Predicate predicateField2 = field2.map(f -> equals(criteriaBuilder, root, "field2", f)).orElse(null);
return andPredicateBuilder(criteriaBuilder, predicateField1, predicateField2);
}
}
Now, I have the fields as Optional since in this case I assumed that you have them as Optional in your request mapping (Spring has this) and I know it is a bit controversial to have Optional as input params, but in this case I think it is acceptable (more on this here: https://petrepopescu.tech/2021/10/an-argument-for-using-optional-as-input-parameters/)
The way the andPredicateBuilder() is made is that it works properly even if one of the supplied predicates is null. Also, I made s simple mapping function, adjust to include for < and >.
Now, in your DAO class, just supply the appropriate filter:
public class SearchDao extends GenericDAO {
public List<MyEntity> search(Filter filter) {
return get(filter);
}
}
Some adjustments need to be made (this is just starter code), like an easier way to initialize the filter (and doing this inside the DAO) and making sure that that the filter can only by applied for the specified entity (probably using generics, JPAFIlter<T> and having SearchFilter extends JPAFilter<MyEntity>). Also, some error handling can be added.
One disadvantage is that the fields have to match the variable names in your entity class.
Can you please help me in solving this problem. I am trying to order the results of an criteria query by date, but I'm not getting the results I need.I saved date in String format,how can i order by date using criteria
The code I'm using is:
#Override
public List<Program> getListProgram() {
Session session=sessionFactory.openSession();
Criteria criteria=session.createCriteria(Program.class);
criteria.addOrder(Order.asc("createdDate"));
List<Program> programs=(List<Program>)criteria.list();
return programs;
}
Results are:
01/02/2009
03/01/2009
04/06/2009
05/03/2009
06/12/2008
07/02/2009
Results should be:
06/12/2008
03/01/2009
01/02/2009
07/02/2009
I need to select the date in the format above.
Your help is much appreciated.
You have to call criteria.addOrder(Order.asc("createdDate")); before executing the list method on criteria.
#Override
public List<Program> getListProgram() {
Session session=sessionFactory.openSession();
Criteria criteria=session.createCriteria(Program.class);
criteria.addOrder(Order.asc("createdDate"));
List<Program> programs=(List<Program>)criteria.list();
return programs;
}
EDIT
In your case, if you want to order by String dates, as i mentionned in the comments, this answer is not the proper you can get ( may be turning creationDate into a Date type is the best! for sure).
You can try some code like :
static final String DF = "DD/MM/YYYY";
static final SimpleDateFormat SDF = new SimpleDateFormat(DF);
#Override
public List<Program> getListProgram() {
Session session=sessionFactory.openSession();
Criteria criteria=session.createCriteria(Program.class);
List<Program> =(List<Program>)criteria.list();
boolean asc = true;
programs.sort((a, b) -> {
int comparison = 0;
try {
comparison = SDF.parse(a.getCreatedDate()).compareTo(SDF.parse(b.getCreatedDate()));
} catch (ParseException e) {
// handle it!!
}
return asc ? comparison : (0-comparison);
});
return programs;
}
EDIT 2
If you want to avoid using lambdas, try using this instead :
Collections.sort(programs, new Comparator<Main>() {
#Override
public int compare(Program a, Program b) {
int comparison = 0;
try {
comparison = SDF.parse(a.getCreatedDate()).compareTo(SDF.parse(b.getCreatedDate()));
} catch (ParseException e) {
// handle it!!
}
return asc ? comparison : (0-comparison);
}
});
This is how Implemented it
private List<Users> findUsers(boolean all, int maxResults, int firstResult, SchoolData data) {
EntityManager em = getEntityManager(data.getExternalId());
try {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery();
Root<Users> from = cq.from(Users.class);
cq.orderBy(cb.desc(from.get("dateCreated")));
cq.select(cq.from(Users.class));
Query q = em.createQuery(cq);
if (!all) {
q.setMaxResults(firstResult);
q.setFirstResult(maxResults);
}
return q.getResultList();
} finally {
em.close();
}
}
Criteria are passed to Hibernate and are translated to the certain language. They appear after where clause in the select so they all the limitations must be set before executing query. Although you don't see it, there is a query generated and executed when calling criteria.list().
Try the following:
#Override
public List<Program> getListProgram() {
Session session=sessionFactory.openSession();
Criteria criteria=session.createCriteria(Program.class);
criteria.addOrder(Order.asc("createdDate"));
List<Program> programs=(List<Program>)criteria.list();
return programs;
}
Firstly, dates should be stored as dates, not strings. If you are working with a legacy system, it may not be possible to change this, but I'd argue that it probably would make your life easier to bite the bullet and refactor.
As a string, the sort function is working because 05 comes before 06.
I'd suggest using annotation or mapping, as outlined in this question, to convert your string to a date in the pojo so that it is the correct type at least somewhere in your application.
public class StringToDateConverter implements AttributeConverter<Date, String> {
String convertToDatabaseColumn(Date attribute) { /* Conversion code */ }
Date convertToEntityAttribute(String dbData) { /* Conversion code */ }
}
and
public class MyPojo {
#javax.persistence.Convert(converter = StringToDateConverter.class)
public Date getCreateDate() {
}
}
I have the following classes:
class ServiceSnapshot {
List<ExchangeSnapshot> exchangeSnapshots = ...
...
}
class ExchangeSnapshot{
Map<String, String> properties = ...
...
}
SayI have a collection of ServiceSnapshots, like so:
Collection<ServiceSnapshot> serviceSnapshots = ...
I'd like to filter the collection so that the resulting collection of ServiceSnapshots only contains ServiceSnapshots that contain ExchangeSnapshots where a property on the ExchangeSnapshots matches a given String.
I have the following untested code, just wondering is there a cleaner/more readable way to do this, using Java 7, and maybe Google Guava if necessary?
Updtae: Note also that the code sample I've provided below isn't suitable for my purposes, since I'm using iterator.remove() to filter the collection. It turns out I cannot do this as it is modifying the underlying collection , meaning subsequent calls to my method below result in fewer and fewer snashots due to previous calls removing them from the collection - this is not what I want.
public Collection<ServiceSnapshot> getServiceSnapshotsForComponent(final String serviceId, final String componentInstanceId) {
final Collection<ServiceSnapshot> serviceSnapshots = getServiceSnapshots(serviceId);
final Iterator<ServiceSnapshot> serviceSnapshotIterator = serviceSnapshots.iterator();
while (serviceSnapshotIterator.hasNext()) {
final ServiceSnapshot serviceSnapshot = (ServiceSnapshot) serviceSnapshotIterator.next();
final Iterator<ExchangeSnapshot> exchangeSnapshotIterator = serviceSnapshot.getExchangeSnapshots().iterator();
while (exchangeSnapshotIterator.hasNext()) {
final ExchangeSnapshot exchangeSnapshot = (ExchangeSnapshot) exchangeSnapshotIterator.next();
final String foundComponentInstanceId = exchangeSnapshot.getProperties().get("ComponentInstanceId");
if (foundComponentInstanceId == null || !foundComponentInstanceId.equals(componentInstanceId)) {
exchangeSnapshotIterator.remove();
}
}
if (serviceSnapshot.getExchangeSnapshots().isEmpty()) {
serviceSnapshotIterator.remove();
}
}
return serviceSnapshots;
}
Using Guava:
Iterables.removeIf(serviceSnapshots, new Predicate<ServiceSnapshot>() {
#Override
public boolean apply(ServiceSnapshot serviceSnapshot) {
return !Iterables.any(serviceSnapshot.getExchangeSnapshots(), new Predicate<ExchangeSnapshot>() {
#Override
public boolean apply(ExchangeSnapshot exchangeSnapshot) {
String foundComponentInstanceId = exchangeSnapshot.getProperties().get("ComponentInstanceId");
return foundComponentInstanceId != null && foundComponentInstanceId.equals(componentInstanceId);
}
});
}
});
I may have a ! missing or inverted somewhere, but the basic strategy is to remove any ServiceSnapshot objects that do not have any ExchangeSnapshot whose ID matches.