- 此篇博文主要是结合官方文档以及对JPA的一些使用进行记录。
- 对于相关介绍,可参考相应博客。
- 后续会对相关实现机制和核心思想进行深入探讨。
参考书目:
- 汪云飞:JavaEE开发的颠覆者 Spring Boot实战
参考博客:
参考文档:
相关使用
1、引入依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<!-- 版本号省略,maven相关不过多介绍 -->
</dependency>
2、建立数据访问层
The goal of Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores.
-
建立抽象的 Repository或Dao 从而减少持久层相关代码的编写(模板化的代码,重复的代码)
-
空接口、标记接口:没有包含方法声明的接口
public interface Repository<T, ID extends Serializable> {
}
- 以自定义 PersonRepository 为例: 使得 Spring 容器能对自定义的 PersonRepository 进行管理(两种实现)
/**
* 继承 Repository<T, ID extends Serializable> 接口使得 Spring 容器能对自定义的 PersonRepository 进行管理
*/
public interface PersonRepository extends Repository<Person, Long> {
//定义数据访问操作的方法
}
/**
* 除了继承 Repository 接口以外,还可以通过注解实现 Spring 容器对自定义 repository 进行管理
*/
@RepositoryDefinition(domainClass = Person.class,idClass = Long.class)
public interface PersonRepository {
//定义数据访问操作的方法
}
Repository 的相关子类实现
自带Repository
- 1)、CrudRepository
public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
// Saves the given entity.
<S extends T> S save(S entity);
// Returns the entity identified by the given id.
Optional<T> findById(ID primaryKey);
// Returns all entities.
Iterable<T> findAll();
// Returns the number of entities
long count();
// Deletes the given entity.
void delete(T entity);
// Indicates whether an entity with the given id exists.
boolean existsById(ID primaryKey);
// … more functionality omitted.
}
- 2)、PagingAndSortingRepository extends CrudRepository
public interface PagingAndSortingRepository<T, ID extends Serializable>
extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
- 3)、JpaRepository extends PagingAndSortingRepository
自定义抽象 Repository
- 选择性地暴露相关持久层数据操作的相关方法
- Repository 中关于 null 的处理,使用 spring 相关注解:
- @NonNullApi – to be used on the package level to declare that the default behavior for parameters and return values is to not accept or produce null values.
- @NonNull – to be used on a parameter or return value that must not be null (not needed on parameter and return value where @NonNullApi applies).
- @Nullable – to be used on a parameter or return value that can be null.
**************************************interface MyBaseRepository****************************
// Make sure you add that annotation to all repository interfaces
// that Spring Data should not create instances for at runtime.
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {
// Java 8 Optinal refering http://www.importnew.com/6675.html
// 也可以返回其他包装类型
Optional<T> findById(ID id);
<S extends T> S save(S entity);
}
**************************************package-info.java**********************************
@NonNullApi
package tech.shunzi.repository;
import org.springframework.lang.NonNullApi;
**************************************interface UserRepository****************************
package tech.shunzi.repository;
import org.springframework.lang.Nullable;
public interface UserRepository extends MyBaseRepository<User, Long> {
// test success
@Nullable
User findByEmailAddress(@Nullable EmailAddress emailAddress);
}
- 关于 Repository 或 项目 中可能出现的 SpringData 多模块问题,所谓的 多模块 指的是在 classpath 中同时存在多个 Spring Data 模块,譬如 MongoDB 和 JPA 两个模块。
- 对于 仓库 中如果显式继承了模块的特定接口:JpaRepository,则不存在多模块的问题。
- 对于 域对象domain 中如果显式使用了模块特定的注解,如JPA的@entity,如Spring Data MongoDB/Spring
Data Elasticsearch的@document注解,也不存在多模块的问题。
- 存在多模块的问题的情况有:
- 使用通用接口的仓库定义 Repository
- 使用有多个注解的域类型的仓库定义。 @Entity、@Document 同时修饰一个对象
- 用于区分仓库的方法是限制仓库的 base packages
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }
2、JPA的基本使用: 4 steps
1)、建立相关接口 two steps
// step 1:Declare an interface extending Repository or one of its subinterfaces and type it to the domain class and ID type that it will handle.
interface PersonRepository extends Repository<Person, Long> {
// step 2: Declare query methods on the interface.
List<Person> findByLastname(String lastname);
}
- 其中定义相应的查询方法主要有两种方式:It can derive the query from the method name directly, or by using a manually defined query.
- direct name:
- manual define:
2)、在 Spring 中为定义好的接口创建代理实例
- step 3:Set up Spring to create proxy instances for those interfaces.
- use 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>
- use java annotation to config
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @EnableJpaRepositories class Config {}
3)、获取 Repository 实例并调用相关方法
- step 4: Get the repository instance injected and use it.
class SomeClient {
private final PersonRepository repository;
SomeClient(PersonRepository repository) {
this.repository = repository;
}
void doSomething() {
List<Person> persons = repository.findByLastname("Matthews");
}
}
3、定义查询方法
1)、查询策略
CREATE
:根据查询方法名称构建,从方法名称中移除一组已知的前缀并解析该方法的其余部分。find…By, read…By, query…By, count…By, get…By, And and Or.
USE_DECLARED_QUERY
:试图找到一个声明的查询(注释或其他方式声明),如果未找到会抛出一个异常。CREATE_IF_NOT_FOUND
:default.它首先查找已声明的查询,并且如果未找到已声明的查询,则会创建一个基于自定义方法名称的查询。这是默认的查找策略,因此如果不明确配置任何内容,将会使用它。它允许通过方法名称快速查询定义,还可以根据需要引入已声明的查询来自定义这些查询。
2)、属性表达式
- 分辨算法会对findBy后的字段进行匹配。从右往左开始匹配。先匹配
AddressZipCode
,没有该属性,匹配失败,从右往左,匹配Code
和AddressZip
,匹配失败,继续匹配Address
和ZipCode
,匹配成功。对应的则为Address.ZipCode
。
List<Person> findByAddressZipCode(ZipCode zipCode);
- 注意:如果
Person
有addressZip
属性,则会因为addressZip
没有code
属性匹配失败,此时需要使用下划线来消除歧义。--不推荐使用
List<Person> findByAddress_ZipCode(ZipCode zipCode);
3)、特殊参数处理(Pageable,Sort)
// A Page knows about the total number of elements and pages available.
Page<User> findByLastname(String lastname, Pageable pageable);
// A Slice only knows about whether there’s a next Slice available
// Slice is suitable for large result set.
Slice<User> findByLastname(String lastname, Pageable pageable);
// Can use sort parameter to sort data.
List<User> findByLastname(String lastname, Sort sort);
// List can avoid extra query for page instance,such as total amount.
List<User> findByLastname(String lastname, Pageable pageable);
To find out how many pages you get for a query entirely you have to trigger an additional count query. By default this query will be derived from the query you actually trigger.
4)、限制查询结果
- First/Top 关键字,可以追加数字进行结果集中结果数量的限制,默认为 1。
- Distinct 关键字
- 结合 Sort 和 First/Top 可实现寻找 第K大/小。
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
5)、流式查询结果
- 利用 Java8 的 Stream
作为返回结果,使用完对应的 stream 后需要 close
@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
6)、异步查询结果
- 方法调用后立即返回结果,
// java.util.concurrent.Future
@Async
Future<User> findByFirstname(String firstname);
// Java 8 java.util.concurrent.CompletableFuture
@Async
CompletableFuture<User> findOneByFirstname(String firstname);
// org.springframework.util.concurrent.ListenableFuture
@Async
ListenableFuture<User> findOneByLastname(String lastname);
4、创建 Repo 实例
1、XML 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="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">
<!-- Use filter to exclude some repo to from being instantiated. -->
<repositories base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
</beans:beans>
2、Java Config (Recommend)
- Annotation @Enable${store}Repositories
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
3、独立使用
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
Repo 的自定义实现
定制个人Repo
- 1、定义接口和实现
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
// you can add some beans according to your demand
@Autowired
private JdbcTemplate jdbcTemplate;
}
// Doing so combines the CRUD and custom functionality
class UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
注意: 当
CustomRepo
中包含和BaseRepo
(JPA自带Repo)相同的方法时,譬如如下例子的save
,CustomRepo
有更高的优先级,所以可以实现 自定义Repo
对原生的Repo
的相关方法的重写。与此同时,可以结合 泛型 使得自定义Repo
被更多的RepoImpl
类继承实现。
interface CustomizedSave<T> {
<S extends T> S save(S entity);
}
class CustomizedSaveImpl<T> implements CustomizedSave<T> {
public <S extends T> S save(S entity) {
// Your custom implementation
}
}
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
- 对于自定义
Repo
的相关配置,如果使用namespace
相关配置,会自动检测扫描指定包下 指定格式的自定义Repo
,默认后缀为Impl
,可以通过修改相关配置进行定制。
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="FooBar" />
- 当一个
Repo
接口有多个实现,且位于不同的包下时, Spring 将基于beanName
对RepoImpl
进行扫描.接口未指定对应的beanName
时,默认为 接口名 + Impl,指定了Component
时,则对应扫描Component
对应的Repo
.
注意:也可以在配置中自定义实例化相关 Bean
package com.acme.impl.one;
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
package com.acme.impl.two;
@Component("specialCustomImpl")
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
interface UserRepository implements CustomizedUserRepository {
// default matches CustomizedUserRepositoryImpl
// if @Component("specialCustomImpl"),matches CustomizedUserRepositoryImpl
}
2、自定义基础Repo
未完待续
实体类 Entity
结合文档官方文档 Version 2.0.4.RELEASE,
2018-02-19