使用Spring Data repository进行数据层的访问
抽象出Spring Data repository是因为在开发过程中,常常会为了实现不同持久化存储的数据访问层而写大量的大同小异的代码。
Spring Data repository的目的就是要大幅减少这些重复的代码。 Spring Data Elasticsearch为文档的存储,查询,排序和统计提供了一个高度抽象的模板。
核心概念
Spring Data repository抽象中最核心的接口就是Repository。该接口使用了泛型,需要提供两个类型参数,
- 第一个是接口处理的域对象类型
- 第二个是域对象的主键类型。
这个接口常被看做是一个标记型接口,用来获取要操作的域对象类型和帮助开发者识别继承这个类的接口。在Repository的基础上,CrudRepository接口提供了针对实体类的复杂的CRUD(增删改查)操作。
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { <S extends T> S save(S entity); T findOne(ID primaryKey); Iterable<T> findAll(); Long count(); void delete(T entity); boolean exists(ID primaryKey); // … more functionality omitted. }
PagingAndSortingRepository接口在CrudRepository的基础上增加了一些方法,使开发者可以方便的对实体类进行分页和排序。
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { Iterable<T> findAll(Sort sort); Page<T> findAll(Pageable pageable); }
在分页长度为20的基础上,想要获取第二页的User数据,代码如下
PagingAndSortingRepository<User, Long> repository = // … get access to a bean Page<User> users = repository.findAll(new PageRequest(1, 20));
查询方法
标准的CRUD(增删改查)功能都要使用查询语句来查询数据库。但通过使用Spring Data,只要五个步骤就可以实现。
- 创建一个Domain类
@Entity @Document public class Person { … }
- 声明一个继承Repository接口或其子接口的持久层接口。并标明要处理的域对象类型及其主键的类型(在下面的例子中,要处理的域对象是Person,其主键类型是Long)
interface PersonRepository extends Repository<Person, Long> { … }
- 在接口中声明查询方法(spring会为其生成实现代码)
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
- 让Spring创建对这些接口的代理实例。
使用JavaConfig的方式
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @EnableJpaRepositories class Config {}
使用xml配置的方式
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <jpa:repositories base-package="com.acme.repositories"/> </beans>
注入repository实例,并使用
public class SomeClient { @Autowired private PersonRepository repository; public void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } }
定义查询方法
CREATE
Spring Data repository自带了一个非常有用的查询构造器。它会从方法名中去掉类似find..By,read...By,query...By,count...By之类的前缀,然后解析剩余的名字。我们也可以在方法名中加入更多的表达式,比如查询时需要distinct约束,那么在方法名中加入Distinct即可。方法名中的第一个By是一个分解符,代表着查询语句的开始,我们可以用And或Or来将多个查询条件关联起来。
public interface PersonRepository extends Repository<User, Long> { List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname); // Enables the distinct flag for the query List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname); List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname); // Enabling ignoring case for an individual property List<Person> findByLastnameIgnoreCase(String lastname); // Enabling ignoring case for all suitable properties List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname); // Enabling static ORDER BY for a query List<Person> findByLastnameOrderByFirstnameAsc(String lastname); List<Person> findByLastnameOrderByFirstnameDesc(String lastname); }
除此之外,我们还可以为方法添加某些特定类型的参数(如:Pageable和Sort)来动态的在查询中添加分页和排序。
Page<User> findByLastname(String lastname, Pageable pageable); Slice<User> findByLastname(String lastname, Pageable pageable); List<User> findByLastname(String lastname, Sort sort); List<User> findByLastname(String lastname, Pageable pageable);
USE_DECLARED_QUERY
如果方法通过 @Query 指定了查询语句,则使用该语句创建Query;如果没有,则查找是否定义了符合条件的Named Query,如果找到,则使用该命名查询;如果两者都没有找到,则抛出异常。使用@Query声明查询语句的例子如下:
//使用Query注解 @Query("select a from AccountInfo a where a.accountId = ?1") public AccountInfo findByAccountId(Long accountId);
CREATE_IF_NOT_FOUND
结合了CREATE和USE_DECLARED_QUERY 两种策略,会先尝试查找声明好的查询,如果没有找到,就按照解析方法名的方式构建查询。这是默认的查询策略,如果不更改配置,会一直使用这种策略构建查询。这种策略支持通过方法名快速定义一个查询,也允许引入声明好的查询。
WEB支持
DomainClassConverter 允许开发者在SpringMVC控制层的方法中直接使用域对象类型(Domain types),而无需通过repository手动查找这个实例。
@Controller @RequestMapping("/users") public class UserController { @RequestMapping("/{id}") public String showUserForm(@PathVariable("id") User user, Model model) { model.addAttribute("user", user); return "userForm"; } }
上面的方法直接接收了一个User对象,开发者不需要做任何的搜索操作,转换器会自动将路径变量id转为User对象的id,并且调用了findOne()方法查询出User实体。注意:当前的Repository 必须实现CrudRepository
HandlerMethodArgumentResolver使开发者可以在controller的方法中使用Pageable和Sort作为参数。
@Controller @RequestMapping("/users") public class UserController { @Autowired UserRepository repository; @RequestMapping public String showUsers(Model model, Pageable pageable) { model.addAttribute("users", repository.findAll(pageable)); return "users"; } }
通过上面的方法定义,Spring MVC会使用下面的默认配置尝试从请求参数中得到一个Pageable的实例。
开发者也可以针对多个表定义多个Pageable或Sort实例,需要使用Spring的@Qualifier注解来区分它们。并且请求参数名要带有${qualifier}_的前缀。例子如下:
public String showUsers(Model model, @Qualifier("foo") Pageable first, @Qualifier("bar") Pageable second) { … }
请求中需要带有foo_page和bar_page等参数。