十八:Spring Boot 依赖(3)-- spring-boot-starter-data-jpa 依赖详解

目录

[1. 理解 JPA(Java Persistence API)](#1. 理解 JPA(Java Persistence API))

[1.1 什么是 JPA?](#1.1 什么是 JPA?)

[1.2 JPA 与 Hibernate 的关系](#1.2 JPA 与 Hibernate 的关系)

[1.3 JPA 的基本注解:@Entity, @Table, @Id, @GeneratedValue](#1.3 JPA 的基本注解:@Entity, @Table, @Id, @GeneratedValue)

[1.4 JPA 与数据库表的映射](#1.4 JPA 与数据库表的映射)

[2. Spring Data JPA 概述](#2. Spring Data JPA 概述)

[2.1 什么是 Spring Data JPA?](#2.1 什么是 Spring Data JPA?)

[2.2 spring-boot-starter-data-jpa 的作用](#2.2 spring-boot-starter-data-jpa 的作用)

[2.3 配置 spring-boot-starter-data-jpa 依赖](#2.3 配置 spring-boot-starter-data-jpa 依赖)

[3. Spring Boot 和 JPA 整合案例](#3. Spring Boot 和 JPA 整合案例)

[3.1 创建一个基本的 CRUD Web 应用](#3.1 创建一个基本的 CRUD Web 应用)

[3.2 Spring Boot 集成 JPA 的实践](#3.2 Spring Boot 集成 JPA 的实践)

[3.2.1 引入基本的 Spring Data JPA 依赖](#3.2.1 引入基本的 Spring Data JPA 依赖)

[3.2.2 引入数据库连接池(默认使用 HikariCP)推荐默认的就行](#3.2.2 引入数据库连接池(默认使用 HikariCP)推荐默认的就行)

[使用 DBCP2 连接池](#使用 DBCP2 连接池)

[使用 C3P0 连接池](#使用 C3P0 连接池)

[3.2.3 数据库驱动](#3.2.3 数据库驱动)

[3.2.4 配置 application.properties 或 application.yml](#3.2.4 配置 application.properties 或 application.yml)

[4. 实体类与数据库表映射](#4. 实体类与数据库表映射)

[4.1 数据库建表语句](#4.1 数据库建表语句)

[4.2 创建一个 JPA 实体类](#4.2 创建一个 JPA 实体类)

[4.2 使用 JPA 的基本类型映射(如:@Column, @OneToMany, @ManyToOne 等)](#4.2 使用 JPA 的基本类型映射(如:@Column, @OneToMany, @ManyToOne 等))

[JPA 提供了许多注解来描述实体类与数据库表之间的关系,包括对列、主键、关系的映射。](#JPA 提供了许多注解来描述实体类与数据库表之间的关系,包括对列、主键、关系的映射。)

[4.2.1 @Column](#4.2.1 @Column)

[4.2.2 @ManyToOne 和 @OneToMany](#4.2.2 @ManyToOne 和 @OneToMany)

[4.2.3 @OneToOne](#4.2.3 @OneToOne)

[4.2.4 @ManyToMany](#4.2.4 @ManyToMany)

[5. Spring Data JPA Repository](#5. Spring Data JPA Repository)

[5.1 JpaRepository 和 CrudRepository 接口](#5.1 JpaRepository 和 CrudRepository 接口)

[5.1.1 CrudRepository 接口](#5.1.1 CrudRepository 接口)

[5.1.2 JpaRepository 接口(推荐,实现的多)](#5.1.2 JpaRepository 接口(推荐,实现的多))

[5.2 创建 Repository 接口](#5.2 创建 Repository 接口)

[5.3 常用的 CRUD 操作(保存、查询、删除、更新)](#5.3 常用的 CRUD 操作(保存、查询、删除、更新))

[5.4 自定义查询方法(如:通过方法名自动生成查询)](#5.4 自定义查询方法(如:通过方法名自动生成查询))

[5.5 使用 @Query 注解编写自定义 SQL 查询](#5.5 使用 @Query 注解编写自定义 SQL 查询)

[5.5.1 使用 JPQL(推荐方式)](#5.5.1 使用 JPQL(推荐方式))

[5.5.2 使用原生 SQL](#5.5.2 使用原生 SQL)

[5.5.3 使用 @Modifying 和 @Transactional 进行更新/删除操作](#5.5.3 使用 @Modifying 和 @Transactional 进行更新/删除操作)

[6. JPA 查询功能](#6. JPA 查询功能)

[6.1 JPQL查询](#6.1 JPQL查询)

[6.2 Criteria API(动态查询)](#6.2 Criteria API(动态查询))

[6.3 使用 Spring Data JPA 提供的分页与排序功能](#6.3 使用 Spring Data JPA 提供的分页与排序功能)

[6.3.1 分页](#6.3.1 分页)

[6.3.2 排序​​​​​​​​​​​​​​​​​​​​​](#6.3.2 排序)

[7. 事务管理](#7. 事务管理)

[7.1 什么是事务?](#7.1 什么是事务?)

[7.2 Spring 的事务管理基础](#7.2 Spring 的事务管理基础)

[7.3 使用 @Transactional 注解](#7.3 使用 @Transactional 注解)

[7.4 事务的传播行为和隔离级别](#7.4 事务的传播行为和隔离级别)

[7.5 事务回滚机制](#7.5 事务回滚机制)

[8. 配置与优化](#8. 配置与优化)

[8.1 配置数据源(DataSource)](#8.1 配置数据源(DataSource))

[8.2 使用 application.properties 配置数据库连接池](#8.2 使用 application.properties 配置数据库连接池)

[8.3 配置 JPA 的 Hibernate 属性(如:DDL 自动生成策略、SQL 日志输出等)](#8.3 配置 JPA 的 Hibernate 属性(如:DDL 自动生成策略、SQL 日志输出等))

[8.3.1 ​​​​​​​​​​​​​​配置 Hibernate 的 DDL 自动生成策略](#8.3.1 配置 Hibernate 的 DDL 自动生成策略)

[8.3.2 配置 Hibernate 的 SQL 日志输出](#8.3.2 配置 Hibernate 的 SQL 日志输出)


1. 理解 JPA(Java Persistence API)

### 1.1 什么是 JPA?

* **JPA**(Java Persistence API)是 Java 平台的一部分,专门用于简化 Java 应用程序与关系型数据库之间的交互。它定义了一种标准的方式来映射 Java 对象到数据库表,同时提供了操作数据库(增、删、改、查)的接口,支持对象关系映射(ORM)。JPA 作为一种规范,定义了如何在 Java 应用程序中实现持久化,但并没有提供具体的实现。

  JPA 的实现可以有多个,其中最常见的实现是 **Hibernate**,但也有其他实现,如 EclipseLink、OpenJPA 等。
### 1.2 JPA 与 Hibernate 的关系

* **Hibernate** 是一个非常流行的 JPA 实现。它是一个开源的 ORM 框架,遵循了 JPA 的规范,并提供了一些额外的功能,比如自动生成 SQL、缓存机制等。简单来说,Hibernate 实现了 JPA 规范,它可以作为 JPA 的一个持久化提供者。

* **JPA 是一个规范**,它规定了 Java 对象和关系型数据库之间的映射规则以及操作数据库的 API。

* **Hibernate 是一个实现**,它提供了具体的代码来执行 JPA 规范中定义的操作。

* 例如,你可以使用 Hibernate 来实现 JPA 标准的接口,或者直接使用 Hibernate 提供的扩展功能。JPA 的目标是解耦持久化层,使得应用程序不依赖于某一个具体的实现,而是遵循 JPA 规范,便于切换实现。
### 1.3 JPA 的基本注解:`@Entity`, `@Table`, `@Id`, `@GeneratedValue`

* 在 JPA 中,注解是实现对象关系映射的关键。常见的注解包括:

* **@Entity** :标识一个类是一个实体类,表示该类的实例将会与数据库中的一条记录映射。每个 `@Entity` 注解的类对应数据库中的一张表。

  *

        @Entity
        public class User {
            // 类体
        }

* **@Table** :指定数据库表的名称。如果不使用该注解,默认情况下,实体类的名字将会被用作表名。通过 `@Table` 可以自定义表名以及其他表级别的配置(如 schema、catalog 等)。

  *

        @Entity
        @Table(name = "users")
        public class User {
            // 类体
        }

* **@Id**:指定实体类的主键字段。每个实体类必须至少有一个主键字段,JPA 使用该字段来唯一标识实体类的对象。

  *

        @Entity
        public class User {
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            private Long id;
            // 其他字段
        }

* **@GeneratedValue** :指定主键的生成策略。通常与 `@Id` 一起使用,表示主键的值如何自动生成。可以选择不同的生成策略,如 `AUTO`、`IDENTITY`、`SEQUENCE` 等。

  * `GenerationType.AUTO`:JPA 容器根据数据库的不同选择合适的生成策略。
  * `GenerationType.IDENTITY`:通过数据库的自增列生成主键值。
  * `GenerationType.SEQUENCE`:使用数据库序列生成主键值(通常用于数据库支持序列的情况)。
  * `GenerationType.TABLE`:使用一个特殊的数据库表来生成主键(这种方式比较少见)。
  *

        @Entity
        public class User {
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            private Long id;
            // 其他字段
        }
### 1.4 JPA 与数据库表的映射

* JPA 将 Java 对象(类)和数据库表之间进行映射,这种映射关系主要由注解和配置来定义。JPA 实现了**对象关系映射**(ORM),它能够将数据库中的记录转换为 Java 对象,并允许开发人员通过操作对象来间接操作数据库。

* 常见的映射关系如下:

  * **一对一关系**(@OneToOne):一个实体对应另一个实体的一个实例。

  * **一对多关系**(@OneToMany):一个实体对应多个实体。

  * **多对一关系**(@ManyToOne):多个实体对应一个实体。

  * **多对多关系**(@ManyToMany):多个实体对应多个实体。

* 举个例子,假设我们有一个 `User` 实体类和一个 `Address` 实体类,一个用户可能有多个地址(多对一关系),那么我们可以使用 `@OneToMany` 和 `@ManyToOne` 来进行映射:

*

  ```java
  @Entity
  public class User {
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
      
      @OneToMany(mappedBy = "user")
      private List<Address> addresses;
      
      // 其他字段和方法
  }

  @Entity
  public class Address {
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
      
      @ManyToOne
      @JoinColumn(name = "user_id")
      private User user;
      
  }
  ```

2. Spring Data JPA 概述

### 2.1 什么是 Spring Data JPA?

* **Spring Data JPA** 是 Spring Data 项目的一个子项目,它简化了基于 JPA(Java Persistence API)进行数据库操作的开发。Spring Data JPA 基于 JPA 规范,结合 Spring 的优势,提供了对 JPA 持久化技术的简化支持,特别是在 Spring Boot 应用中使用时,极大地减少了样板代码(boilerplate code)。

  Spring Data JPA 通过提供一个简单的接口 `JpaRepository` 和一些其他的 CRUD 接口,使开发者可以轻松实现对数据库的基本操作,如增、删、改、查等,而无需编写实现代码。它通过动态代理技术,自动生成实现类,简化了开发过程。

  **核心特性**:
  * **简化 CRUD 操作**:无需编写实现代码,直接通过继承接口来使用常见的数据库操作。

  * **基于 JPA**:继承了 JPA 的标准特性,如实体类映射、查询方法等。

  * **查询方法自动生成**:Spring Data JPA 根据方法名自动生成 SQL 查询,无需手写 SQL。

  * **分页和排序支持**:内置分页、排序功能,支持复杂的查询。
### 2.2 `spring-boot-starter-data-jpa` 的作用

* **`spring-boot-starter-data-jpa`** 是 Spring Boot 提供的一个启动器(starter),用于集成和配置 Spring Data JPA。在应用中引入该依赖后,Spring Boot 会自动配置与 JPA 相关的 Bean,简化了 JPA 的配置和初始化工作。

* **作用**:

  * 自动配置数据源(`DataSource`)、JPA 相关的配置(如 `EntityManagerFactory`)、事务管理器(`PlatformTransactionManager`)等。

  * 集成常见的 JPA 实现(如 Hibernate),并提供简单的配置方式。

  * 支持自动创建数据库表和执行初始化 SQL(根据 `spring.jpa.hibernate.ddl-auto` 配置项)。

  * 提供分页、排序等功能,结合 Spring Data JPA 的接口,可以非常方便地执行复杂查询。

* **自动配置**:

  * 配置数据库连接、实体类管理、事务管理等。

  * 使用 `JpaRepository` 等接口简化持久化操作。
### 2.3 配置 `spring-boot-starter-data-jpa` 依赖

* 要在 Spring Boot 项目中使用 Spring Data JPA,首先需要在项目的 `pom.xml` 文件中添加 `spring-boot-starter-data-jpa` 依赖。这个依赖会自动引入 Spring Data JPA 和 Hibernate 等相关库。
* 在 `pom.xml` 中添加 `spring-boot-starter-data-jpa` 依赖。
  *

    ```java
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    ```

3. Spring Boot 和 JPA 整合案例

### 3.1 创建一个基本的 CRUD Web 应用
### 3.2 Spring Boot 集成 JPA 的实践

*

  #### 3.2.1 引入基本的 Spring Data JPA 依赖

  *

    ```java
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    ```

*

  #### 3.2.2 引入数据库连接池(默认使用 HikariCP)推荐默认的就行

  * Spring Boot 默认使用 HikariCP 作为连接池,它不需要额外的依赖。如果你使用的是 HikariCP,你不需要手动添加依赖,因为它已经包含在 Spring Boot 的默认配置中。

  * 如果你需要使用其他连接池(例如 DBCP2 或 C3P0),可以选择手动添加相应的依赖:

    *

      ##### 使用 DBCP2 连接池

      *

        ```java
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
        </dependency>
        ```

    *

      ##### 使用 C3P0 连接池

      *

        ```java
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
        </dependency>
        ```

*

  #### 3.2.3 数据库驱动

  * 根据你使用的数据库,你需要添加相应的数据库驱动。例如,假设你使用 MySQL,你需要添加 MySQL 驱动:
  *

    ```java
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    ```

*

  #### 3.2.4 配置 `application.properties` 或 `application.yml`

  * application.properties
    *

      ```java
      # 数据库配置
      spring.datasource.url=jdbc:mysql://localhost:3306/lirui?useSSL=false&serverTimezone=UTC
      spring.datasource.username=root
      spring.datasource.password=root
      spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

      # 使用HikariCP作为连接池(Spring Boot默认使用HikariCP)
      spring.datasource.hikari.maximum-pool-size=10
      spring.datasource.hikari.minimum-idle=5

      # JPA配置
      spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
      spring.jpa.hibernate.ddl-auto=update
      spring.jpa.show-sql=true
      spring.jpa.properties.hibernate.format_sql=true
      ```

  * application.yml
    *

      ```java
      spring:
        datasource:
          url: jdbc:mysql://localhost:3306/lirui?useSSL=false&serverTimezone=UTC
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver

        jpa:
          hibernate:
            ddl-auto: update
          show-sql: true
          database-platform: org.hibernate.dialect.MySQL5Dialect

        hikari:
          maximum-pool-size: 10
          minimum-idle: 5
      ```

4. 实体类与数据库表映射

### 4.1 数据库建表语句

*

      CREATE TABLE `users` (
        `id` int(11) NOT NULL AUTO_INCREMENT,
        `name` varchar(255) NOT NULL,
        `email` varchar(255) NOT NULL,
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
### 4.2 创建一个 JPA 实体类

* ![](https://i-blog.csdnimg.cn/direct/a5a98123443749d9852ef4a9eff07bef.png)
*

  ```java
  package com.lirui.springbootmoduledemo.dao;


  import javax.persistence.*;

  @Entity  // 标识该类为一个 JPA 实体类
  @Table(name = "users")  // 映射到数据库中的 users 表
  public class User {

      @Id  // 主键标识
      @GeneratedValue(strategy = GenerationType.IDENTITY)  // 自增主键
      @Column(name = "id")  // 映射到数据库中的 id 列
      private Integer id;

      @Column(name = "name", nullable = false, length = 255)  // 映射到 name 列
      private String name;

      @Column(name = "email", nullable = false, length = 255)  // 映射到 email 列
      private String email;

      // Getter 和 Setter 方法
      public Integer getId() {
          return id;
      }

      public void setId(Integer id) {
          this.id = id;
      }

      public String getName() {
          return name;
      }

      public void setName(String name) {
          this.name = name;
      }

      public String getEmail() {
          return email;
      }

      public void setEmail(String email) {
          this.email = email;
      }

      @Override
      public String toString() {
          return "User{" +
                  "id=" + id +
                  ", name='" + name + '\'' +
                  ", email='" + email + '\'' +
                  '}';
      }
  }
  ```

* **`@Entity`** :声明 `User` 类是一个 JPA 实体类,Spring Data JPA 会根据该类生成对应的数据库表操作。

* **`@Table(name = "users")`** :指定实体类对应的数据库表名为 `users`。如果类名与表名相同,可以省略这个注解。

* **`@Id`** :标识该字段是实体的主键(`id` 字段)。

* **`@GeneratedValue(strategy = GenerationType.IDENTITY)`** :指定主键的生成策略为 `IDENTITY`,表示使用数据库的自增机制。适用于 MySQL 等数据库。

* **`@Column(name = "column_name")`** :映射实体类字段到数据库表中的列(如 `name`、`email`)。使用 `nullable = false` 来指定列不允许为 `NULL`,并且可以设置列的最大长度(如 `length = 255`)。

* **`Integer` 类型的 `id` 字段** :我们将 `id` 定义为 `Integer` 类型,这样它可以自动与数据库中的 `int` 类型映射。如果你更倾向于使用 `Long` 类型,也可以使用 `Long` 类型。
### 4.2 使用 JPA 的基本类型映射(如:`@Column`, `@OneToMany`, `@ManyToOne` 等)

*

  #### JPA 提供了许多注解来描述实体类与数据库表之间的关系,包括对列、主键、关系的映射。

*

  #### 4.2.1 @Column

  * 映射实体类属性到数据库表的列。
  * 可设置属性(如:`nullable`、`length`、`unique`、`columnDefinition` 等)。
    * nullable

      * 定义列是否允许 `NULL` 值。

      * **默认值** :`true`(表示列允许为 `NULL`)。

      * **使用场景** :如果我们希望某个字段在数据库中不能为空,可以将 `nullable` 设置为 `false`,表示该列必须有值。

    * length

      * 定义字符串类型的列(如 `String`)的最大长度。

      * **使用场景** :主要用于设置数据库中 `VARCHAR` 类型的字段的最大长度。如果没有显式设置,默认长度为 255。

      * **适用类型** :`String`、`char` 和 `Character` 类型。

    * unique

      * **功能**:指定列是否应该是唯一的。

      * **默认值** :`false`(表示列不要求唯一)。

      * **使用场景** :如果我们希望某个字段的值在数据库中是唯一的,可以设置 `unique = true`,表示该列的值在数据库表中必须是唯一的。

    * columnDefinition

      * **功能**:指定列的具体定义,包括数据类型、默认值、约束等。

      * **使用场景** :当我们需要对列进行更精细的控制,例如设置数据库列的数据类型、默认值、约束条件等时,可以使用 `columnDefinition`。它允许你直接写 SQL 语句来控制列的定义。

      *

        ```java
        @Column(columnDefinition = "VARCHAR(100) NOT NULL DEFAULT 'Unknown'")  
        private String username;
        ```

    * name

      * 定义数据库中列的名称。如果不设置,默认使用字段名。

    * insertable

      * 指定是否允许在插入时写入该列。默认值是 `true`。

    * updatable

      * 指定是否允许更新该列。默认值是 `true`。

    * `precision` 和 `scale`

      * 主要用于 `BigDecimal` 类型,用于定义列的精度和小数位数。

*

  #### `4.2.2 @ManyToOne` 和 `@OneToMany`

  * `​​​​​@ManyToOne`:表示多对一的关系。例如,一个用户有多个订单,但每个订单只能属于一个用户。

  * `@OneToMany`:表示一对多的关系。例如,一个用户可以有多个订单。

  *

    ```java
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.OneToMany;
    import java.util.List;

    @Entity
    @Table(name = "user")
    public class User {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        private String username;

        @OneToMany(mappedBy = "user")  // 通过 user 字段映射到 Order
        private List<Order> orders;  // 一对多关系

        // Getter 和 Setter
    }
    ```

    ```java
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.ManyToOne;

    @Entity
    @Table(name = "order")
    public class Order {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        private String productName;

        @ManyToOne  // 多对一关系
        @JoinColumn(name = "user_id")  // 映射外键
        private User user;  // 每个订单对应一个用户

        // Getter 和 Setter
    }
    ```

*

  #### `4.2.3 @OneToOne`

  * `​​​​​​​​​​​​​​`如果实体之间存在一对一关系,可以使用 `@OneToOne` 注解。

    ```java
    @Entity
    public class Profile {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        private String bio;

        @OneToOne
        @JoinColumn(name = "user_id")  // 外键字段
        private User user;  // 一对一关系

        // Getter 和 Setter
    }
    ```

*

  #### 4.2.4 @ManyToMany

  * 如果两个实体之间存在多对多的关系,可以使用 `@ManyToMany` 注解。

    ```java
    @Entity
    public class Course {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        private String name;

        @ManyToMany
        @JoinTable(
            name = "course_student", 
            joinColumns = @JoinColumn(name = "course_id"), 
            inverseJoinColumns = @JoinColumn(name = "student_id")
        )
        private List<Student> students;

        // Getter 和 Setter
    }
    ```

5. Spring Data JPA Repository

### 5.1 `JpaRepository` 和 `CrudRepository` 接口

*

  #### 5.1.1 `CrudRepository` 接口

  * `CrudRepository` 是 Spring Data JPA 提供的基本接口之一,它提供了最基本的 CRUD 操作方法。它是所有 JPA Repository 接口的基础。

  * 常用的方法:

    * `save(S entity)`:保存实体。

    * `findById(ID id)`:根据主键查找实体。

    * `findAll()`:查找所有实体。

    * `deleteById(ID id)`:根据 ID 删除实体。

*

  #### `5.1.2 JpaRepository` 接口(推荐,实现的多)

  * `JpaRepository` 扩展了 `PagingAndSortingRepository` 和 `CrudRepository`,因此它不仅提供了 CRUD 功能,还支持分页和排序功能。

  * 常用的方法:

    * `findAll(Pageable pageable)`:分页查询。

    * `findAll(Sort sort)`:排序查询。

    * `flush()`:刷新持久化上下文。

    * `saveAndFlush(S entity)`:保存并立即刷新实体。
### 5.2 创建 Repository 接口

* Spring Data JPA 中,我们只需要定义一个接口并继承 `JpaRepository` 或 `CrudRepository`,Spring 会自动实现所有方法。

*

  ```java
  @Repository  // 标记为一个 Spring 的 Repository
  public interface UserRepository extends JpaRepository<User, Integer> {
      // 可以在这里定义查询方法,例如:
      // List<User> findByName(String name);
  }
  ```
### 5.3 常用的 CRUD 操作(保存、查询、删除、更新)

*

  ```java
  @Service  // 标注为 Spring 管理的 Service 类
  public class UserService {

      @Autowired
      private UserRepository userRepository;  // 注入 UserRepository

      // 保存用户
      public User saveUser(User user) {
          return userRepository.save(user);  // 调用 UserRepository 的 save 方法
      }

      // 获取所有用户
      public List<User> getAllUsers() {
          return userRepository.findAll();  // 获取所有用户
      }

      // 根据 ID 查找用户
      public Optional<User> getUserById(Integer id) {
          return userRepository.findById(id);  // 根据 ID 查找用户
      }

      // 删除用户
      public void deleteUser(Integer id) {
          userRepository.deleteById(id);  // 删除用户
      }
  }
  ```
### 5.4 自定义查询方法(如:通过方法名自动生成查询)

* Spring Data JPA 支持通过方法名自动生成查询。只需要按照一定的规则命名方法,Spring Data JPA 就能自动实现查询。

* 常见的查询方法命名规则:
  * **根据属性查找** :`findBy<属性名>`  

    例如:`findByName(String name)`,表示根据 `name` 属性查找。

  * **根据多个属性查找** :`findBy<属性1>And<属性2>`  

    例如:`findByNameAndEmail(String name, String email)`,表示根据 `name` 和 `email` 属性查找。

  * **根据属性模糊查找** :`findBy<属性名>Like`  

    例如:`findByNameLike(String name)`,表示根据 `name` 属性模糊查找。

*

  ```java
  @Repository  // 标记为一个 Spring 的 Repository
  public interface UserRepository extends JpaRepository<User, Integer> {
      // 可以在这里定义查询方法,例如:
      List<User> findByName(String name);  // 根据名字查询用户
      List<User> findByEmail(String email);  // 根据 email 查询用户
      List<User> findByNameAndEmail(String name, String email);  // 根据名字和 email 查询用户
      List<User> findByNameLike(String name);  // 根据名字模糊查询用户
  }
  ```
### 5.5 使用 `@Query` 注解编写自定义 SQL 查询

* 如果方法名不能满足需求,或者你需要更复杂的查询,可以使用 `@Query` 注解来编写自定义的 JPQL或者原生 SQL 查询。

*

  #### 5.5.1 使用 JPQL(推荐方式)

  JPQL 是针对实体对象的查询语言,而不是针对数据库表的 SQL 语言。你可以使用 `@Query` 注解来编写 JPQL 查询。
  *

    ```java
    package com.lirui.springbootmoduledemo.repository;

    import com.lirui.springbootmoduledemo.dao.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.stereotype.Repository;

    import java.util.List;

    @Repository  // 标记为一个 Spring 的 Repository
    public interface UserRepository extends JpaRepository<User, Integer> {
        // 可以在这里定义查询方法,例如:
        @Query("SELECT u FROM User u WHERE u.name = ?1")  // JPQL 查询
        List<User> findByName(String name);  // 根据名字查询用户
        List<User> findByEmail(String email);  // 根据 email 查询用户
        List<User> findByNameAndEmail(String name, String email);  // 根据名字和 email 查询用户
        List<User> findByNameLike(String name);  // 根据名字模糊查询用户
    }
    ```

*

  #### 5.5.2 使用原生 SQL

  如果你需要直接使用原生 SQL 查询,可以通过 `nativeQuery = true` 来指定。
  *

    ```java
        @Query(value = "SELECT * FROM users WHERE email LIKE %?1%", nativeQuery = true)  // 原生 SQL 使用 LIKE 查询
        List<User> findByEmail(String email);  // 根据 email 查询用户
    ```

*

  #### 5.5.3 使用 `@Modifying` 和 `@Transactional` 进行更新/删除操作

  * 如果你需要执行更新或删除操作,必须使用 `@Modifying` 注解,并且方法应该是事务性的。

  *

    ```java
    package com.lirui.springbootmoduledemo.repository;

    import com.lirui.springbootmoduledemo.dao.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Modifying;
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.stereotype.Repository;

    import javax.transaction.Transactional;
    import java.util.List;

    @Repository  // 标记为一个 Spring 的 Repository
    public interface UserRepository extends JpaRepository<User, Integer> {
        // 可以在这里定义查询方法,例如:
        @Query("SELECT u FROM User u WHERE u.name = ?1")  // JPQL 查询
        List<User> findByName(String name);  // 根据名字查询用户

        @Query(value = "SELECT * FROM users WHERE email LIKE %?1%", nativeQuery = true)  // 原生 SQL 使用 LIKE 查询
        List<User> findByEmail(String email);  // 根据 email 查询用户
        List<User> findByNameAndEmail(String name, String email);  // 根据名字和 email 查询用户
        List<User> findByNameLike(String name);  // 根据名字模糊查询用户

        @Modifying  // 标注为修改操作
        @Transactional  // 事务支持
        @Query("UPDATE User u SET u.name = ?1 WHERE u.id = ?2")
        int updateUserNameById(String name, Integer id);

        @Modifying
        @Transactional
        @Query("DELETE FROM User u WHERE u.id = ?1")
        void deleteUserById(Integer id);
    }
    ```

6. JPA 查询功能

### 6.1 JPQL查询

* 同上
### 6.2 Criteria API(动态查询)

* **Criteria API** 是 JPA 提供的一个用于构建动态查询的 API,它允许我们通过编程方式构造查询,适用于那些查询条件动态变化的情况。使用 Criteria API,可以避免拼接字符串,避免 SQL 注入风险。

* Criteria API 的常用组件:

  * **`CriteriaBuilder`**:用于创建查询的各种条件和表达式。

  * **`CriteriaQuery`**:表示查询的具体内容。

  * **`Root`**:表示查询的根对象,通常是实体类。

  * **`Predicate`**:表示查询条件。

*

  ```java
  package com.lirui.springbootmoduledemo.service;

  import com.lirui.springbootmoduledemo.dao.User;
  import com.lirui.springbootmoduledemo.repository.UserRepository;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.stereotype.Service;

  import javax.persistence.EntityManager;
  import javax.persistence.criteria.CriteriaBuilder;
  import javax.persistence.criteria.CriteriaQuery;
  import javax.persistence.criteria.Predicate;
  import javax.persistence.criteria.Root;
  import java.util.List;
  import java.util.Optional;

  @Service  // 标注为 Spring 管理的 Service 类
  public class UserService {

      @Autowired
      private UserRepository userRepository;  // 注入 UserRepository

      @Autowired
      private EntityManager entityManager;

      public List<User> findUsersByCriteria(String name, String email) {
          CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
          CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
          Root<User> userRoot = criteriaQuery.from(User.class);  // 设置查询根对象

          // 构建查询条件
          Predicate predicate = criteriaBuilder.conjunction();  // 创建一个"与"的条件

          if (name != null) {
              predicate = criteriaBuilder.and(predicate, criteriaBuilder.equal(userRoot.get("name"), name));  // 添加 name 的条件
          }
          if (email != null) {
              predicate = criteriaBuilder.and(predicate, criteriaBuilder.equal(userRoot.get("email"), email));  // 添加 email 的条件
          }

          criteriaQuery.where(predicate);  // 设置查询条件

          return entityManager.createQuery(criteriaQuery).getResultList();  // 执行查询
      }
      // 保存用户
      public User saveUser(User user) {
          return userRepository.save(user);  // 调用 UserRepository 的 save 方法
      }

      // 获取所有用户
      public List<User> getAllUsers() {
          return userRepository.findAll();  // 获取所有用户
      }

      // 根据 ID 查找用户
      public Optional<User> getUserById(Integer id) {
          return userRepository.findById(id);  // 根据 ID 查找用户
      }

      // 删除用户
      public void deleteUser(Integer id) {
          userRepository.deleteById(id);  // 删除用户
      }
  }
  ```
### 6.3 使用 Spring Data JPA 提供的分页与排序功能

*

  #### 6.3.1 分页

  * Spring Data JPA 为我们提供了 `Pageable` 接口,用来表示分页请求。`Pageable` 可以指定页码(page)和每页大小(size)。
  *

    ```java
        @Query("SELECT u FROM User u WHERE u.name = ?1")  // JPQL 查询
        List<User> findByName(String name, Pageable pageable);  // 根据名字查询用户
    ```

    ```java
        public User findByName(User user) {
            Pageable pageable = PageRequest.of(0, 10);  // 创建 Pageable 实例
            List<User> byName = userRepository.findByName(user.getName(), pageable);
            return byName.get(0);  // 调用 UserRepository 的 save 方法
        }
    ```

*

  #### 6.3.2 排序

  * 用法一样
  *

    ```java
        @Query("SELECT u FROM User u WHERE u.name = ?1")  // JPQL 查询
        List<User> findByName(String name, Pageable pageable, Sort sort);  // 根据名字查询用户
    ```

    ```java
        public User findByName(User user) {
            Pageable pageable = PageRequest.of(0, 10);  // 创建 Pageable 实例
            Sort sort = Sort.by(Sort.Order.asc("name"));  // 根据 name 字段升序排序
            List<User> byName = userRepository.findByName(user.getName(), pageable,sort);
            return byName.get(0);  // 调用 UserRepository 的 save 方法
        }
    ```

7. 事务管理

### 7.1 什么是事务?

* **事务(Transaction)** 是一组操作的集合,这些操作要么全部执行成功,要么在出现问题时全部回滚。事务是数据库管理系统(DBMS)中用于确保数据一致性、完整性、可靠性的一个关键概念。
* **事务的 ACID 特性**:

  * **原子性(Atomicity)**:事务中的操作要么全部成功,要么全部失败,不会出现部分成功的情况。
  * **一致性(Consistency)**:事务执行前后,数据库的状态必须是合法的。
  * **隔离性(Isolation)**:一个事务的执行不应该受到其他事务的干扰,事务的执行结果对其他事务是隔离的。
  * **持久性(Durability)**:一旦事务提交,操作的结果会永久保存到数据库中,即使系统崩溃也不会丢失。
### 7.2 Spring 的事务管理基础

* Spring 提供了两种主要的事务管理方式:

* **声明式事务管理** :通过 `@Transactional` 注解或 AOP 配置来管理事务。

* **编程式事务管理** :通过编程方式(即通过 `TransactionTemplate` 或 `PlatformTransactionManager`)显式控制事务的开始、提交和回滚。

* **声明式事务管理** 是 Spring 推荐的事务管理方式,通常使用 `@Transactional` 注解。
### 7.3 使用 `@Transactional` 注解

* Spring 的 `@Transactional` 注解可以用来声明式地管理事务。通过将其应用于方法或类上,Spring 会自动在方法执行时开启事务,方法执行结束时提交事务,若方法抛出异常则回滚事务。

  基本使用
  * **应用于类**:应用于类级别时,类中的所有方法都会被事务管理。

  * **应用于方法**:应用于方法时,只有该方法会被事务管理。

* `@Transactional` 常用属性:

  * **`propagation`**:定义事务的传播行为(如如何处理事务的嵌套)。

  * **`isolation`**:定义事务的隔离级别(如多个事务之间如何隔离)。

  * **`rollbackFor`**:指定哪些异常会导致事务回滚。

  * **`noRollbackFor`**:指定哪些异常不会导致事务回滚。

  * **`timeout`**:指定事务的超时时间(单位:秒)。

  * **`readOnly`**:设置事务是否为只读事务,优化数据库性能(对于只读取数据的操作)。

  * **`value`**:指定事务管理器的名字,默认情况下 Spring 会使用默认的事务管理器。
### 7.4 事务的传播行为和隔离级别

* 事务传播行为(Propagation)

  * 事务传播行为定义了一个事务方法如何与另一个事务方法交互。Spring 支持以下几种传播行为:

    * **`REQUIRED`**(默认值):如果当前没有事务,则创建一个新的事务。如果当前已经存在事务,当前方法将加入到这个事务中。

    * **`REQUIRES_NEW`**:无论当前是否存在事务,都会创建一个新的事务。如果当前有事务,则会暂停当前事务。

    * **`SUPPORTS`**:如果当前有事务,则加入事务。如果没有事务,则以非事务的方式执行。

    * **`NOT_SUPPORTED`**:如果当前有事务,则将事务挂起,并以非事务方式执行。

    * **`MANDATORY`**:当前必须有事务。如果当前没有事务,则抛出异常。

    * **`NEVER`**:当前不能有事务。如果当前有事务,则抛出异常。

    * **`NESTED`**:如果当前没有事务,则创建一个新的事务。如果当前有事务,则创建一个嵌套事务。嵌套事务的提交与回滚会独立于外部事务。

* 事务隔离级别(Isolation)

  * 事务隔离级别定义了不同事务之间如何隔离。Spring 支持以下几种隔离级别:

    * **`READ_UNCOMMITTED`**:允许读取未提交的数据,可能导致脏读、不可重复读、幻读问题。

    * **`READ_COMMITTED`**:只能读取已经提交的数据,避免脏读,但可能会有不可重复读和幻读。

    * **`REPEATABLE_READ`**:读取的是一致的数据,避免脏读和不可重复读,但可能会有幻读问题。

    * **`SERIALIZABLE`**:强制事务串行化,避免脏读、不可重复读和幻读,但性能开销较大。
### 7.5 事务回滚机制

* Spring 的事务回滚机制允许我们控制事务在出现异常时是否回滚。默认情况下,Spring 仅在遇到 **运行时异常(`RuntimeException`)** 或 **错误(`Error`)** 时回滚事务。

* 默认回滚规则:

  * **回滚事务** :遇到 `RuntimeException` 或 `Error`。

  * **不回滚事务** :遇到 `checked exceptions`(检查型异常)。

8. 配置与优化

### 8.1 配置数据源(`DataSource`)

* 如果你使用的是关系型数据库(如 MySQL、PostgreSQL、H2 等),Spring Boot 会自动配置数据源。

  例如,对于 **MySQL** ,Spring Boot 会自动使用 `HikariCP`(Spring Boot 默认的数据源连接池)来管理连接池。
### 8.2 使用 `application.properties` 配置数据库连接池

* Spring Boot 默认使用 `HikariCP` 作为连接池,它是一个高性能的 JDBC 连接池,可以有效地管理数据库连接。在 `application.properties` 文件中,可以配置数据库连接池的相关属性。

* 配置连接池的基本参数

  *

    ```java
    # 数据源配置
    spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    spring.datasource.username=root
    spring.datasource.password=root
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

    # 连接池配置
    spring.datasource.hikari.maximum-pool-size=20  # 最大连接池大小
    spring.datasource.hikari.minimum-idle=5       # 最小空闲连接数
    spring.datasource.hikari.idle-timeout=600000 # 空闲连接超时时间(毫秒)
    spring.datasource.hikari.max-lifetime=1800000 # 连接最大生命周期(毫秒)
    spring.datasource.hikari.connection-timeout=30000 # 连接超时时间(毫秒)
    ```

* 配置其他连接池特性

  *

    ```java
    spring.datasource.hikari.auto-commit=true   # 自动提交事务
    spring.datasource.hikari.connection-test-query=SELECT 1 # 检测连接是否有效的查询语句
    spring.datasource.hikari.pool-name=HikariCP # 连接池名称
    spring.datasource.hikari.leak-detection-threshold=15000 # 设置连接泄露检测时间(毫秒)
    ```

* 配置连接池性能优化

  * **`spring.datasource.hikari.maximum-pool-size`**:控制最大连接数。根据业务量进行调整,过大或过小都会影响性能。

  * **`spring.datasource.hikari.minimum-idle`**:空闲连接数,设置为空闲连接池的最小值。可以帮助快速响应查询请求。

  * **`spring.datasource.hikari.idle-timeout`**:空闲连接超时时间,当连接池中的连接空闲时间超过此值时,将被销毁。

  * **`spring.datasource.hikari.max-lifetime`**:连接最大生命周期,当连接池中的连接达到此最大生命周期时,它们将被销毁并重新创建。
### 8.3 配置 JPA 的 Hibernate 属性(如:DDL 自动生成策略、SQL 日志输出等)

*

  #### 8.3.1 配置 Hibernate 的 DDL 自动生成策略

  * **`spring.jpa.hibernate.ddl-auto`**:控制 Hibernate 如何自动管理数据库模式。

  * 常见的值包括:

    * `none`:不进行任何操作,数据库架构由外部管理。

    * `update`:Hibernate 会根据实体类的变更自动更新数据库架构。

    * `create`:每次应用启动时都会创建数据库架构。

    * `create-drop`:在应用启动时创建架构,并在应用关闭时删除架构。

      *

        ```java
        spring.jpa.hibernate.ddl-auto=update
        ```

*

  #### 8.3.2 配置 Hibernate 的 SQL 日志输出

  * **`spring.jpa.show-sql`**:控制是否输出 SQL 语句。

  * **`spring.jpa.properties.hibernate.format_sql`**:格式化 SQL,使其更易读。

  * **`spring.jpa.properties.hibernate.use_sql_comments`**:是否输出 SQL 注释,帮助理解 SQL 执行过程。

    *

      ```java
      spring.jpa.show-sql=true
      spring.jpa.properties.hibernate.format_sql=true
      spring.jpa.properties.hibernate.use_sql_comments=true
      ```
相关推荐
枫叶_v1 分钟前
【SpringBoot】19 文件/图片下载(MySQL + Thymeleaf)
spring boot·后端·mysql
激流丶7 分钟前
【缓存策略】你知道 Write Through(直写)这个缓存策略吗?
java·分布式·后端·缓存·中间件
白如意i22 分钟前
如何在 CentOS 6 上设置 NFS 挂载
数据库·mysql·adb
CodeFans1 小时前
Java常用工具使用方法
后端
子竹聆风1 小时前
支持查询未执行任务位置的消息队列:如何选择最适合的解决方案?
后端
谦谦均1 小时前
PostgreSQL中的COPY命令:高效数据导入与导出
数据库·postgresql·oracle
不会编程的猫星人1 小时前
向量数据库PGVECTOR安装
数据库·postgresql·向量数据库
蓝田~1 小时前
Springboot -- 自定义异常,异常处理
java·spring boot·spring
A阳俊yi2 小时前
SQL练习(2)
数据库·sql·oracle
SAP学习成长之路2 小时前
【翻译】伯明翰市议会因 Oracle 系统崩溃而面临 £216.5M 的损失
数据库·oracle·sap