SpringBoot入门到精通(四):整合JDBC和JPA(2021最新最易懂)

整合JDBC,JdbcTemplate,JPA(2021最新最易懂)

  当前环境说明:

  Windows10_64

  Maven3.x

  JDK1.8

  MySQL5.6

  SpringToolSuite4(Spring官方提供的开发工具,实际就是一个Eclipse)

一.整合JDBC

  对于数据访问层,无论是SQL(关系型数据库)还是NOSQL(非关系型数据库),Spring Boot底层都是采用Spring Data的方式进行统一处理。 Spring Data也是Spring中与Spring Boot,Spring Cloud等齐名的知名项目。并且Spring Data中内置连接池数据源HikariDataSource,HikariDataSource号称Java WEB当前速度最快的数据源,相比于传统的C3P0、DBCP、Druid、TomcatJDBC等连接池更加优秀;Spring Data允许开发者自主选择数据源。

  访问SpringBoot官网,可以查看Spring Data的支持,获取Spring Data启动器依赖。

1.SpringBoot实现JDBC整合

  实现JDBC整合,使用SpringData默认连接池数据源

  1. 新建SpringBoot项目,选择依赖
     1 <!-- SpringBoot整合JDBC -->
     2 <dependency>
     3     <groupId>org.springframework.boot</groupId>
     4     <artifactId>spring-boot-starter-jdbc</artifactId>
     5 </dependency>
     6 <!-- MySQL驱动:默认8.x,如果你使用的MySQL版本较低,且手动切换版本 -->
     7 <dependency>
     8     <groupId>mysql</groupId>
     9     <artifactId>mysql-connector-java</artifactId>
    10     <scope>runtime</scope>
    11 </dependency>
  2. 修改yml配置文件,添加数据库链接配置
     1 spring:
     2   datasource:
     3     #根据MySQL版本配置驱动类5.x----8.x 驱动类“com.mysql.jdbc.Driver” 或 “com.mysql.cj.jdbc.Driver”。
     4     driver-class-name: com.mysql.cj.jdbc.Driver
     5     #useSSL:SSL协议提供服务主要作用:(不建议在没有服务器身份验证的情况下建立SSL连接。)     
     6     #   1)认证用户服务器,确保数据发送到正确的服务器;    .
     7     #   2)加密数据,防止数据传输途中被窃取使用;
     8     #   3)维护数据完整性,验证数据在传输过程中是否丢失;
     9     #serverTimezone:设置时区,不设置会报错。GMT%2B8:东八区北京时间  Asia/Shanghai:上海时间
    10     #useServerPrepStmts:在url中给出useServerPrepStmts=true参数,开启预编译(默认PS是关闭的)
    11     #allowMultiQueries:设置为true,开启批量执行sql的开关。更多请持续关注博主文档
    12     url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
    13     username: root
    14     password: xsge
  3. 新建测试类,获取JDBC链接对象和池对象
    获取JDBC链接对象后,就可以实现CURD了。
     1 @SpringBootTest
     2 class SpringbootJdbcApplicationTests {
     3     @Autowired
     4     DataSource dataSource;// 自动注入链接池数据源对象
     5     @Test
     6     void contextLoads() throws Exception {
     7         Connection connection = dataSource.getConnection();
     8         System.out.println("数据源:"+dataSource);
     9         System.out.println("链接对象:"+connection);
    10     }
    11 
    12 }
  4. 运行查看控制台结果
    1 数据源:HikariDataSource (HikariPool-1)
    2 链接对象:HikariProxyConnection@1886247880 wrapping com.mysql.cj.jdbc.ConnectionImpl@fd09e43

就这???都框架整合了,难道以后使用原始JDBC写CURD ?这里主要是介绍Spring Data内部的默认集成数据源,且Spring Data也提供了很多CURD工具支持,例如:JPA,JdbcTemplate等。

二.整合JdbcTemplate

1.概述

  在实际项目中,在对数据库访问层对数据库进行操作时,大部分时候我们都用的MyBatis/Hibernate,但是偶尔会有用到使用JDBC的时候,一般使用JDBC的话要么就自己封装一个JDBC连接池进行使用,要么就是使用Apache Common 提供的 DbUtils 工具类,还有个就是Spring JDBC ,提供的JdbcTemplate 工具类。

  Spring属于一栈式框架,针对JAVAEE三层中的每一层,都提供了解决技术。在DAO层中Spring中使用JdbcTemplate完成技术需求。(详情请关注后续发表Spring集)

 1 JdbcTemplate中常用类介绍:
 2 DriverManagerDataSource类:DataSource连接池的实现子类
 3 作用:主要是设置连接数据库的相关信息。如:连接地址、用户名、密码等
 4 常用方法:
 5 setDriverClassName(……):设置驱动类
 6 setUsername(……):设置用户名
 7 setPassword(……):设置密码
 8 setUrl(……):设置连接地址
 9 JdbcTemplate类:
10 作用:由Spring提供的JDBC操作模板类。用于完成CURD操作
11 常用构造方法:
12 JdbcTemplate(DataSource dataSource):创建对象并指定连接管理源
13 常用其他方法:
14 update(……):执行增、删、改。返回值为int类型,表示结果影响行数。
15 queryXXX(……);执行查询。更多请关注博主Spring全集案例

2.准备工作

  1. 创建数据库。
  2. 新增对应数据库实体类。
    1 @Data
    2 @AllArgsConstructor
    3 @NoArgsConstructor
    4 public class Person {
    5     private Integer id;
    6     private String name;
    7     private String password;
    8 }
  3. 修改pom.xml引入依赖
    SpringBoot引入MySQL驱动默认版本为8.x,可以手动配置版本。
     1 <!-- SpringBoot整合JdbcTemplate -->
     2 <dependency>
     3     <groupId>org.springframework.boot</groupId>
     4     <artifactId>spring-boot-starter-jdbc</artifactId>
     5 </dependency>
     6         
     7 <!-- MySQL驱动(SpringBoot默认引入版本为8.x)手动配置版本 -->
     8 <dependency>
     9     <groupId>mysql</groupId>
    10     <artifactId>mysql-connector-java</artifactId>
    11     <scope>runtime</scope>
    12 </dependency>
  4. 修改application.yml文件添加数据源配置
     1 spring:
     2   datasource:
     3     #根据MySQL版本配置驱动类5.x----8.x 驱动类“com.mysql.jdbc.Driver” 或 “com.mysql.cj.jdbc.Driver”。
     4     driver-class-name: com.mysql.cj.jdbc.Driver
     5     #useSSL:SSL协议提供服务主要作用:(不建议在没有服务器身份验证的情况下建立SSL连接。)     
     6     #   1)认证用户服务器,确保数据发送到正确的服务器;    .
     7     #   2)加密数据,防止数据传输途中被窃取使用;
     8     #   3)维护数据完整性,验证数据在传输过程中是否丢失;
     9     #serverTimezone:设置时区,不设置会报错。GMT%2B8:东八区北京时间  Asia/Shanghai:上海时间
    10     #useServerPrepStmts:在url中给出useServerPrepStmts=true参数,开启预编译(默认PS是关闭的)
    11     #allowMultiQueries:设置为true,开启批量执行sql的开关。更多请持续关注博主文档
    12     url: jdbc:mysql://localhost:3306/springboot?useSSL=false&serverTimezone=GMT%2B8
    13     username: root
    14     password: xsge

3.整合实现

  1. 编写DAO层基础的CURD。
    使用注解@Repository标注为Spring--DAO层组件。注入JdbcTemplate对象
    BeanPropertyRowMapper是JdbcTemplate提供的一个结果集处理器类,是RowMapper结果集处理器接口的实现。
    注意:两个查询方法不一样
     1 @Repository    // Spring注解标注DAO层组件
     2 public class PersonDao{
     3     
     4     @Autowired // 注入jdbcTemplate
     5     private JdbcTemplate jdbcTemplate;
     6     
     7     /**
     8      * 插入数据
     9      */
    10     public void insertPerson(Person p) {
    11         jdbcTemplate.update("INSERT INTO person(name,password) VALUES (?,?)", 
    12                 p.getName(),p.getPassword());
    13     }
    14     /**
    15      * 根据ID查询
    16      * 方法queryForObject可以查询单行单列,也可以查询单行多列
    17      * 参数1:SQL
    18      * 参数2:一个类型 或者 一个结果集处理器(取决于查询的结果和需求)
    19      * 参数3:可变参数
    20      */
    21     public Person selectPersonById(Integer id) {
    22         Person person = jdbcTemplate.queryForObject("SELECT *  FROM person WHERE id =?",
    23                 new BeanPropertyRowMapper<Person>(Person.class), id);
    24         return person;
    25     }
    26     /**
    27      * 查询所有
    28      * 方法query可以查询多行多列。
    29      * 参数1:SQL
    30      * 参数2:一个结果集处理器(取决于查询的结果和需求)
    31      * 参数3:可变参数
    32      */
    33     public List<Person> selectPersonList() {
    34         List<Person> personList = jdbcTemplate.query("SELECT * FROM person", 
    35                 new BeanPropertyRowMapper<Person>(Person.class));
    36         return personList;
    37     }
    38 
    39 }
  2. 编写Service接口
     1 public interface PersonService {
     2     /**
     3      * 新增
     4      */
     5     void insertPerson(Person p);
     6     /**
     7      * 查询一个
     8      */
     9     Person selectPersonById(Integer id);
    10     /**
    11      * 查询多个
    12      */
    13     List<Person> selectPersonList();
    14     
    15 }
  3. 编写Service实现类
    使用注解@Service标注为Spring组件。注入DAO对象
     1 @Service
     2 public class PersonServiceImpl implements PersonService {
     3     
     4     @Autowired
     5     private PersonDao personDao;
     6     
     7     @Override
     8     public void insertPerson(Person p) {
     9         personDao.insertPerson(p);
    10     }
    11 
    12     @Override
    13     public Person selectPersonById(Integer id) {
    14         return personDao.selectPersonById(id);
    15     }
    16 
    17     @Override
    18     public List<Person> selectPersonList() {
    19         return personDao.selectPersonList();
    20     }
    21 
    22 }
  4. 编写Controller,提供一组restFUL风格的接口
    使用注解@RestController标注控制器类,使其返回数据结果都是JSON格式。注入Service对象,使用RestFUL风格提供访问接口
     1 @RestController
     2 public class PersonController {
     3     
     4     @Autowired
     5     private PersonService personService;
     6     
     7     /**
     8      * 接口地址:http://localhost:8080/insertPerson
     9      * 请求方式:PUT 入参:JSON数据
    10      */
    11     @RequestMapping(value = "/insertPerson", method = RequestMethod.PUT)
    12     public void insertPerson(@RequestBody Person p) {
    13         personService.insertPerson(p);
    14     };
    15     /**
    16      * 接口地址:http://localhost:8080/selectPersonById/1
    17      * 请求方式:GET 入参:id
    18      */
    19     @GetMapping(value = "/selectPersonById/{id}")
    20     public Person selectPersonById(@PathVariable Integer id) {
    21         System.err.println("id值为:"+id);
    22         return personService.selectPersonById(id);
    23     };
    24     /**
    25      * 接口地址:http://localhost:8080/selectPersonList
    26      * 请求方式:POST 入参:无
    27      */
    28     @PostMapping("/selectPersonList")
    29     public List<Person> selectPersonList(){
    30         return personService.selectPersonList();
    31     };
    32 }
  5. 安装Postman工具,访问测试接口。
    启动SpirngBoot项目主程序,请求接口测试!
    Postman是一款常用的接口测试工具,可以测试发送不同请求,传递不同参数,具体操作请关注博主尽情期待。PostMan下载地址

 

三.整合JPA

  JPAJava Persistence API的简称,中文名Java持久层APISUN公司推出的一套基于ORM的规范。Hibernate框架中提供了JPA的实现,Spring Data JPASpring基于Hibernate开发的一个JPA框架。可以极大的简化JPA的写法,可以在几乎不用写具体代码的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页排序等一些常用的功能。此外更强大的是,它还可以通过方法命名规则进行数据库查询操作

  Spring Data JPA提供的接口:

  1. Repository:最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别。
  2. CrudRepository :是Repository的子接口,提供CRUD的功能(内置默认的简单的CURD方法,可以直接引用)
  3. PagingAndSortingRepository:是CrudRepository的子接口,添加分页和排序的功能。
  4. JpaRepository:是PagingAndSortingRepository的子接口,增加了一些实用的功能,比如:批量操作等。(相对最完整的内置方法:简单的CURD/分页/批量)
  5. JpaSpecificationExecutor:用来做负责查询的接口
  6. Specification:是Spring Data JPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件即可。

其中,自定义查询,方法名命名规范如下:

命名可选关键字方法命名举例执行方法时对应产生的SQL,Where子句
And findByNameAndPwd where name= ? and pwd =?
Or findByNameOrSex where name= ? or sex=?
Is,Equals findById,findByIdEquals where id= ?
Between findByIdBetween where id between ? and ?
LessThan findByIdLessThan where id < ?
LessThanEquals findByIdLessThanEquals where id <= ?
GreaterThan findByIdGreaterThan where id > ?
GreaterThanEquals findByIdGreaterThanEquals where id > = ?
After findByIdAfter where id > ?
Before findByIdBefore where id < ?
IsNull findByNameIsNull where name is null
isNotNull,NotNull findByNameNotNull where name is not null
Like findByNameLike where name like ?
NotLike findByNameNotLike where name not like ?
StartingWith findByNameStartingWith where name like ‘?%’
EndingWith findByNameEndingWith where name like ‘%?’
Containing findByNameContaining where name like ‘%?%’
OrderBy findByIdOrderByXDesc where id=? order by x desc
Not findByNameNot where name <> ?
In findByIdIn(Collection<?> c) where id in (?)
NotIn findByIdNotIn(Collection<?> c) where id not in (?)
True findByAaaTue where aaa = true
False findByAaaFalse where aaa = false
IgnoreCase findByNameIgnoreCase where UPPER(name)=UPPER(?)

这个确实,够强大!但查询条件一多,这个方法名就也会很长了哦……

  Spring Data JPA简单总结:

  1. 提供了基本的CURD方法,批处理,分页方法等。
  2. 允许自定义方法名,根据指定命名规则实现查询。
  3. 允许使用@Query注解,自定义更高级,更复杂的SQL

1.准备工作

  1. 引入spring-data-jap依赖
    1 <!-- SpringBoot整合JPA -->
    2 <dependency>
    3     <groupId>org.springframework.boot</groupId>
    4     <artifactId>spring-boot-starter-data-jpa</artifactId>
    5 </dependency>
  2. 修改yml配置文件设置JPA配置及数据库配置
     1 spring:
     2   datasource:
     3     #根据MySQL版本配置驱动类5.x----8.x 驱动类“com.mysql.jdbc.Driver” 或 “com.mysql.cj.jdbc.Driver”。
     4     driver-class-name: com.mysql.cj.jdbc.Driver
     5     #useSSL:SSL协议提供服务主要作用:(不建议在没有服务器身份验证的情况下建立SSL连接。)     
     6     #   1)认证用户服务器,确保数据发送到正确的服务器;    .
     7     #   2)加密数据,防止数据传输途中被窃取使用;
     8     #   3)维护数据完整性,验证数据在传输过程中是否丢失;
     9     #serverTimezone:设置时区,不设置会报错。GMT%2B8:东八区北京时间  Asia/Shanghai:上海时间
    10     #useServerPrepStmts:在url中给出useServerPrepStmts=true参数,开启预编译(默认PS是关闭的)
    11     #allowMultiQueries:设置为true,开启批量执行sql的开关。更多请持续关注博主文档
    12     url: jdbc:mysql://localhost:3306/springboot?useSSL=false&serverTimezone=GMT%2B8
    13     username: root
    14     password: xsge
    15   #JPA配置——————————————————————————————————————————————————————————————————————————
    16   jpa:
    17     #是否显示SQL
    18     show-sql: true 
    19     hibernate:
    20       #表结构处理方式方式。update表示,第一次执行时根据实体类创建表结构,之后的操作只做数据更新
    21       ddl-auto: update
  3. 新增实体类对应数据库表

    配置实体常用注解:(这里仅仅列举,更多详情请关注博主Hibernate文章)

    @Entity

    作用:标识当前实体为数据实体类

    @Tablename=值)

    作用:标识当前实体对应的是数据库表

    属性:Name:指定当前实体对应数据库表名称

    @Id

    作用:标识当前属性为主键列属性

    @Column

    作用:标识当前属性对应数据库的字段

    属性:Name:指定当前属性所对应的数据库字段列名称

    @GeneratedValue

    作用:指定字段列的主键策略

    属性:Generator:该属性值,指定Hibernate中提供的主键策略声明别名

      Strategy:该属性,标注JPA提供的主键生成策略(即主键生成方式)。

      取值GenerationType:

      AUTO:自适应,选择一下三种,默认选择TABLE

      IDENTITY:Mysql自增长,要判断是否支持

         SEQUENCE:Oracle自增长,要判断是否支持

        TABLE:类似于Helw高低位算法,无需判断,因为使用的是固定格式的算法生成。

     1 @Entity    // 标识当前实体为数据实体类
     2 @Data    // 自动生成get/set等
     3 @AllArgsConstructor    // 全参构造函数
     4 @NoArgsConstructor // 无参构造函数
     5 @Table(name = "student") // 标识实体对应的表名
     6 @EntityListeners(AuditingEntityListener.class) // spring-data-jap实体类数据更新的监听器注解
     7 public class Student {
     8     // 标识当前属性为主键列属性
     9     @Id    
    10     // 标识字段列的主键策略(主键生成方式)GenerationType.IDENTITY表示shiyongMySQL默认自增长生成主键值
    11     @GeneratedValue(strategy = GenerationType.IDENTITY) 
    12     private Integer sid;
    13     
    14     // 标识当前属性对应数据库的字段
    15     @Column(name = "name") 
    16     private String name;
    17     @Column(name = "age")
    18     private Integer age;
    19     
    20     // spring-data-jap中提供的自动填充,新增时自动填充时间(配合SPRING-DATA-JPA监听注解使用)
    21     @CreatedDate 
    22     private Date createTime;
    23     
    24     // spring-data-jap中提供的自动填充,有更新时自动填充更新时间(配合SPING-DATA-JPA监听注解使用)
    25     @LastModifiedDate    
    26     private Date updateTime;
    27 }

    这里需要注意,在《Mybatis-Plus使用全解》中,介绍过如何设置公共字段自动填充功能。比如创建时间和修改时间,创建人和修改人等等,都是可以一进行赋值的。在spring-data-jap中,是使用@CreatedDate@LastModifiedDate标记,同时,需要在实体类上,加@EntityListeners(AuditingEntityListener.class),然后在启动类上加入注解@EnableJpaAuditing(开启JPA自动填充),这样就实现类似公共字段自动填充的功能了。(JPA自动填充注解如下图)

  4. 修改SpringBoot主程序
    在启动类上加入注解@EnableJpaAuditing
     1 @SpringBootApplication
     2 @EnableJpaAuditing  // 开启JPA自动填充功能
     3 @Slf4j
     4 public class AnnotaticSpringBootApplication {
     5     // 主函数
     6     public static void main(String[] args) {
     7         // 启动App
     8         SpringApplication.run(AnnotaticSpringBootApplication.class, args);
     9         log.info("主程序启动了………………");
    10     }
    11 }

     

2.整合实现CURD

  在Spring-Data-Jpa概述中,简单总结了JPA操作的方法,概括有三种:调用原接口方法实现简单CURD,自定义方法名实现简单CURD,自定义SQL实现CURD。通常情况下,三者结合使用,如果功能需求本身直接使用原接口方法就能实现那就无需更改,如果需求需要不同条件,那么自定义方法名即可,如果更为复杂的需求,直接使用自定义SQL实现。

A,接口源方法实现CURD

  1. 编写子接口继承接口JpaRepository。
    说明:JpaRepository接口中继承了来自各个父类的简单CURD方法,包括分页/排序等。这些方法可以直接引用,无需关注SQL。
    1 /**
    2  * 编写DAO接口继承JpaRepository接口
    3  * 泛型参数1:CURD实体类型
    4  * 泛型参数2:主键的类型(通常为Integer或Long)
    5  */
    6 public interface StudentDao extends JpaRepository<Student, Integer>{
    7 }
  2. 编写Service实现。
    (实际开发中需要抽象接口(Inteface)的不能省略,这里图方便就省略了,直接显示的是接口实现类)
    DAO对象直接自动注入即可,SpringBoot-Data-JPA自主扫描Repository及其所有子接口/子实现类组件。
    更多其他方法请自行参考附录
     1 @Service
     2 public class StudentServiceImpl implements StudentService {
     3     
     4     @Autowired
     5     private StudentDao studentDao;
     6     
     7     /**
     8      * 添加:调用JpaRepository的默认方法save实现保存
     9      * 返回值为添加的数据对象,同时还会将添加数据的id给返回
    10      */
    11     @Override
    12     public Student save(Student student) {
    13         return studentDao.save(student);
    14     }
    15     /**
    16      * 根据ID查询:调用JpaRepository的默认方法findById实现根据id查询
    17      * 返回结果为Optional<Student>,是JDK1.8新增的防null对象问题的一个核心类。
    18      * 你可以理解为将对象查询后,有放入到一个Optional容器中。调用方法get即可将对象取出
    19      */
    20     @Override
    21     public Student findById(Integer sid) {
    22         return studentDao.findById(sid).get();
    23     }
    24     /**
    25      * 分页查询:调用JpaRepository的默认方法findAll实现查询所有
    26      * 实际参数类型为:Pageable分页接口,PageRequest使其间接实现子类。
    27      * 参数1:当前页码(从0开始,不能为负数)
    28      * 参数2:当前页数据显示行数(从1开始,不能为负数)
    29      */
    30     @Override
    31     public Page<Student> findAll(Integer page,Integer size) {
    32         return studentDao.findAll(PageRequest.of(page,size));
    33     }
    34 
    35 }
  3. 新增控制器,提供接口测试

     1 @RestController
     2 public class StudentController {
     3     
     4     @Autowired
     5     private StudentService studentService;
     6     
     7     /**
     8      * 测试接口:http://localhost:8080/saveStu
     9      * 请求方式:PUT 入参:JSON数据
    10      */
    11     @RequestMapping(value = "/saveStu",method = RequestMethod.PUT)
    12     public Student save(@RequestBody Student student) {
    13         return studentService.save(student);
    14     }
    15     
    16     /**
    17      * 测试接口:http://localhost:8080/findById
    18      * 请求方式:GET 入参:占位符参数sid
    19      */
    20     @GetMapping("/findById/{id}")
    21     public Student findById(@PathVariable(name = "id") Integer sid) {
    22         return studentService.findById(sid);
    23     }
    24     /**
    25      * 测试接口:http://localhost:8080/findAll
    26      * 请求方式:POST 入参:page=?& size=?
    27      */
    28     @PostMapping("/findAll")
    29     public Page<Student> findAll(Integer page,Integer size) {
    30         return studentService.findAll(page,size);
    31     }
    32     
    33 }
  4. 启动主程序,利用Postman工具测试

     

     

B,自定义方法名及自定义SQL实现操作

  当功能需求无法通过原接口方法能实现时,就需要手动自定义方法,或者自定义SQL实现CURD。其实并不需要多复杂的部署,在源方法案例的基础上,直接在持久层接口中新增自定义方法名的方法或者新增自定义SQL的方法即可。

 1 public interface StudentDao extends JpaRepository<Student, Integer>{
 2     // 使用自定义命名方法名规则,进行查询服务,并添加分页功能
 3     List<Student> findByNameContaining(String name,Pageable pageable);// …… where name like ‘%?%’
 4     
 5     /**
 6      * @Query进行 自定义sql编写
 7      * nativeQuery=true:表示定义的SQL为标准SQL(没有这一项,SQL语句中的表名和字段名是实体类名和实体类中的字段名)
 8      * 传参数使用占位符?代替,但需要注意的是这里的占位符后面需要跟数字(第几个?N 数字N从1开始)
 9      */
10     @Query(value="select * from student where name = ?1",nativeQuery=true)
11     List<Student> queryByName(String name);
12 }

  其他测试省略……

 

附录

  JPA默认方法说明:查源码结构如下(内部方法好好看吧!)

1  QueryDslJpaRepository
2  ┣━ QueryDslPredicateExecutor
3  ┗━ SimpleJpaRepository
4      ┣━ JpaSpecificationExecutor
5      ┗━ JpaRepository
6          ┣━ QueryByExampleExecutor
7          ┗━ PagingAndSortingRepository
8              ┗━ CrudRepository
9                  ┗━Repository

 

  

 

你是否还会在灯火下守候……
posted @ 2020-11-04 20:16  净重21克  阅读(2363)  评论(0编辑  收藏  举报