上一篇我们说到了spring-data-rest 和spring-data-jpa 使用的主要意义。主要目的就是为了减少模板化的代码,那么针对某个特定的repository的查询,我们是不是也可以将查询简化呢?答案当然是肯定的。
我们先来看一下jpa有哪些查询方法:
1.Query methods
The JPA module supports defining a query manually as String or have it being derived from the
method name.
JPA 模块支持手动定义一个查询字符串或者从一个方法名中导出查询。
用法如下:
public interface UserRepository extends Repository<User, Long> {
List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
}
对应的生成的查询为:select u from User u where u.emailAddress = ?1 and u.lastname = ?2.
当然你也可以直接指定查询语句:
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
若是想使用原生的sql,只需要开启 nativeQuery =true
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery =
true)
User findByEmailAddress(String emailAddress);
}
如果我们只是在后台内部做查询,不涉及到前端web请求查询,那么命名查询是一个不错的选择。因为如果走web请求,想要实现动态查询,那么你就不得不去写很多个方法或者写很多条查询语句了。
2.Stored proceduresThe JPA 2.1 specification introduced support for calling stored procedures via the JPA criteria query
API. We Introduced the @Procedure annotation for declaring stored procedure metadata on a
repository method.
JPA2.1 规范通过JPA条件查询API引进了对存储过程的支持。在一个库方法上,通过使用@Procedure声明存储过程元数据。
@Entity
@NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout",
parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer
.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "res", type =
Integer.class) })
public class User {}
在方法上调用存储过程:
@Procedure("plus1inout")
Integer explicitlyNamedPlus1inout(Integer arg);
因为我们涉及到的都是单表查询,而且没有复杂的逻辑要处理,所以存储过程还是用在它该用的地方吧!
3.Specifications
Specifications can easily be used to build an extensible set of
predicates on top of an entity that then can be combined and used with JpaRepository without the
need to declare a query (method) for every needed combination。
Specifications 可以容易地在一个predicate实体之上构造可扩展的predicates集合,它可以被用来组合,而不需要为每次的组合声明一个查询(方法)。
看到这里,看到了希望,因为它很明确的说明出了查询条件是可以组合的。
要想使用Specifications,我们自己的repository一定要继承JpaSpecificationExecutor接口
public interface CustomerRepository extends CrudRepository<Customer, Long>,
JpaSpecificationExecutor {
List<T> findAll(Specification<T> spec);
}
下面是如何构造自己的specification
public class CustomerSpecs {
public static Specification<Customer> isLongTermCustomer() {
return new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
LocalDate date = new LocalDate().minusYears(2);
return builder.lessThan(root.get(_Customer.createdAt), date);
}
};
}
public static Specification<Customer> hasSalesOfMoreThan(MontaryAmount value) {
return new Specification<Customer>() {
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
// build query here
}
};
}
}
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));
它的用法很简单,关键是它为我们提供了一种动态查询的可能。
4.Query by Example
Query by Example (QBE) is a user-friendly querying technique with a simple interface. It allows
dynamic query creation and does not require to write queries containing field names. In fact,
Query by Example does not require to write queries using store-specific query languages at all.
通过 Example查询是一种通过实现简单接口的很友好的查询技术。它允许创建动态查询,并且不需要写包含 域的名字的查询。实际上,QBE不需要写任何特定存储查询语言。
看到这里心里不免又窃喜了一下,看来要实现动态查询还有第二种选择。
先来看看用法:
• Probe: That is the actual example of a domain object with populated fi