集成 Spring Data Jpa
- 什么是 Jpa
- 什么是 Spring Data Jpa
- 什么是 Hibernate
- JPA、Spring Data Jpa、Hibernate 之间的关系
- 集成 Spring Data Jpa
- POM 依赖
- 配置文件
- UserEntity
- 启动程序
- Jpa 配置
- Jpa 注解
- UserRepository
- UserService
- UserServiceImpl
- UserController
- BaseEntity
什么是 Jpa
JPA(Java Persistence API)是 Java 标准中的一套 ORM 规范(提供了一些编程的 API 接口,具体实现由 ORM 厂商实现,如Hiernate、TopLink 、Eclipselink等都是 JPA 的具体实现),借助 JPA 技术可以通过注解或者 XML 描述【对象-关系表】之间的映射关系,并将实体对象持久化到数据库中(即Object Model与Data Model间的映射)。
什么是 Spring Data Jpa
Spring Data JPA 是在实现了 JPA 规范的基础上封装的一套 JPA 应用框架(Criteria API还是有些复杂)。虽然 ORM 框架都实现了 JPA 规范,但是在不同的 ORM 框架之间切换仍然需要编写不同的代码,而使用 Spring Data JPA 能够方便的在不同的 ORM 框架之间进行切换而不需要更改代码。Spring Data JPA 旨在通过统一 ORM 框架的访问持久层的操作,来提高开发人的效率。
Spring Data JPA 是一个 JPA 数据访问抽象。也就是说 Spring Data JPA 不是一个实现或 JPA 提供的程序,它只是一个抽象层,主要用于减少为各种持久层存储实现数据访问层所需的样板代码量。但是它还是需要 JPA 提供实现程序,其实 Spring Data JPA 底层就是使用的 Hibernate 实现。
什么是 Hibernate
Hibernate对数据库结构提供了较为完整的封装,Hibernate的O/R Mapping实现了POJO 和数据库表之间的映射,以及SQL 的自动生成和执行。往往只需定义好了POJO 到数据库表的映射关系,即可通过Hibernate 提供的方法完成持久层操作。甚至不需要对SQL 的熟练掌握, Hibernate/OJB 会根据制定的存储逻辑,自动生成对应的SQL 并调用JDBC 接口加以执行。
hibernate对 JPA 的支持,不是另提供了一套专用于 JPA 的注解。一些重要的注解如@Column, @OneToMany等,hibernate并没有提供,这说明 JPA 的注解已经是hibernate 的核心,hibernate只提供了一些补充,而不是两套注解。JPA 和hibernate都提供了的注解(例如@Entity),若 JPA 的注解够用,就直接用,若 JPA 的注解不够用,直接使用hibernate的即可。
JPA、Spring Data Jpa、Hibernate 之间的关系
集成 Spring Data Jpa
POM 依赖
<!--删掉 jdbc 依赖-->
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>-->
<!-- 添加 jpa 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
配置文件
spring:
jpa:
# 控制台显示SQL
show-sql: true
hibernate:
# 程序启动后自动更新或者创建数据表结构
ddl-auto: update
properties:
hibernate:
# 格式化打印 sql
format_sql: true
UserEntity
@Data
@Entity
@Table(name = "sys_user")
public class UserEntity {
/**
* Id 表示为表 ID
* GenerationType.IDENTITY 使用自增长主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String nickname;
private Integer age;
private String email;
private String password;
private String createUser;
private String updateUser;
private Date createTime;
private Date updateTime;
}
启动程序
做完这些操作之后我们先启动程序看看情况,先给大家看看我的数据库下现在是没有任何一张表的。
ok,启动程序,查看一下结果,程序启动成功,但是我先日志里面打印了一段 SQL 语句。
很明显是一个建表语句,并且字段名和类型跟我们刚才新建的 UserEitity 一模一样,猜测是 Jpa 自动根据实体类帮我们建表了,我们查看一下数据库,果然多了一张 sys_user 表。
这是因为我们刚才配置了 spring.jpa.hibernate.ddl-auto=update
,update 默认会根据添加了@Entity的映射实体类进行表结构的创建或更新,生产上我们应该关闭这个功能,配置为 none。
spring.jpa.hibernate.ddl-auto
配置比较重要,表示建表的策略,可选的枚举值如下:
create
:不管表是否存在,每次启动都会重新建表(会导致数据丢失)。create-drop
:启动的时候创建表,程序退出(SessionFactory
关闭)的时候删除表。none
:不进行任何操作。update
:如果数据表不存在则创建,在实体对象被修改后,下次启动重新修改表结构(不会删除已经存在的数据)。validate
:启动的时候验证数据表的结构。
Jpa 配置
Spring Data JPA已经提供了一些独立于供应商的配置选项(例如SQL日志),Spring Boot将这些选项以及一些针对Hibernate的选项作为外部配置属性公开。
属性 | 描述 | 备注 |
---|---|---|
spring.jpa.database | 要操作的目标数据库,默认自动检测 | 可选配置 |
spring.jpa.database-platform | 要操作的目标数据库的名称,默认情况下是自动检测的 | 可以使用"Database"枚举 |
spring.jpa.defer-datasource-initialization | datasource初始化延迟 | 默认false |
spring.jpa.generate-ddl | 启动时是否初始化数据库schema | 默认false |
spring.jpa.show-sql | 是否启用SQL语句日志记录 | 默认false |
spring.jpa.mapping-resources | 资源映射(等价于persistence.xml中的“mapping-file”条目) | |
spring.jpa.open-in-view | OpenEntityManagerInViewInterceptor注册,将JPA EntityManager绑定到线程,用于整个请求处理 | 默认true |
spring.jpa.properties | 要在JPA提供程序上设置的其他本地属性 | 例如:spring.jpa.properties.hibernate.connection.autocommit |
spring.data.jpa.repositories.enabled | 是否启用JPA Repository | 默认true |
spring.data.jpa.repositories.bootstrap-mode | JPA Repository的引导模式 | 三种模式:DEFAULT(默认), DEFERRED ,LAZY |
spring.jpa.hibernate.ddl-auto | DDL模式 | hibernate.hbm2ddl的快捷方式,当使用嵌入式数据库时,默认为create-drop,否则默认值为 none |
spring.jpa.hibernate.naming.implicit-strategy | 全限定名的隐式命名策略 | 例如:org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy |
spring.jpa.hibernate.naming.physical-strategy | 物理命名策略的完全限定名 | |
spring.jpa.hibernate.use-new-id-generator-mappings | 是否使用Hibernate更新的IdentifierGenerator AUTO, TABLE和SEQUENCE。 | 默认true。hibernate.id.new_generator_mappings快捷方式 |
Jpa 注解
看完了 Spring Boot 中对于 Jpa 的配置,我们再大致的了解一下 Jpa 提供的一些注解,后续我们需要经常用到这些注解来进行开发。
注解 | 解释 |
---|---|
@Entity | 声明类为实体或表。 |
@Table | 声明表名。 |
@Basic | 指定非约束明确的各个字段。 |
@Embedded | 指定类或它的值是一个可嵌入的类的实例的实体的属性。 |
@ld | 指定的类的属性,用于识别(一个表中的主键)。 |
@GeneratedValue | 指定如何标识属性可以被初始化,例如自动、手动、或从序列表中获得的值。 |
@Transient | 指定的属性,它是不持久的,即:该值永远不会存储在数据库中。 |
@Column | 指定持久属性栏属性。 |
@SequenceGenerator | 指定在@GeneratedValue注解中指定的属性的值。它创建了一个序列. |
@TableGenerator | 指定在@GeneratedValue批注指定属性的值发生器。它创造了的值生成的表。 |
@AccessType | 这种类型的注释用于设置访问类型。如果设置@AccessType(FIELD),则可以直接访问变量并目不需要getter和setter,但必须为public。如果设置@AccessType(PROPERTY),通过getter和setter方法访问Entity的变量。 |
@JoinColumn | 指定一个实体组织或实体的集合。这是用在多对一和一对多关联。 |
@UniqueConstraint | 指定的字段和用于主要或辅助表的唯一约束。 |
@ColumnResult | 参考使用select-子句的SQL查询中的列名。 |
@ManyToMany | 定义了连接表之间的多对多对多的关系。 |
@ManyToOne | 定义了连接表之间的多对一的关系。 |
@OneToMany | 定义了连接表之间存在一个一对多的关系。 |
@OneToOne | 定义了连接表之间有一个一对一的关系。 |
@NamedQueries | 指定命名查询的列表。 |
@NamedQuery | 指定使用静态名称的查询。 |
UserRepository
要创建一个 repository 接口,你首先需要定义一个实体类专用的 repository 接口。该接口必须继承 Repository
,并将其泛型设置为实体类和ID类型。
# 表示这是一个 Repository 接口
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long>, Serializable {
}
定义好了 UserRepository
接口之后,我们就可以对 sys_user 表进行增删改查了,我们编写一个测试类来测试一下。
@Slf4j
@SpringBootTest
class ApplicationTests {
@Resource
UserRepository userRepository;
@Test
void saveUserTest() {
UserEntity user = new UserEntity();
user.setName("张三");
user.setNickname("法外狂徒");
user.setAge(18);
user.setPassword("666");
// 保存用户并返回
UserEntity result = userRepository.save(user);
log.info("用户添加成功:{}", result);
}
@Test
void contextLoads() {
}
}
运行程序后,程序执行成功,查看日志发现执行了一条插入语句,数据库中也是正常的存在一条数据,说明Spring Data Jpa 已经集成成功,是不是很简单,只需要定义一个 Entity 和一个 Repository,什么增删改查的方法都不用定义就可以了。那么这是为什么呢,咱们继续往下看。
其实就是我们集成的 JpaRepository
接口中已经预定义了各种 CRUD 方法,我们只需要在集成的时候插入相应的泛型对象就可以。 JpaRepository
的泛型对象是一个实体类型和 ID 类型,他继承的接口是 ListCrudRepository
、ListPagingAndSortingRepository
以及 QueryByExampleExecutor
接口。
继续点进去 ListCrudRepository
发现继承的是 CrudRepository
,他两其实是提供了同等的方法,但ListCrudRepository
返回 List
,而 CrudRepository
的方法返回 Iterable
。
ListPagingAndSortingRepository
是可以进行分页和排序操作的接口。
最后点进去CrudRepository
发现继承的是Repository
接口,他是Spring Data repository 抽象的中心接口,它把要管理的 domain 类以及 domain 类的ID类型作为泛型参数。这个接口主要是作为一个标记接口,用来捕捉工作中的类型,并帮助你发现扩展这个接口的接口。 CrudRepository
和 ListCrudRepository
接口为被管理的实体类提供复杂的CRUD功能。
repository 接口的继承关系
CrudRepository
接口提供的一些方法
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity); 1
Optional<T> findById(ID primaryKey); 2
Iterable<T> findAll(); 3
long count(); 4
void delete(T entity); 5
boolean existsById(ID primaryKey); 6
// … more functionality omitted.
}
1、保存给定的实体。
2、根据ID返回实体。
3、返回所有实体。
4、返回实体数量。
5、删除给定的实体。
6、根据ID判断实体是否存在。
ok,我们接着完善一下代码,编写完整的业务逻辑吧。
UserService
public interface UserService {
/**
* 根据ID查询用户
*
* @param id 用户ID
* @return UserEntity 用户信息
*/
UserEntity get(Long id);
/**
* 查询全部用户
*
* @return List<UserEntity> 用户集合
*/
List<UserEntity> lists();
/**
* 保存用户
*
* @param user 用户信息
*/
void save(UserEntity user);
/**
* 修改用户
*
* @param user 用户信息
*/
void update(UserEntity user);
/**
* 删除用户
*
* @param id 用户id
*/
void delete(Long id);
/**
* 分页查询用户
*
* @param pageable 分页参数
* @return Page<UserEntity> 分页用户
*/
Page<UserEntity> page(Pageable pageable);
}
UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserRepository userRepository;
@Override
public UserEntity get(Long id) {
return userRepository.getReferenceById(id);
}
@Override
public List<UserEntity> lists() {
return userRepository.findAll();
}
@Override
public void save(UserEntity user) {
userRepository.save(user);
}
@Override
public void update(UserEntity user) {
userRepository.save(user);
}
@Override
public void delete(Long id) {
userRepository.deleteById(id);
}
@Override
public Page<UserEntity> page(Pageable pageable) {
return userRepository.findAll(pageable);
}
}
UserController
@RestController
@RequestMapping("/sys/user")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/{id}")
public UserEntity get(@PathVariable Long id) {
return userService.get(id);
}
@GetMapping("/list")
public List<UserEntity> lists() {
return userService.lists();
}
@GetMapping("/page")
public Page<UserEntity> page(int page, int size) {
return userService.page(PageRequest.of(page - 1, size));
}
@PostMapping
public void save(@RequestBody UserEntity user) {
userService.save(user);
}
@PutMapping
public void update(@RequestBody UserEntity user) {
userService.update(user);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
userService.delete(id);
}
}
ok,启动程序,使用 postman 进行测试一下,下一节我们集成 Swagger,就可以不用 postman 测试接口了。
证明接口是OK滴,这些方法都是一些基础的方法,复杂的用法我们后面遇到了再说。
BaseEntity
我们编写这个 BaseEntity 的目的就是,将所有的 Entity 中共有的属性给他抽取出来,像是createUser、createTIme、updateUser以及updateTime 这些字段,同时进行一个数据的自动填充,我们在插入数据的时候就不需要关注这几个字段的值了。
1、添加@EnableJpaAuditing注解,启用jpa的审计功能
@EnableJpaAuditing
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2、在基础类上添加jpa实体侦听器@EntityListeners(AuditingEntityListener.class),并且在具体属性上添加@CreatedBy、@CreatedDate、@LastModifiedBy、@LastModifiedBy注解。
@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@CreatedBy
@Column(name = "create_user", updatable = false)
private String createUser;
@LastModifiedBy
@Column(name = "update_user")
private String updateUser;
@CreatedDate
@Column(name = "create_time", updatable = false)
private Date createTime;
@LastModifiedDate
@Column(name = "update_time")
private Date updateTime;
}
3、配置jpa自动填充用户,因为jpa是不知道当前的操作用户是谁的
@Configuration
public class JpaAuditorConfig implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
// TODO 先写死,等集成了Spring Security后再获取实际用户
return Optional.of("admin");
}
}
4、修改 UserEntity 集成 BaseEntity
@Data
@Entity
@Table(name = "sys_user")
public class UserEntity extends BaseEntity implements Serializable {
/**
* Id 表示为表 ID
* GenerationType.IDENTITY 使用自增长主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String nickname;
private Integer age;
private String email;
private String password;
}
5、启动测试
插入成功我们,看一下数据,没得任何问题,数据填充成功,不过这个时间格式还得再调整调整。再配置文件里面添加 Jackson 的配置就行了。
spring:
jackson:
# 全局日期格式化
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
本节内容到这里就结束啦。