Spring Boot 整合 Spring Data Jpa

Spring Data JPA 是 Spring 框架提供的一个模块,用于简化与关系型数据库的交互和数据访问。它基于JPA(Java Persistence API)标准,并提供了一组易于使用的API和工具,帮助开发人员更轻松地进行数据库操作。通过Spring Data JPA,开发人员可以通过编写简洁的代码来执行常见的 CRUD 操作,同时还支持高级查询、分页、事务管理等功能。它的目标是提供一种更简单、更高效的方式来处理数据库操作,减少开发人员的工作量,并提高应用程序的可维护性和可扩展性。

本文将会指导你如何在 Spring Boot 应用中整合、使用 Spring Data Jpa。

软件版本:

  • Java:17
  • Spring Boot:3.1.3
  • MySQL:8.0.27

创建工程

点击 start.springboot.io 快速创建 Spring Boot 整合 Spring Data Jpa 的示例应用。

我们选择了 spring-boot-starter-webspring-boot-starter-data-jpamysql-connector-jspring-boot-starter-test 依赖。

pom.xml 如下:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

spring-boot-starter-data-jpa 默认使用 Hibernate 作为 JPA 实现。本文使用 MYSQL 数据库进行演示,如果你使用其他数据库需要修改驱动。

项目设置

创建实体类

cn.springdoc.demo.entity 包中创建 SysUser 实体类。

package cn.springdoc.demo.entity;

import java.time.LocalDateTime;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "sys_user") // 表名称
public class SysUser {

    // ID
    @Id
    @Column
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 使用数据库自增
    private Long id;

    // 账户
    @Column
    private String account;

    // 是否启用
    @Column
    private Boolean enabled;

    // 创建时间
    @Column
    private LocalDateTime createAt;

    // 省略 get/set/toString 方法
}

@Entity 表示这个类是一个需要受 EntityManager 管理的实体类。@Table(name = "sys_user") 注解指定了实体在数据库中所对应的表名称。@Id 用于标识ID字段,@GeneratedValue(strategy = GenerationType.IDENTITY) 注解指定了 ID 值的生成方式,其中 GenerationType.IDENTITY 表示主键由数据库自动生成(自增)。@Column 注解表示对象字段和数据表列的映射关系。

注意,实体类一定要有一个 无参构造函数

SysUser 实体对应的表结构如下:

CREATE TABLE `sys_user` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `account` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '账户',
  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `enabled` tinyint unsigned NOT NULL COMMENT '是否启用。0:禁用,1:启用',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统用户';

本文重点在于介绍如何整合、使用 spring-data-jpa,对于更多可用的实体注解,及其详细用法可以参考 官方文档

创建 Repository 接口

cn.springdoc.demo.repository 包下创建 SysUserRepository 接口,并且继承 JpaRepositoryJpaSpecificationExecutor 接口。

package cn.springdoc.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

import cn.springdoc.demo.entity.SysUser;

@Repository
public interface SysUserRepository extends JpaRepository<SysUser, Long>, JpaSpecificationExecutor <SysUser> {

}

通过继承 JpaRepositoryJpaSpecificationExecutor 就可以获得已经预定义的各种 CRUD 方法。其中 JpaRepository 的泛型对象是实体类型和 ID 类型,JpaSpecificationExecutor 的泛型对象只有实体类型。

使用 @Repository 注解表示这是一个 Repository 接口。

定义 @EnableJpaRepositories/ @EntityScan 注解

接下来,需要在启动类上定义 @EnableJpaRepositories@EntityScan 注解,分别指定 repository 和实体类所在的包。

package cn.springdoc.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@EnableJpaRepositories(basePackages = "cn.springdoc.demo.repository")  // repository 所在的包
@EntityScan(basePackages = "cn.springdoc.demo.entity") // 实体所在的包
public class SpringdocJpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringdocJpaApplication.class, args);
    }

}

这两个注解的 basePackages 属性都是一个 String[],也就是说可以同时指定多个目录。

application.properties 配置

你也可以把 application.properties 替换为 application.yaml

#---------------------
# 数据源配置
#---------------------
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=root

#---------------------
# 日志配置
#---------------------
# 打印执行 SQL 中绑定的参数
logging.level.org.hibernate.orm.jdbc.bind="TRACE"

#---------------------
# JPA 配置
#---------------------
# 数据库方言
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
# 是否打印 SQL
spring.jpa.show-sql=true
# 格式化打印的 SQL
spring.jpa.properties.hibernate.format_sql=true

# 应用启动时候的建表策略
spring.jpa.hibernate.ddl-auto=none

上述常用配置中,数据源的配置是必须的。其他配置项都有默认值。

其中 spring.jpa.hibernate.ddl-auto 比较重要,表示建表的策略,可选的枚举值如下:

  • create:不管表是否存在,每次启动都会重新建表(会导致数据丢失)。
  • create-drop:启动的时候创建表,程序退出(SessionFactory 关闭)的时候删除表。
  • none:不进行任何操作。
  • update:如果数据表不存在则创建,在实体对象被修改后,下次启动重新修改表结构(不会删除已经存在的数据)。
  • validate:启动的时候验证数据表的结构。

建议使用 none,手动维护数据表结构,以避免不小心修改了实体对象后导致表结构被修改,甚至是数据丢失。

这里仅列出了部分核心配置,关于完整的配置项及其详情请参阅 官方文档

测试

创建测试类 SpringdocJpaApplicationTests

package cn.springdoc.demo;

import java.time.LocalDateTime;
import java.util.Optional;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

import cn.springdoc.demo.entity.SysUser;
import cn.springdoc.demo.repository.SysUserRepository;

@SpringBootTest(classes = SpringdocJpaApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
class SpringdocJpaApplicationTests {

    static final Logger logger = LoggerFactory.getLogger(SpringdocJpaApplicationTests.class);

    @Autowired
    SysUserRepository sysUserRepository;

    @Test
    void contextLoads() {
        
        // 创建实体类
        SysUser sysUser = new SysUser();
        sysUser.setAccount("springdoc");
        sysUser.setEnabled(Boolean.TRUE);
        sysUser.setCreateAt(LocalDateTime.now());
        
        // 保存实体
        this.sysUserRepository.saveAndFlush(sysUser);
        
        // 根据自增ID检索实体
        Optional<SysUser> user = this.sysUserRepository.findById(sysUser.getId());
        
        logger.info("user={}", user.get());
    }
}

在该测试中,先创建、初始化了一个 SysUser 实体对象,然后使用 sysUserRepository 先进行保存。然后再根据回写的自增ID,进行检索。

执行测试,输出日志如下:

Hibernate: 
    insert 
    into
        sys_user
        (account,create_at,enabled) 
    values
        (?,?,?)
2023-09-04T10:21:05.841+08:00 TRACE 11444 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter [1] as [VARCHAR] - [springdoc]
2023-09-04T10:21:05.842+08:00 TRACE 11444 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter [2] as [TIMESTAMP] - [2023-09-04T10:21:05.747462800]
2023-09-04T10:21:05.843+08:00 TRACE 11444 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter [3] as [BOOLEAN] - [true]
Hibernate: 
    select
        s1_0.id,
        s1_0.account,
        s1_0.create_at,
        s1_0.enabled 
    from
        sys_user s1_0 
    where
        s1_0.id=?
2023-09-04T10:21:05.964+08:00 TRACE 11444 --- [           main] org.hibernate.orm.jdbc.bind              : binding parameter [1] as [BIGINT] - [1]
2023-09-04T10:21:05.982+08:00  INFO 11444 --- [           main] c.s.demo.SpringdocJpaApplicationTests    : user=SysUser [id=1, account=springdoc, enabled=true, createAt=2023-09-04T10:21:06]

测试通过,日志中可以看到测试所执行的 insertselect SQL 以及绑定的参数,一切OK,整合成功!

最后附上工程完整的目录结构。

Sring Boot 整合 Spring Data Jpa 项目的结构

总结

在 Spring Boot 中整合、使用 JPA 的步骤如下:

  1. 添加 spring-boot-starter-data-jpa 依赖,以及对应的数据库驱动。
  2. 创建实体类,使用 @Entity@Table 进注解,必须要有 @Id 字段。
  3. 创建 Repository 接口,可以通过继承 JpaRepositoryJpaSpecificationExecutor 接口来继承预定义的 CRUD 方法。
  4. 在配置文件(properties/yaml)中配置数据源、JPA 的属性。
  5. 在启动类上添加 @EnableJpaRepositories@EntityScan 注解,以指定 Repository 接口和 Entity 类所在的包。