Spring Data JPA Advanced--specifications and QUERYDSL
This article describes some of the features that spring data JPA can offer more convenience for the development of the Access program, and we know that the spring data repository configuration is simple, and a typical repository looks like this:
publicinterface CustomerRepository extends JpaRepository<Customer, Long> { Customer findByEmailAddress(String emailAddress); List<Customer> findByLastname(String lastname, Sort sort); Page<Customer> findByFirstname(String firstname, Pageable pageable);}
The first method indicates that a customer is queried based on an email, the second method represents a collection of customer queries based on LastName and sorting criteria, and the third method represents a page query based on Fristname and paging customer
This way is very simple, even without the implementation of writing methods can implement the function of the query, but there is still a disadvantage, if the query conditions grow, the method will be more and more, if you can dynamically assemble the query conditions is good
So, can I? The answer is yes, of course.
We all know that JPA provides the criteria API, so let's use an example to show the criteria, imagine a scenario where we want to give a blessing to a long-term customer on his birthday, how do we do that?
Using the Criteria API
We have two conditions, birthdays and long-term customers, we assume that two years ago registered is the long-term customer bar, how to use the JPA 2.0 criteria API implementation:
new LocalDate();CriteriaBuilder builder = em.getCriteriaBuilder();CriteriaQuery<Customer> query = builder.createQuery(Customer.class);Root<Customer> root = query.from(Customer.class);Predicate hasBirthday = builder.equal(root.get(Customer_.birthday), today);Predicate isLongTermCustomer = builder.lessThan(root.get(Customer_.createdAt), today.minusYears(2);query.where(builder.and(hasBirthday, isLongTermCustomer));em.createQuery(query.select(root)).getResultList();
We first created a Localdate object, then the three-row boilerplate code, and the next two lines were to establish the query criteria and then join it through the WHERE clause and execute the query
There are two problems with the query above
- First, the reuse and extensibility of query conditions is not very good because criteriabuilder,criteriaquery,root is established every time.
- Second, the above procedures are generally readable, and do not know at a glance what the program is doing
Using specifications
To reuse the query conditions, we introduced the specification interface, derived from the concept in Eric Evans ' Domain driven Design, which defines a specification for predicates that query an entity. The entity type is determined by the generic parameter of the specification interface, which contains only one of the following methods:
publicinterface Specification<T> { Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);}
We can now easily use it with a tool class:
Publiccustomerspecifications { Public StaticSpecification<customer>Customerhasbirthday() {return Newspecification<customer> { Publicpredicatetopredicate(root<t> Root, criteriaquery query, Criteriabuilder CB) {returnCb.equal (Root.get (Customer_.birthday), today); } }; } Public StaticSpecification<customer>Islongtermcustomer() {return Newspecification<customer> { Publicpredicatetopredicate(root<t> Root, criteriaquery query, Criteriabuilder CB) {returnCb.lessthan (Root.get (Customer_.createdat),NewLocaldate.minusyears (2)); } }; }}
Admittedly, this is not the most elegant code, but at least it solves the need to reuse the judging condition, how to execute it, very simply, we just let repository inherit the Jpaspecificationexecutor interface:
publicinterface CustomerRepository extends JpaRepository<Customer>, JpaSpecificationExecutor { // Your query methods here}
It can then be called as follows:
customerRepository.findAll(hasBirthday());customerRepository.findAll(isLongTermCustomer());
The default implementation will provide you with objects such as Criteriaquery,root,criteriabuilder, using a given specification to apply the criteria, and then execute the query, the advantage is that we can arbitrarily combine the query conditions, without writing many methods, The Specifications tool class provides a write traversal method to combine conditions such as and (...), or (...). and other connection methods, and where (...) Provides an easier-to-read representation, let's look at the effect:
customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));
Compared to the native interface of the JPA Criteria API, our implementations are more extensible and readable, but it is worthwhile to have a little twist when implementing specification.
Using QUERYDSL
To address the above pain, an open source project called QUERYDSL also provides a similar solution, but the implementation is different, provides a better API, and not only supports JPA, but also supports HIBERNATE,JDO,LUCENE,JDBC even the original collection of queries
In order to use QUERYDSL, dependencies need to be introduced in the Pom.xml and an additional apt plugin is configured
<plugin> <groupId>Com.mysema.maven</groupId> <artifactid>Maven-apt-plugin</artifactid> <version>1.0</version> <executions> <execution> <phase>Generate-sources</phase> <goals> <goal>Process</goal> </goals> <configuration> <outputdirectory>Target/generated-sources</outputdirectory> <processor>Com.mysema.query.apt.jpa.JPAAnnotationProcessor</Processor> </configuration> </Execution> </executions></plugin>
The following can be done by Qcustomer to achieve our above functions.
new LocalDate();BooleanExpression customerHasBirthday = customer.birthday.eq(today);BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));
The above writing not only read very smoothly, booleanexpressions can also be reused directly, eliminating the use of more packaging methods of writing, the more cool is also can get the IDE code auto-complete support, to execute queries, similar to specification, Let repository inherit the Querydslpredicateexecutor interface:
publicinterface CustomerRepository extends JpaRepository<Customer>, QueryDslPredicateExecutor { // Your query methods here}
Can be called in the following way
BooleanExpression customerHasBirthday = customer.birthday.eq(today);BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));customerRepository.findAll(customerHasBirthday.and(isLongTermCustomer));
Summarize
Spring Data JPA Repository abstraction allows you to simplify development by wrapping the JPA Criteria API into specification, and you can also use QUERYDSL, which is simple to implement. Integrate Jpaspecificationexecutor or querydslpredicateexecutor separately, and of course, if necessary, use it together.
Original https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
Spring Data JPA Advanced--specifications and QUERYDSL