作者: 追梦 1819
前言
前面几章, 我们介绍了 JDBCTemplate,MyBatis 等 ORM 框架. 下面我们来介绍极简模式的 Spring Data JPA.
Spring Data JPA
简介
我们先来了解几个基本概念, 捋一下各个概念之间的关系.
1,JPA
JPA 是 Java Persistence API 的简称, 中文名 Java 持久层 API,SUN 公司出品. 是 JDK 5.0 注解或 xml 描述对象 - 关系表的映射关系, 并将运行期的实体对象持久化到数据库中.
它的出现目的有两个:
简化数据库访问层;
实现 ORM 框架的统一 (目前还没有完全做到).
2,JPA 和 hibernate 的关系
JPA 是 Hibernate 的一个抽象, 就像 JDBC 和 JDBC 驱动的关系;
JPA 是一种 ORM 规范, 是 Hibernate 功能的一个子集 ;
Hibernate 是 JPA 的一个实现;
3,Spring Data
众所周知, Spring 是一个大家庭, 一个技术的生态圈, 其中整合的内容包罗万象. 而 Spring Data xxx 系列就是 Spring 这个生态圈对数据持久化层的整合.
Spring Data 是 Spring 用来解决数据访问的解决方案, 它包含了大量关系型数据库以及 NoSQL 数据库的数据持久层访问解决方案. 它包含 Spring Data JPA,Spring Data MongoDB,Spring Data Neo4j,Spring Data GernFile,Spring Data Cassandra 等.
目前现有的 Spring-data-jpa,Spring-data-template,Spring-data-MongoDB,Spring-data-Redis 等. 当然, 也包括最开始用的 mybatis-spring ,MyBatis 与 Spring 的整合.
于是, 就出现了 Spring-data-jpa ,Spring 与 JPA 的整合.
具体详情参看 Spring Data 官网介绍 https://spring.io/projects/spring-data .
4, 关于 "dao"
在 Java web 项目中, 我们经常会看到这个包名或者以它为后缀名的类名, 它所代表的是, 数据库持久化层, 是 Data Access Object 的简写. 不过有时候我们也会看到 mapper 等. 其实这些都是同一个意思, 代表数据库持久化层.
在 JDBCTemplate 中, 可能会用 "dao", 在 MyBatis 中, 可能会用 "mapper"(对应 xxxMapper.xml 文件), 在 JPA 中, 可能会用 "repository" 等.
其实名称完全是自定义, 只不过开发者在开发的时候会有一些约定俗成的习惯, 仅此而已.
5,Spring Data JPA
正如上所说, Spring Data JPA 是 Spring 与 JPA 的整合. 是基于 JPA 对数据库访问层的增强支持. 旨在简化数据库访问层, 减少工作量.
具体详情参看 Spring Data JPA 官网 https://spring.io/projects/spring-data-jpa .
核心概念
对于开发者来说, 使用起来很简单. 下面我们看看 Spring Data JPA 几个核心概念.
Repository: 最顶层的接口, 是一个空的接口, 目的是为了统一所有 Repository 的类型, 且能让组件扫描的时候自动识别;
CrudRepository : 是 Repository 的子接口, 提供 CRUD 的功能;
- @NoRepositoryBean
- 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);
- Optional<T> findById(ID id);
- boolean existsById(ID id);
- Iterable<T> findAll();
- Iterable<T> findAllById(Iterable<ID> ids);
- long count();
- void deleteById(ID id);
- void delete(T entity);
- void deleteAll(Iterable<? extends T> entities);
- void deleteAll();
- }
JpaRepository: 是 PagingAndSortingRepository 的子接口, 增加了一些实用的功能, 比如: 批量操作等;
- @NoRepositoryBean
- public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
- List<T> findAll();
- List<T> findAll(Sort var1);
- List<T> findAllById(Iterable<ID> var1);
- <S extends T> List<S> saveAll(Iterable<S> var1);
- void flush();
- <S extends T> S saveAndFlush(S var1);
- void deleteInBatch(Iterable<T> var1);
- void deleteAllInBatch();
- T getOne(ID var1);
- <S extends T> List<S> findAll(Example<S> var1);
- <S extends T> List<S> findAll(Example<S> var1, Sort var2);
- }
PagingAndSortingRepository: 是 CrudRepository 的子接口, 添加分页和排序的功能;
- @NoRepositoryBean
- public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
- Iterable<T> findAll(Sort sort);
- Page<T> findAll(Pageable pageable);
- }
Specification: 是 Spring Data JPA 提供的一个查询规范, 要做复杂的查询, 只需围绕这个规范来设置查询条件即可;
- public interface Specification<T> extends Serializable {
- long serialVersionUID = 1L;
- static <T> Specification<T> not(Specification<T> spec) {
- return Specifications.negated(spec);
- }
- static <T> Specification<T> where(Specification<T> spec) {
- return Specifications.where(spec);
- }
- default Specification<T> and(Specification<T> other) {
- return Specifications.composed(this, other, CompositionType.AND);
- }
- default Specification<T> or(Specification<T> other) {
- return Specifications.composed(this, other, CompositionType.OR);
- }
- @Nullable
- Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
- }
JpaSpecificationExecutor: 用来做负责查询的接口.
- public interface JpaSpecificationExecutor<T> {
- Optional<T> findOne(@Nullable Specification<T> var1);
- List<T> findAll(@Nullable Specification<T> var1);
- Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
- List<T> findAll(@Nullable Specification<T> var1, Sort var2);
- long count(@Nullable Specification<T> var1);
- }
Spring Data JPA 的使用
下面我们演示几种 JPA 的使用.
使用内置方法
构建 SpringBoot 项目, 首先引入 maven 依赖:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-Web</artifactId>
- </dependency>
- <dependency>
- <groupId>MySQL</groupId>
- <artifactId>MySQL-connector-java</artifactId>
- <scope>runtime</scope>
- </dependency>
然后, application.properties 中配置相关的信息:
- server.address=
- server.servlet.context-path=
- server.port=8082
- spring.datasource.driver-class-name=com.MySQL.jdbc.Driver
- spring.datasource.url=jdbc:MySQL://192.168.1.88:3306/test?serverTimezone=GMT+8
- spring.datasource.username=root
- spring.datasource.password=pass123
- # 第一次建表用 create, 后面使用 update
- # 不然每次重新系统工程都会先删除表再新建
- spring.jpa.hibernate.ddl-auto=create
- # 控制台打印 sql
- spring.jpa.show-sql=true
下一步, 创建实体类 User:
- package com.yanfei1819.jpademo.entity;
- import javax.persistence.*;
- /**
- * Created by 追梦 1819 on 2019-05-05.
- */
- @Entity
- @Table(name="user")
- public class User {
- @Id
- // 字段自动生成
- @GeneratedValue(strategy= GenerationType.AUTO)
- private Long id;
- private int age;
- private String name;
- // set/get 省略
- }
再者, 创建 dao 层:
- package com.yanfei1819.jpademo.repository;
- import com.yanfei1819.jpademo.entity.User;
- import org.springframework.data.jpa.repository.JpaRepository;
- /**
- * Created by 追梦 1819 on 2019-05-05.
- */
- public interface UserRepository extends JpaRepository<User,Long> {
- }
最后, 创建 controller 层 (此处为了简化代码, 省略了 service 层):
- package com.yanfei1819.jpademo.Web.controller;
- import com.yanfei1819.jpademo.entity.User;
- import com.yanfei1819.jpademo.repository.UserRepository;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.Web.bind.annotation.GetMapping;
- import org.springframework.Web.bind.annotation.PathVariable;
- import org.springframework.Web.bind.annotation.ResponseBody;
- import java.util.List;
- /**
- * Created by 追梦 1819 on 2019-05-05.
- */
- @Controller
- public class UserController {
- @Autowired
- private UserRepository userRepository;
- @ResponseBody
- @GetMapping("/queryUser")
- public List<User> queryUser(){
- // 通过 jpa 内置的方法进行查询
- return userRepository.findAll();
- }
- }
在上述示例中, 我们使用的是 JPA 的内置方法 findAll() , 另外也有很多别的方法, 如下:
写了几个查询方法.
创建查询方法
在以上示例的基础上, 我们添加一个新的方法:
- UserController:
- @ResponseBody
- @GetMapping("/queryUser/{name}")
- public User queryUserByName(@PathVariable String name){
- // 通过自定义的方法进行查询
- return userRepository.findByName(name);
- }
- UserRepository:
- User findByName(String name);
spring-data-jpa 会根据方法的名字来自动生成 sql 语句, Spring Data JPA 支持通过定义在 Repository 接口中的方法名来定义查询, 方法名是根据实体类的属性名来确定的. 我们只需要按照方法定义的规则即可. 其中 findBy 关键字可以用 find,read,readBy,query,queryBy,get,getBy 替代.
这种方式不仅仅限制这种简单的参数支持, 还包括限制返回的查询的结果, 返回流式查询结果, 异步查询等, 可以参看示例:
- // 限制查询结果
- User findTopByOrderByAgeDesc();
- Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
- // 返回流式查询结果
- Stream<User> readAllByFirstnameNotNull();
- // 异步查询
- @Async
- Future<User> findByFirstname(String firstname);
@Query 查询
在上述示例中添加新的方法:
- UserController:
- @ResponseBody
- @GetMapping("/queryUser2/{id}")
- public User queryUserById(@PathVariable Long id){
- // 通过 @Query 注解查询
- return userRepository.withIdQuery(id);
- }
- UserRepository:
- // @Query("select u from User u where u.id = :id")
- @Query(value = "select u from User u where u.id = ?1",nativeQuery = true)
- User withIdQuery(@Param("id") Long id);
使用这种命名查询来声明实体查询是一种有效的方法, 适用于少量查询. 上面的 @Query 注解需要注意的是:
1,@Query(value = "select u from User u where u.id = ?1",nativeQuery = true) 中的 nativeQuery 属性如果设置为 true 时, 表示的意思是: 可以执行原生 sql 语句, 所谓原生 sql, 也就是说这段 sql 拷贝到数据库中, 然后把参数值给一下就能运行了, 原生的 sql 都是真实的字段, 真实的表名. 如果设置为 false 时, 就不是原生的 sql , 而不是数据库对应的真正的表名, 而是对应的实体名, 并且 sql 中的字段名也不是数据库中真正的字段名, 而是实体的字段名;
2,nativeQuery 如果不设置时, 即 @Query("select u from User u where u.id = ?1") , 默认是 false;
3, @Query("select u from User u where u.id = ?1") 可以写成 @Query("select u from User u where u.id = :id") .
4, 如涉及到删除和修改在需要加上 @Modifying. 也可以根据需要添加 @Transactional 对事物的支持, 查询超时的设置等.
@NamedQuery 查询
在以上的示例中, 为了区分 User 实体类, 我们重新创建一个实体类 UserVo:
- package com.yanfei1819.jpademo.entity;
- import javax.persistence.*;
- /**
- * Created by 追梦 1819 on 2019-05-23.
- */
- @Entity
- @Table(name = "user")
- @NamedQuery(name = "findAll",query = "select u from User u")
- public class UserVo {
- @Id
- @GeneratedValue(strategy= GenerationType.AUTO)
- private Long id;
- @Column(name = "age")
- private int age;
- @Column(name = "name")
- private String name;
- // get/set 省略
- }
在 UserController 中加一个方法:
- @Autowired
- private EntityManager entityManager;
- @ResponseBody
- @GetMapping("/queryUserByNameQuery")
- public List<UserVo> queryUserByNameQuery(){
- // 通过 @Query 注解查询
- return entityManager.createNamedQuery("findAll").getResultList();
- }
启动程序, 访问 http://localhost:8082/queryUserByNameQuery , 能够查询到所有用户:
再看看控制台打印结果:
注意一点, 如果是多个查询, 可以使用 @NamedQuerys .
笔者其实并不推荐使用这种自定义方法, 因为它对实体类的污染很严重. 不利于可读性.
总结
JPA 可以单独使用, 只不过 SpringBoot 这个大平台将该技术引入进来.
JPA 也是一个大的分支, 细枝末节有很多, 作者认为, 对于 JPA 的学习, 只要关注三个方面:
1) ORM 映射元数据, 支持 xml 和 JDK 注解两种元数据的形式;
2) 核心的 API;
3) 查询语言: JPQL.
当然, 如果感兴趣的, 可以好好研究一下 JPA 的官方文档, 深入研读一下其源码. 此处由于篇幅所限, 就不一一阐述. 后续有时间会开设一个 JPA 专栏, 专门讲述这一大分支, 欢迎大家持续关注.
话分两头, 另一方面, 正如前面几章说的, 各个 ORM 技术框架各有优缺点, 没有一种框架是绝对完美的. 技术, 框架的选型, 没必要追求最新, 追求最适合即可.
参考
spring doc
Spring Data 官网介绍 https://spring.io/projects/spring-data
Spring Data JPA 官网 https://spring.io/projects/spring-data-jpa
来源: https://www.cnblogs.com/yanfei1819/p/10910059.html