SpringData 
配置相关均在**实例**环节给出,后续仅做*部分*重申。
1 Spring Data 简介 
Spring Data 是 Spring Framework 的一个子项目,旨在为数据访问层提供统一的编程模型和 API,从而简化对各种数据存储的访问。Spring Data 不仅支持关系型数据库(如 MySQL、PostgreSQL),还支持 NoSQL 数据库(如 MongoDB、Redis),甚至大数据存储(如 Hadoop)。
1.1 Spring Data 的核心目标 
- 统一的编程模型: Spring Data 通过提供一致的接口和抽象,使得开发者可以使用相同的方式来访问不同类型的数据存储,减少了学习成本和开发工作量。
- 简化数据访问: Spring Data 提供了丰富的功能和工具,如自动生成查询、分页、排序、事务管理等,大大简化了数据访问层的开发。
- 支持多种数据存储: Spring Data 支持多种类型的数据存储,包括关系型数据库、NoSQL 数据库、大数据存储等,为开发者提供了灵活的选择。
1.2 Spring Data 的核心组件 
- 统一接口 (Repositories):
- Repository<T, ID extends Serializable>:这是 Spring Data 中最基础的接口,提供了基本的 CRUD (增删改查) 操作。
- CrudRepository<T, ID extends Serializable>:继承自- Repository接口,提供了更丰富的 CRUD 操作,如- saveAll、- findAllById、- deleteAll等。
- PagingAndSortingRepository<T, ID extends Serializable>:继承自- CrudRepository接口,增加了分页和排序功能,如- findAll(Pageable pageable)。
- 模板类 (Templates):
Spring Data 为不同的 NoSQL 数据库提供了相应的模板类,这些模板类封装了底层的数据访问操作,使得开发者可以使用更简单的方式来操作 NoSQL 数据库。
- RedisTemplate:用于操作 Redis 数据库。
- MongoTemplate:用于操作 MongoDB 数据库。
- JPA (Java Persistence API):
JPA 是 Java EE 平台的标准 ORM 规范,Spring Data JPA 是 Spring Data 对 JPA 的实现。通过 Spring Data JPA,开发者可以使用面向对象的方式来操作关系型数据库,而无需编写复杂的 SQL 语句。
1.3 Spring Data 的优势 
- 提高开发效率: 通过提供统一的编程模型和丰富的功能,Spring Data 可以显著提高数据访问层的开发效率。
- 降低学习成本: 开发者只需掌握 Spring Data 的核心概念和 API,就可以轻松地访问各种数据存储。
- 增强代码可读性: Spring Data 的代码简洁易懂,易于维护和扩展。
- 灵活性和可扩展性: Spring Data 支持多种数据存储,并提供了丰富的自定义选项,可以满足各种复杂的业务需求。
2 SpringData 实例 
2.1 pom.xml 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>SpringBoot_DEMO</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
    </parent>
    <dependencies>
        <!--SpringBoot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <dependency>
            <!--swagger2-->
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>2.2 配置文件 
resources 中创建 application.properties
# 服务器将在此端口上运行
server.port=4444
# 数据库的URL
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false
# 数据库的用户名
spring.datasource.username=root
# 数据库的密码
spring.datasource.password=114514
# 指定要使用的Hibernate方言。此属性告诉Hibernate如何生成与MySQL数据库兼容的SQL查询。
# 方言类'org.hibernate.dialect.MySQL8Dialect'用于MySQL 8及更高版本。
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
# 指定用于连接数据库的JDBC驱动程序。
# 'com.mysql.cj.jdbc.Driver'驱动程序类用于MySQL数据库。
# JDBC(Java Database Connectivity)是一个Java API,使Java程序能够执行SQL语句。
# 这使Java程序可以与任何符合SQL的数据库进行交互。
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 在控制台显示SQL查询
spring.jpa.show-sql=true
# 'spring.jpa.hibernate.ddl-auto'属性控制Hibernate的模式生成行为。
# 它会自动更新数据库模式以匹配应用程序中的实体。
# 此属性的可能值为:
# - 'none':不执行任何操作。
# - 'validate':将验证模式。它检查表和列是否存在,如果不存在,它会抛出异常。
# - 'update':将更新模式。如果表或列不存在,Hibernate将创建它们。
# - 'create':将创建模式。如果表或列存在,Hibernate将删除它们并创建新的。
# - 'create-drop':与'create'类似,但是当SessionFactory明确关闭时,模式将被删除,对于单元测试很有用。
spring.jpa.hibernate.ddl-auto=update
# 启用隐藏方法过滤器以支持PUT,PATCH和DELETE请求
spring.mvc.hiddenmethod.filter.enabled=true
# 日期格式
spring.mvc.format.date=yyyy-MM-dd
# 此属性修改Spring Boot用于匹配映射的默认策略。
# 它设置为'ant_path_matcher'以使其与Swagger2兼容。
# 'ant_path_matcher'策略使用Ant风格的路径模式。
# 例如,"/example/**"匹配/example路径下的所有路径。
# 当你想使用Swagger2进行API文档化时,这很有用,因为它需要特定的路径匹配模式。
spring.mvc.pathmatch.matching-strategy=ant_path_matcher2.3 主启动程序 
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
public class SpringDataDEMO
{
public static void main( String[] args )
    {
            SpringApplication.run(SpringDataDEMO.class,args);
    }
}2.4 Entity 
Spring Data JPA 与 Hibernate
Spring Data JPA 是 Spring Data 项目的一部分,它利用 Hibernate 框架作为底层 ORM (Object-Relational Mapping) 实现。ORM 用于在面向对象的领域模型和关系型数据库之间建立映射关系,使得开发者可以使用面向对象的方式操作数据库,无需编写复杂的 SQL 语句。
Hibernate 注解是用于描述实体类与数据库表之间映射关系的元数据。通过在实体类中添加注解,Hibernate 可以自动生成 SQL 语句,实现对数据库的增删改查操作。
常用 Hibernate 注解:
- @Entity: 标识一个类为实体类,表示它将映射到数据库中的一个表。 - name(可选): 指定实体类对应的表名,默认为实体类的简单名称。
 
- @Table: 指定实体类映射的数据库表的信息。 - name(可选): 指定表名,默认为实体类的简单名称。
- catalog(可选): 指定数据库的 catalog。
- schema(可选): 指定数据库的 schema。
- uniqueConstraints(可选): 指定表的唯一约束。
- indexes(可选): 指定表的索引。
 
- @Id: 标识实体类的主键属性。 
- @GeneratedValue: 指定主键生成策略。 - strategy: 主键生成策略,常用的有:- GenerationType.IDENTITY: 数据库自增 (适用于 MySQL, SQL Server)。
- GenerationType.SEQUENCE: 数据库序列 (适用于 Oracle)。
- GenerationType.TABLE: 数据库表生成 (适用于不支持自增和序列的数据库)。
- GenerationType.AUTO: 由 Hibernate 根据底层数据库自动选择。
 
 
- @Column: 指定实体类属性映射的数据库列的信息。 - name(可选): 指定列名,默认为属性名称。
- unique(可选): 是否唯一。
- nullable(可选): 是否允许为空。
- insertable(可选): 是否允许插入。
- updatable(可选): 是否允许更新。
- columnDefinition(可选): 定义列的类型、长度等。
- length(可选): 字符串类型的长度。
- precision,- scale(可选): 数值类型的精度和小数位数。
 
package org.example.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.*;
import java.util.Date;
/**
 * Game类代表游戏实体,并将其映射到数据库中的"tab_game"表。
 *
 * @Entity 这个注解表明这个类是一个实体。这是一个JPA注解,用于指定该类是一个持久化的Java类。
 * @Table(name="tab_game") 这个注解用于指定该实体映射到数据库中的哪个表。如果省略@Table注解,表名将与类名相同。
 * @DynamicUpdate 这是一个特定于Hibernate的注解,启用后,只更新数据库中值已更改的列。这可能比更新所有列更有效率。
 * @ApiModel(description="游戏实体") 这个注解是一个Swagger注解,用于在生成的Swagger文档中为模型定义添加元数据。description属性提供了模型的简短描述。
 */
@Entity
@Table(name = "tab_game")
@DynamicUpdate
@ApiModel(description = "游戏实体")
public class Game {
    /**
     * gid字段代表游戏的ID。
     * @Id 注解用于表示这个字段是主键。
     * @GeneratedValue 注解用于指定主键生成策略,这里是自增。
     * @Column 注解用于指定该字段映射到数据库中的哪个列。
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "g_id")
    @ApiModelProperty("游戏ID")
    private Integer gid;
    /**
     * gname字段代表游戏的名称。
     * 它映射到数据库中的"g_name"列,其最大长度为200个字符。
     */
    @Column(name = "g_name", length = 200)
    @ApiModelProperty(value = "游戏名称", example = "暗黑破坏神2")
    private String gname;
    /**
     * gprice字段代表游戏的价格。
     * 它映射到数据库中的"g_price"列。
     */
    @Column(name = "g_price")
    @ApiModelProperty("游戏价格")
    private Double gprice;
    /**
     * gdate字段代表游戏的发布日期。
     * 它映射到数据库中的"g_date"列。
     * @Temporal注解用于指定日期的类型,这里是DATE。
     */
    @Column(name = "g_date")
    @Temporal(TemporalType.DATE)
    @ApiModelProperty(value = "游戏发售日期", example = "2024-01-10")
    private Date gdate;
    /**
     * gdesc字段代表游戏的描述。
     * 它映射到数据库中的"g_desc"列。
     */
    @Column(name = "g_desc")
    @ApiModelProperty("游戏备注")
    private String gdesc;
    /**
     * 默认构造函数。
     */
    public Game() {
    }
    /**
     * 包含所有字段的构造函数。
     */
    public Game(Integer gid, String gname, Double gprice, Date gdate, String gdesc) {
        this.gid = gid;
        this.gname = gname;
        this.gprice = gprice;
        this.gdate = gdate;
        this.gdesc = gdesc;
    }
    /**
     * 重写toString方法,提供Game对象的字符串表示形式。
     */
    @Override
    public String toString() {
        return "Game{" +
                "gid=" + gid +
                ", gname='" + gname + '\'' +
                ", gprice=" + gprice +
                ", gdate=" + gdate +
                ", gdesc='" + gdesc + '\'' +
                '}';
    }
    // Getters and setters…
    public Integer getGid() {
        return gid;
    }
    public void setGid(Integer gid) {
        this.gid = gid;
    }
    public String getGname() {
        return gname;
    }
    public void setGname(String gname) {
        this.gname = gname;
    }
    public Double getGprice() {
        return gprice;
    }
    public void setGprice(Double gprice) {
        this.gprice = gprice;
    }
    public Date getGdate() {
        return gdate;
    }
    public void setGdate(Date gdate) {
        this.gdate = gdate;
    }
    public String getGdesc() {
        return gdesc;
    }
    public void setGdesc(String gdesc) {
        this.gdesc = gdesc;
    }
}2.5 Repository 
package org.example.repository;
import org.example.entity.Game;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
/**
 * 这是GameRepository接口。
 * 它扩展了JpaRepository以利用Spring Data JPA。
 * Spring Data JPA提供了一种非常简单且一致的方式来定义数据访问层。
 *
 * @author wryyyy4444
 * @Repository 注解用于指示GameRepository接口是一个Bean。
 * 它将在Spring进行组件扫描时自动被检测到。
 * 这意味着这个接口的实现将由Spring自动创建。
 * 我们可以在任何想要使用它的地方使用@Autowired进行注入。
 */
@Repository
public interface GameRepository extends JpaRepository<Game, Integer>, JpaSpecificationExecutor<Game> {
// JpaRepository<Game,Integer> 是针对ID类型为Integer的Game实体。
// JpaSpecificationExecutor<Game> 基于JPA criteria API执行Specifications。
// Specification支持:
// - 动态查询构造
// - 所有查询条件
// - 子查询,连接查询,排序,分页
// - 不支持自定义模型。只能返回定义的实体。
// 对于大于或小于等查询,使用Specification。
}2.6 Service 
package org.example.service;
import org.example.entity.Game;
import org.springframework.data.domain.Page;
import java.util.List;
/**
 * 这是Game实体的服务接口。
 * 定义了为Game实体提供业务逻辑的方法。
 */
public interface GameService {
    /**
     * 从数据库中获取所有Game实体。
     *
     * @return 所有Game实体的列表。
     */
    List<Game> findAll();
    /**
     * 从数据库中获取所有Game实体,并按价格降序排序。
     *
     * @return 按价格排序的所有Game实体的列表。
     */
    List<Game> findAllByGPriceDesc();
    /**
     * 根据其ID获取Game实体。
     *
     * @param gid 要获取的Game实体的ID。
     * @return 具有给定ID的Game实体,如果不存在此类实体,则返回null。
     */
    Game findByGid(Integer gid);
    /**
     * 获取具有给定名称的所有Game实体。
     *
     * @param gname 要获取的Game实体的名称。
     * @return 具有给定名称的所有Game实体的列表。
     */
    List<Game> findByGname(String gname);
    /**
     * 获取名称包含给定子字符串的所有Game实体。
     *
     * @param gname 要在Game实体的名称中搜索的子字符串。
     * @return 名称包含给定子字符串的所有Game实体的列表。
     */
    List<Game> findLikeGname(String gname);
    /**
     * 获取价格高于给定值的所有Game实体。
     *
     * @param gprice 用于比较的价格值。
     * @return 价格高于给定值的所有Game实体的列表。
     */
    List<Game> findAboveGprice(Double gprice);
    /**
     * 从数据库中获取Game实体的页面。
     *
     * @param pageNo   要获取的页面的编号。
     * @param pageSize 要获取的页面的大小。
     * @return Game实体的页面。
     */
    Page<Game> findByPage(int pageNo, int pageSize);
    /**
     * 将新的Game实体插入数据库。
     *
     * @param game 要插入的Game实体。
     * @return 如果插入成功,则返回true,否则返回false。
     */
    boolean insert(Game game);
    /**
     * 在数据库中更新现有的Game实体。
     *
     * @param game 要更新的Game实体。
     * @return 如果更新成功,则返回true,否则返回false。
     */
    boolean update(Game game);
    /**
     * 从数据库中删除Game实体。
     *
     * @param gid 要删除的Game实体的ID。
     * @return 如果删除成功,则返回true,否则返回false。
     */
    boolean delete(Integer gid);
}2.6.1 ServiceImpl 
增加&更新&删除
- SpringData 的增加和更新均使用 save方法,根据主键是否为空判断。
- 删除时失败抛出异常,使用 try-catch处理。
package org.example.service.impl;
import org.example.entity.Game;
import org.example.repository.GameRepository;
import org.example.service.GameService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
 * 这是GameServiceImpl类。
 * 它实现了GameService接口并为应用程序提供业务逻辑。
 * 它使用GameRepository与数据库进行交互。
 *
 * @author wryyyy4444
 * @Service 注解用于指示GameServiceImpl类是一个Bean。
 * 它将在Spring进行组件扫描时自动被检测到。
 */
@Service
public class GameServiceImpl implements GameService {
    /**
     * 该服务将用于与数据库交互的GameRepository。
     */
    @Autowired
    private GameRepository gameRepository;
    /**
     * 返回数据库中的所有游戏。
     *
     * @return 所有游戏的列表。
     */
    @Override
    public List<Game> findAll() {
        return gameRepository.findAll();
    }
    /**
     * 返回按价格降序排序的所有游戏。
     *
     * @return 按价格降序排序的所有游戏的列表。
     */
    @Override
    public List<Game> findAllByGPriceDesc() {
        // 创建一个Sort对象来定义排序条件。
        Sort sort = Sort.by(Sort.Direction.DESC, "gprice");
        // 使用GameRepository获取按定义的条件排序的所有游戏。
        return gameRepository.findAll(sort);
    }
    /**
     * 该方法用于通过其ID在数据库中查找游戏。
     *
     * @param gid 要搜索的游戏的ID。
     * @return 如果找到,则返回Game对象,否则返回null。
     */
    @Override
    public Game findByGid(Integer gid) {
        // 使用GameRepository获取具有指定ID的游戏。
        // findById方法返回一个Optional<Game>对象。
        Optional<Game> game = gameRepository.findById(gid);
        // 如果存在,则返回Game对象,否则返回null。
        // orElse方法返回值(如果存在),否则返回指定的默认值。
        return game.orElse(null);
    }
    /**
     * 返回具有特定名称的所有游戏。
     *
     * @param gname 要搜索的游戏的名称。
     * @return 具有指定名称的游戏的列表。
     */
    @Override
    public List<Game> findByGname(String gname) {
        Game gameSearch = new Game();
        gameSearch.setGname(gname);
        Example<Game> example = Example.of(gameSearch);
        return gameRepository.findAll(example);
    }
    /**
     * 返回名称包含特定字符串的所有游戏。
     *
     * @param gname 要在游戏名称中搜索的字符串。
     * @return 名称包含指定字符串的所有游戏的列表。
     */
    @Override
    public List<Game> findLikeGname(String gname) {
        // ExampleMatcher的matching方法是个链式结构,它将在执行完后返回自己
        ExampleMatcher exampleMatcher = ExampleMatcher.matching().withMatcher("gname",
                ExampleMatcher.GenericPropertyMatcher::contains);
        Game gameSearch = new Game();
        gameSearch.setGname(gname);
        Example<Game> example = Example.of(gameSearch, exampleMatcher);
        return gameRepository.findAll(example);
    }
    /**
     * 该方法用于查找数据库中价格大于指定值的所有游戏。
     *
     * @param gprice 用于比较的价格值。所有价格大于此值的游戏将被返回。
     * @return 价格大于指定值的所有游戏的列表。
     */
    @Override
    public List<Game> findAboveGprice(Double gprice) {
        // 创建一个Specification对象来定义查询条件。
        Specification<Game> specification = new Specification<Game>() {
            /**
             * 该方法用于构造查询条件。
             *
             * @param root            表单中的根类型,始终引用查询定义的实体。
             * @param query           CriteriaQuery对象,用于添加限制,排序,聚合等。
             * @param criteriaBuilder 用于构造Predicate,表达式,排序和聚合函数。
             * @return 包含查询条件的Predicate对象。
             */
            @Override
            public Predicate toPredicate(Root<Game> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                // 创建一个列表来存储查询谓词。
                List<Predicate> predicates = new ArrayList<>();
                // 向列表中添加一个谓词:游戏价格应大于或等于指定的值。
                predicates.add(criteriaBuilder.ge(root.get("gprice"), gprice));
                // 将谓词列表转换为数组。
                Predicate[] pre = new Predicate[predicates.size()];
                predicates.toArray(pre);
                // 将谓词添加到查询中并返回更新后的查询。
                return query.where(pre).getRestriction();
            }
        };
        // 执行查询并返回结果。
        return gameRepository.findAll(specification);
    }
    /**
     * 从数据库中返回游戏的页面。
     *
     * @param pageNo   要获取的页面的编号。页面编号从1开始。
     * @param pageSize 单个页面中要包含的游戏数量。
     * @return 包含指定页面中的游戏的Page对象。
     */
    @Override
    public Page<Game> findByPage(int pageNo, int pageSize) {
        // 计算页面的起始索引。
        int start = (pageNo - 1) * pageSize;
        // 创建一个Pageable对象来定义分页条件。
        Pageable pageable = PageRequest.of(start, pageSize);
        // 使用GameRepository获取指定的游戏页面。
        return gameRepository.findAll(pageable);
    }
    /**
     * 将新游戏插入数据库。
     *
     * @param game 要插入的游戏。
     * @return 如果操作成功,则返回true,否则返回false。
     */
    @Override
    public boolean insert(Game game) {
        try {
            gameRepository.save(game);
            return true;
        } catch (Exception e) {
            System.out.println("保存游戏时出现错误:" + e.getMessage());
            return false;
        }
    }
    /**
     * 在数据库中更新现有的游戏。
     *
     * @param game 要更新的游戏。
     * @return 如果操作成功,则返回true,否则返回false。
     */
    @Override
    public boolean update(Game game) {
        try {
            gameRepository.save(game);
            return true;
        } catch (Exception e) {
            System.out.println("更新游戏时出现错误:" + e.getMessage());
            return false;
        }
    }
    /**
     * 从数据库中删除游戏。
     *
     * @param gid 要删除的游戏的id。
     * @return 如果操作成功,则返回true,否则返回false。
     */
    @Override
    public boolean delete(Integer gid) {
        try {
            gameRepository.deleteById(gid);
            return true;
        } catch (EmptyResultDataAccessException e) {
            System.out.println("删除游戏时出现错误:游戏不存在,ID为 " + gid);
            return false;
        } catch (Exception e) {
            System.out.println("删除游戏时出现错误:" + e.getMessage());
            return false;
        }
    }
}2.7 Controller 
`@RequestBody` & `@PathVariable`
- 使用 @RequestBody注解参数 => 前端传输 JSON
- 使用 @PathVariable标注Mapping中参数
package org.example.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.example.entity.Game;
import org.example.service.GameService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * GameController是一个处理与游戏相关的HTTP请求的REST控制器。
 * 它使用GameService与底层数据存储进行交互。
 *
 * @RestController 注解将此类直接改为RestFul风格。也就是说,接口以json模式显示。相当于:
 * @Controller + @ResponseBody 这两个注解的结合。
 * 在SpringBoot中,使用Jackson框架直接将对象序列化并转换为Json数据。
 */
@RestController
@Api(tags = "游戏接口")
public class GameController {
    @Autowired
    private GameService gameService;
    @GetMapping("/games")
    @ApiOperation(value = "获取所有游戏")
    public List<Game> findAll() {
        return gameService.findAll();
    }
    @GetMapping("/game/{gid}")
    @ApiOperation(value = "根据gid获取单条游戏数据")
    public Game findByGid(@PathVariable("gid") Integer gid) {
        return gameService.findByGid(gid);
    }
    @PostMapping("/game")
    @ApiOperation(value = "新增游戏")
    public Boolean insert(@RequestBody Game game) {
        return gameService.insert(game);
    }
    @PutMapping("/game")
    @ApiOperation(value = "更新游戏")
    @ApiParam(value = "需要传入需要更新的游戏实体,JSON格式")
    public Boolean update(@RequestBody Game game) {
        return gameService.update(game);
    }
    @DeleteMapping("/game/{gid}")
    @ApiOperation(value = "根据gid删除游戏")
    public Boolean delete(@PathVariable("gid") Integer gid) {
        return gameService.delete(gid);
    }
}2.8 SpringBootTest 
PackageName better be same to main

package org.example.service;
import org.example.entity.Game;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import java.util.Date;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
// @SpringBoot 标注为SpringBoot测试类
@SpringBootTest
class GameServiceTest {
    @Autowired
    private GameService gameService;
	// @Test 标注为测试用例
    @Test
    public void testFindAll() {
        List<Game> gameList = gameService.findAll();
        gameList.forEach(System.out::println);
    }
    @Test
    public void testFindByGname() {
        List<Game> gameList = gameService.findByGname("test game");
        assertNotNull(gameList, "The returned game list is null");
        gameList.forEach(System.out::println);
    }
    @Test
    public void testFindLikeGname() {
        List<Game> gameList = gameService.findLikeGname("test");
        gameList.forEach(System.out::println);
        assertFalse(gameList.isEmpty(), "模糊查询失败!返回数据:" + gameList.size() + "条");
    }
    @Test
    public void testFindAboveGprice() {
        List<Game> gameList = gameService.findAboveGprice(300d);
        gameList.forEach(System.out::println);
        assertFalse(gameList.isEmpty(), "模糊查询失败!返回数据:" + gameList.size() + " 条");
    }
    @Test
    public void testFindAllByGPriceDesc() {
        List<Game> gameList = gameService.findAllByGPriceDesc();
        gameList.forEach(System.out::println);
        assertFalse(gameList.isEmpty(), "模糊查询失败!返回数据:" + gameList.size() + " 条");
    }
    @Test
    public void testFindByPage() {
        Page<Game> gamePage = gameService.findByPage(1, 1);
        System.out.println(gamePage.getTotalElements());
        System.out.println(gamePage.getTotalPages());
        System.out.println(gamePage.getNumber());
        List<Game> gameList = gamePage.getContent();
        gameList.forEach(System.out::println);
        assertFalse(gameList.isEmpty(), "模糊查询失败!返回数据:" + gameList.size() + " 条");
    }
    @Test
    public void testInsert() {
        Game game = new Game(null, "暗黑四", 399d, new Date(), "角色扮演动作类游戏!");
        game.setGname("test game");
        boolean result = gameService.insert(game);
        assertTrue(result, "Insert operation failed");
    }
    @Test
    public void testUpdate() {
        Game game = new Game();
        // 修改game的某些属性
        game.setGid(2);
        game.setGname("test game2");
        boolean result = gameService.update(game);
        assertTrue(result, "Update operation failed");
    }
    @Test
    public void testDelete() {
        Game game = new Game();
        // 设置game的属性
        game.setGid(2);
        boolean result = gameService.delete(game.getGid());
        assertTrue(result, "Delete operation failed");
    }
}3 动态查询 
有时,我们需要执行动态查询,例如根据名字进行模糊查询。Spring Data 提供了 `Example` 类来构建动态 SQL 查询。
3.1 Example 查询(QBE - Query By Example) 
Example 查询是一种用户友好的查询技术,允许动态创建查询,无需编写包含字段名称的查询,也不需要使用特定数据库的查询语言。
优势:
- 使用动态或静态限制进行查询
- 重构实体时,无需担心影响现有查询
- 独立于数据查询 API
劣势:
- 不支持组合查询(如 firstname = ? or (firstname = 1 and lastname = 2))
- 仅支持字符串的 starts/contains/ends/regex 匹配,对非字符串属性仅支持精确匹配
3.1.1 精确查询 
/**
 * 返回具有特定名称的所有游戏。
 *
 * @param gname 要搜索的游戏的名称。
 * @return 具有指定名称的游戏的列表。
 */
@Override
public List<Game> findByGname(String gname) {
	Game gameSearch = new Game();
	gameSearch.setGname(gname);
	Example<Game> example = Example.of(gameSearch);
	return gameRepository.findAll(example);
}3.1.2 条件查询 
使用 ExampleMatcher 类进行各种字符串匹配条件查询。
/**
 * 返回名称包含特定字符串的所有游戏。
 *
 * @param gname 要在游戏名称中搜索的字符串。
 * @return 名称包含指定字符串的所有游戏的列表。
 */
@Override
public List<Game> findLikeGname(String gname) {
	// ExampleMatcher的matching方法是个链式结构,它将在执行完后返回自己
	ExampleMatcher exampleMatcher = ExampleMatcher.matching().withMatcher("gname",
			ExampleMatcher.GenericPropertyMatcher::contains);
	Game gameSearch = new Game();
	gameSearch.setGname(gname);
	Example<Game> example = Example.of(gameSearch, exampleMatcher);
	return gameRepository.findAll(example);
}3.2 Specification (复杂查询) 
如果需要大于、小于等查询条件,可以使用 `Specification`。
- 动态构建查询语句
- 支持所有查询条件、子查询、连接查询、排序、分页
- 不支持自定义模型,无法做到将查询的结果封装为自定义的 model,或类似 List<String>的单列结果。只能返回 DAO 定义的 entity
3.2.1 继承接口 
Repo 类继承 JpaSpecificationExecutor
public interface GameRepository extends JpaRepository<Game, Integer>, JpaSpecificationExecutor<Game>3.2.2 编写查询条件 
/**
 * 该方法用于查找数据库中价格大于指定值的所有游戏。
 *
 * @param gprice 用于比较的价格值。所有价格大于此值的游戏将被返回。
 * @return 价格大于指定值的所有游戏的列表。
 */
@Override
public List<Game> findAboveGprice(Double gprice) {
	// 创建一个Specification对象来定义查询条件。
	Specification<Game> specification = new Specification<Game>() {
		/**
		 * 该方法用于构造查询条件。
		 *
		 * @param root            表单中的根类型,始终引用查询定义的实体。
		 * @param query           CriteriaQuery对象,用于添加限制,排序,聚合等。
		 * @param criteriaBuilder 用于构造Predicate,表达式,排序和聚合函数。
		 * @return 包含查询条件的Predicate对象。
		 */
		@Override
		public Predicate toPredicate(Root<Game> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
			// 创建一个列表来存储查询谓词。
			List<Predicate> predicates = new ArrayList<>();
			// 向列表中添加一个谓词:游戏价格应大于或等于指定的值。
			predicates.add(criteriaBuilder.ge(root.get("gprice"), gprice));
			// 将谓词列表转换为数组。
			Predicate[] pre = new Predicate[predicates.size()];
			predicates.toArray(pre);
			// 将谓词添加到查询中并返回更新后的查询。
			return query.where(pre).getRestriction();
		}
	};4 排序和分页 
4.1 使用 Sort 对象排序 
/**
 * 返回按价格降序排序的所有游戏。
 *
 * @return 按价格降序排序的所有游戏的列表。
 */
@Override
public List<Game> findAllByGPriceDesc() {
	// 创建一个Sort对象来定义排序条件。
	Sort sort = Sort.by(Sort.Direction.DESC, "gprice");
	// 使用GameRepository获取按定义的条件排序的所有游戏。
	return gameRepository.findAll(sort);
}4.2 使用 Pageable 对象分页 
/**
 * 从数据库中返回游戏的页面。
 *
 * @param pageNo   要获取的页面的编号。页面编号从1开始。
 * @param pageSize 单个页面中要包含的游戏数量。
 * @return 包含指定页面中的游戏的Page对象。
 */
@Override
public Page<Game> findByPage(int pageNo, int pageSize) {
	// 计算页面的起始索引。
	int start = (pageNo - 1) * pageSize;
	// 创建一个Pageable对象来定义分页条件。
	Pageable pageable = PageRequest.of(start, pageSize);
	// 使用GameRepository获取指定的游戏页面。
	return gameRepository.findAll(pageable);
}5 RESTful 
RESTful (Representational State Transfer) 是一种软件架构风格,它将网络操作视为对资源的调度。每个资源由唯一的 URI (Uniform Resource Identifier) 标识,客户端通过标准的 HTTP 方法 (GET, POST, PUT, DELETE) 对这些资源进行操作。
RESTful 的核心原则:
- 资源为中心: 系统中的所有实体都被抽象为资源,每个资源都有唯一的标识符。
- 统一接口: 使用标准的 HTTP 方法对资源进行操作,包括获取 (GET)、创建 (POST)、更新 (PUT) 和删除 (DELETE)。
- 无状态: 服务器不保存客户端的状态信息,每个请求都是独立的,包含了处理该请求所需的全部信息。
- 可缓存: 服务器的响应可以被客户端缓存,从而提高性能和可扩展性。
- 分层系统: 系统可以分为多个层次,每个层次只负责特定的功能,提高了系统的可维护性和可扩展性。
RESTful 与传统架构风格的对比:
| 操作 | 传统架构风格 | RESTful 架构风格 | 
|---|---|---|
| 读取所有数据 | /games/all.do(GET) | /games(GET) | 
| 读取单条数据 | /games/get.do?gid=1(GET) | /games/{gid}(GET) | 
| 新增单条数据 | /games/save.do(POST) | /games(POST) | 
| 更新单条数据 | /games/update.do(POST) | /games/{gid}(PUT) | 
| 删除单条数据 | /games/delete.do?gid=1(GET/POST) | /games/{gid}(DELETE) | 
RESTful 的优势:
- 简单易用: 使用标准的 HTTP 方法和 URI,使得 API 易于理解和使用。
- 可扩展性强: 无状态设计使得系统更容易扩展,可以轻松地添加新的服务器来处理更多的请求。
- 松耦合: 客户端和服务器之间的耦合度低,使得系统更易于维护和升级。
- 跨平台: RESTful API 可以被多种类型的客户端访问,包括 Web 浏览器、移动应用等。
RESTful 架构风格已经成为 Web 服务设计的主流,被广泛应用于各种类型的应用程序中。
5.1 application.properties 
# 启用隐藏方法过滤器以支持PUT,PATCH和DELETE请求
spring.mvc.hiddenmethod.filter.enabled=true
# 日期格式
spring.mvc.format.date=yyyy-MM-dd6 Swagger 2 
6.1 Dependency 
Include in `pom. xml` at front
<!--swagger2-->
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger2</artifactId>
	<version>2.9.2</version>
</dependency>
<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger-ui</artifactId>
	<version>2.9.2</version>
</dependency>6.1.1 Config for Swagger2 
package org.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("org.example.controller"))
                .paths(PathSelectors.any())
                .build()
                .apiInfo(apiInfo())
                .enable(true);
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("游戏管理系统接口文档")
                .description("接口文档")
                .version("1.0.0")
                .build();
    }
}6.1.2 application.properties 
# 此属性修改Spring Boot用于匹配映射的默认策略。
# 它设置为'ant_path_matcher'以使其与Swagger2兼容。
# 'ant_path_matcher'策略使用Ant风格的路径模式。
# 例如,"/example/**"匹配/example路径下的所有路径。
# 当你想使用Swagger2进行API文档化时,这很有用,因为它需要特定的路径匹配模式。
spring.mvc.pathmatch.matching-strategy=ant_path_matcher6.1.3 注解 
Controller 层注解:
- @Api:用于类上,描述 API 接口的整体信息,如标签、描述等。
- @ApiOperation:用于方法上,描述接口方法的用途、参数、返回值等。
- @ApiImplicitParams:用于方法上,描述隐式参数(如请求头、Cookie 等)。
- @ApiResponses:用于方法上,描述接口方法可能返回的 HTTP 状态码及响应信息。
- @ApiParam:用于方法的参数上,描述参数的含义、类型、是否必填等信息。
Model 层注解:
- @ApiModel:用于类上,描述实体类的信息。
- @ApiModelProperty:用于属性上,描述实体类属性的信息,如类型、描述、是否必填等。
其他注解:
- @ApiIgnore:用于类、方法或属性上,表示忽略该元素,不在 API 文档中显示。
- @ApiModelPropertyOptional:用于属性上,表示该属性为可选参数。
示例:
@Api(tags = "用户管理")
@RestController
public class UserController {
    @ApiOperation("获取用户信息")
    @GetMapping("/users/{id}")
    public User getUser(@ApiParam("用户ID") @PathVariable Long id) {
        // …
    }
}
@ApiModel("用户实体")
public class User {
    @ApiModelProperty("用户ID")
    private Long id;
    @ApiModelProperty("用户名")
    private String name;
    // …
}