网站首页 > 技术文章 正文
Spring Data架构与应用
Spring Data是Spring家族中专门用于实现数据访问的开源框架,其核心思路是对所有存储媒介进行资源配置从而实现数据访问。我们知道,数据访问需要完成领域对象与存储数据之间的映射并对外提供访问入口,SpringData基于Repository架构模式抽象出一套实现该模式的统一数据访问方式。
Spring Data架构
Spring Data对数据访问过程的抽象主要体现在两个方面,一方面是提供了一套完整的Repository接口定义及实现,另一方面则是实现了各种多样化的查询支持。
Repository接口是Spring Data中对数据访问的最高层抽象,我们通常可以使用它的子接口CrudRepository来实现数据访问。CrudRepository接口添加了对领域实体的CRUD功能,包括保存单个实体、保存集合、根据ID查找实体、根据ID判断实体是否存在、查询所有实体、查询实体数量、根据ID删除实体、删除一个实体的集合以及删除所有实体等常见操作。CrudRepository的定义如代码清单9-1所示。
代码清单9-1 CrudRepository接口定义代码
public interface CrudRepository<T, ID> extends Repository<T, ID> {
//保存单个实体对象
<S extends T> S save(S entity);
//保存一组实体对象
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
//根据ID查询单个实体对象
Optional<T> findById(ID id);
//判断指定ID的对象是否存在
boolean existsById(ID id); //获取所有实体对象
Iterable<T> findAll();
//根据一组ID获取对应的一组实体对象
Iterable<T> findAllById(Iterable<ID> ids);
//获取对象总数
long count();
//根据ID删除实体对象
void deleteById(ID id);
//根据实体对象执行删除操作
void delete(T entity);
//删除一组实体对象
void deleteAll(Iterable<? extends T> entities);
//删除所有实体对象
void deleteAll();
}
在日常开发过程中,对数据的查询操作需求远高于新增、删除和修改操作,所以Spring Data除了对领域对象提供默认的CRUD操作之外,还重点对查询场景做了高度抽象并提供了一系列解决方案,其中最典型的就是@Query注解和方法名衍生查询机制。
我们可以通过@Query注解直接在代码中嵌入查询语句和条件,从而提供类似ORM框架的强大功能。代码清单9-2所示的就是使用@Query注解进行查询的典型例子。
代码清单9-2 @Query注解示例代码
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select * from User u where u.userName = ?1")
User findUserByUserName(String userName);
}
方法名衍生查询也是Spring Data在查询上的特色之一,通过在方法命名上直接使用查询字段和参数,Spring Data就能自动识别相应的查询条件并组装对应的查询语句。典型的示例如代码清单9-3所示。
代码清单9-3 方法名衍生查询示例代码
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByFirstNameAndLastName(String firstName, String
lastName);
}
在代码清单9-3的例子中,通过findByFirstNameAndLastname()这种符合普通语义的方法名,并在参数列表中按照方法名中参数的顺序和名称(即第一个参数是firstName,第二个参数lastName)传入相应的参数,SpringData就能自动组装SQL语句从而实现衍生查询。
Spring Data在官方网站中列出来提供的所有组件,包括针对关系型数据库的JPA Repository,与MongoDB、Neo4j、Redis等NoSQL对应的Repository等。本章重点讨论的是用于实现关系型数据库访问的Spring Data JPA。
Spring Data JPA
在介绍Spring Data JPA的使用方法之前,我们有必要对JPA规范做一定的了解。JPA全称是Java Persistence API,即Java持久化API,是一个Java应用程序接口规范,充当面向对象的领域模型和关系数据库系统之间的桥梁,所以属于一种ORM技术。
JPA规范定义了一些概念和约定,这些定义集中位于javax.persistence包中。常见的定义包含对实体(Entity)以及实体标识(Identifier)的定义,实体与实体之间的关联关系的定义。但是请注意,JPA只是一种规范,而不是具体的实现。而诸如Hibernate等框架遵循JPA规范,并且还添加了一些自己的附加特性。要想在应用程序中使用Spring Data JPA,我们需要在pom文件中引入spring-boot-starter-data-jpa依赖,如代码清单9-4所示。
代码清单9-4 spring-boot-starter-data-jpa依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Spring Data JPA案例分析
接下来,我们将基于第8章介绍的SpringJdbcExample案例中的数据模型和业务场景,通过Spring Data JPA框架对数据访问过程进行重构。
我们知道在案例中,存在两个主要领域对象,即Account和Authority。
因为在使用Spring Data JPA时,需要在实体对象上添加符合JPA规范的注解,所以需要重新定义这两个实体对象。我们先来看比较简单的Authority类,如代码清单9-5所示。
代码清单9-5 Authority类实现代码
@Entity
@Table(name="authority")
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String authorityCode;
private String authorityName;
private String description;
}
Authority类使用了JPA规范中用于定义实体的几个注解,例如最重要的@Entity注解、用于指定表名的@Table注解、用于标识主键的@Id注解以及用于标识自增数据的@GeneratedValue注解。这些注解的含义和应用方式都非常明确,我们一般在实体类上直接使用即可。
接着我们再来看比较复杂的Account类,如代码清单9-6所示。
代码清单9-6 Account类实现代码
@Entity
@Table(name = "account")
public class Account implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String accountNumber;
private String accountName;
@ManyToMany(targetEntity = Authority.class)
@JoinTable(name = "account_authority", joinColumns =
@JoinColumn(name = "account_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "authority_id",
referencedColumnName = "id"))
private List<Authority> authorities;
}
这里除了常见的一些注解之外,最重要的就是引入了@ManyToMany注解来表示account表与authority表中数据的关联关系。JPA规范中提供了one-toone、one-to-many、many-to-one、many-to-many这4种关系,分别处理一对一、一对多、多对一以及多对多的关联场景。
针对案例中的业务场景,在使用@ManyToMany注解时,我们通过@JoinTable注解来指定account_authority中间表,并通过joinColumns和inverseJoinColumns配置项来分别指定中间表中的字段名称以及引用account和authority这两张主表中的外键名称。
定义完实体对象之后,我们提供Repository接口。这一步非常简单,AccountRepository定义如代码清单9-7所示。
代码清单9-7 AccountRepository接口定义代码
@Repository
public interface AccountRepository extends JpaRepository<Account,
Long>{
}
可以看到这是一个继承了JpaRepository接口的空接口。基于前面内容的介绍,我们知道AccountRepository实际上已经具备了访问数据库的基本CRUD功能。
有了上面定义的Account和Authority实体类以及AccountRepository接口,我们已经可以完成很多数据访问操作了。例如,如果想通过ID来获取Account对象,可以构建一个AccountService并直接注入AccountRepository接口即可,如代码清单9-8所示。
代码清单9-8 AccountService类实现代码
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
public Account getAccountById(Long accountId) {
return accountRepository.getOne(accountId);
}
}
我们构建一个Controller类来调用上述方法,并通过HTTP请求查询id为1的Account对象,可以获得如代码清单9-9所示的结果。
代码清单9-9 Account查询结果
{
"id": 1,
"accountNumber": "Account1",
"accountName": "MyAccount",
"authorities": [
{
"id": 1,
"authorityCode": " authorityCode1",
"authorityName": " authorityName1",
"description": "description1"
},
{
"id": 2,
"authorityCode": " authorityCode2",
"authorityName": " authorityName2",
"description": "description2"
},
]
}
请注意,这里在获取了account表中的账户基础数据之外,还同时获取了authority表中的用户权限数据。能够实现这种效果的原因就在于在Account对象中添加了@Many-ToMany注解,该注解会自动从account_authority表中获取用户权限主键信息并从authority表中获取对应的用户权限信息。
对比上篇中中通过JdbcTemplate获取这部分数据的实现过程,可以看到使用SpringData JPA就要简单很多。除了JpaRepository中默认集成的各种CRUD方法,我们也可以使用@Query注解、方法名衍生查询等机制来实现多样化查询。其中,使用@Query注解实现查询的示例如代码清单9-10所示。
代码清单9-10 使用@Query注解实现查询Account的示例代码
@Repository
public interface AccountRepository extends JpaRepository<Account,
Long> {
@Query("select a from Account a where a.accountNumber = ?1")
Account getAccountByAccountNumberWithQuery(String accountNumber);
}
这里使用了类似SQL语言的JPQL(Java Persistence Query Language,Java持久化查询语言)来根据AccountNumber查询用户账户信息。
使用方法名衍生查询是最方便的一种自定义查询方式,开发人员唯一要做的就是在JpaRepository接口中定义一个符合查询语义的方法。如果我们希望通过AccountName查询账户信息,那么可以提供如代码清单9-11所示的接口定义。
代码清单9-11 方法名衍生查询Account的示例代码
@Repository
public interface AccountRepository extends JpaRepository<Account,
Long> {
Account getAccountByAccountName(String accountName);
}
现在,通过getAccountByAccountName()方法就可以自动根据AccountName来获取用户账户详细信息。最后,我们还将引入Specification机制来丰富查询方式。我们已经在前面介绍到Spring缓存时使用过Specification机制。这里,我们将对这一机制进一步展开讨论。
考虑这样一种场景,我们需要查询某个实体,而给定的查询条件是不固定的,这时候就需要动态构建相应的查询语句。在Spring Data JPA中,我们可以通过JpaSpecification-Executor接口实现这类查询。继承了JpaSpecificationExecutor的AccountRepository定义如代码清单9-12所示。
代码清单9-12 继承了JpaSpecificationExecutor的AccountRepository接口定义代码
@Repository
public interface AccountRepository extends JpaRepository<Account,
Long>, JpaSpecificationExecutor<Account> {
}
对于JpaSpecificationExecutor接口而言,它背后就是Specification接口。我们可以简单理解该接口的作用就是构建查询条件。Specification接口的核心方法只有一个,如代码清单9-13所示。
代码清单9-13 Specification接口定义代码
public interface Specification<T> extends Serializable {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder);
}
在上述方法中,Root对象代表所查询的根对象,可以通过Root获取实体中的属性;CriteriaQuery对象代表一个顶层查询对象,用来实现自定义查询;而CriteriaBuilder对象显然是用来构建查询条件的。基于Specification机制,我们同样对根据AccountNumber查询订单的实现过程进行重构,重构后的getAccountByAccountNumberBySpecification()方法如代码清单9-14所示。
代码清单9-14 重构后的getAccountByAccountNumberBySpecification()方法实现代码
public Account getAccountByAccountNumberBySpecification(String
accountNumber) {
Account account = new Account();
account.setAccountNumber(accountNumber);
Specification<Account> spec = new Specification<Account>() {
@Override
public Predicate toPredicate(Root<Account> root,
CriteriaQuery<?> query, CriteriaBuilder cb) {
Path<Object> accountNumberPath =
root.get("accountNumber");
Predicate predicate = cb.equal(accountNumberPath,
accountNumber);
return predicate;
}
};
return accountRepository.findOne(spec).orElse(new Account());
}
可以看到,在这里的toPredicate()方法中,我们从Root对象中获取了accountNumber属性,然后通过cb.equal()方法将该属性与传入的accountNumber参数进行比对,从而完成查询条件的构建。相比JPQL,使用Specification机制的优势是类型安全。
完整的Spring Data JPA案例请参考:
https://github.com/tianminzheng/spring-boot
examples/tree/main/SpringDataJpaExample。
本文给大家讲解的内容是SpringORM最佳实践: Spring Data架构与应用
- 下文给大家讲解的是应用Spring ORM最佳实践:N+1性能问题
猜你喜欢
- 2024-10-01 全面解析45种设计模式(Design pattern)和六大原则
- 2024-10-01 5分钟了解(什么是设计模式)(设计模式的意思)
- 2024-10-01 mongoHelper 0.3.9 发布,简化 CRUD操作
- 2024-10-01 JPA 的 Metamodel(jpa格式图片)
- 2024-10-01 设计模式简介(设计模式是干嘛的)
- 2024-10-01 SlideLive网站:如何实现个性化搜索
- 2024-10-01 JPA核心接口EntityManager之API功能详解(三)
- 2024-10-01 使用ElasticSearch快速搭建数据搜索服务
- 2024-10-01 Spring Data JPA 自定义存储库(spring data jpa调用存储过程)
- 2024-10-01 Spring Data之JPA @Query注解(spring+jpa)
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)