springboot11 JPA

一、JPA

1. JPA 介绍

JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一 。 其实在5.0出现发布之前,市面上有诸如、Hibernate 、OpenJPA 、TopLink 等一些列 ORM框架,并且hibernate已经很受大众喜爱了,后来hibernate在3.2版本正式接入JPA 规范,表示自己即属于JPA的框架的具体实现。 一句话概括: JPA 是一种规范、 Hibernate 是这种规范的最好实现。

Dao最受欢迎的两个框架: MyBatis ----不是JPA体系 ----- 需要写sql语句 | Hibernate --- JPA 体系 -----可以不用写sql语句

2. ORM介绍

Object Relational Mapping : 对象关系映射 。 ORM的思想其实就是让 表 和 JavaBean 形成一种映射关系 , 并且让表里面的字段 和JavaBean 里面的成员形成映射关系。

01

3. JPA 入门

此处就以一个简单的保存案例来实现JPA 入门吧。

  • 添加JPA 依赖
// 在maven 搜索关键字  persistence 即可  hibernate的核心依赖,会包含它对JPA规范的升级,所以这个依赖可以不加 
//compile group: 'javax.persistence', name: 'persistence-api', version: '1.0'

//不要忘记了添加mysql数据库依赖
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.17'

//下面要添加hibernate的依赖,因为JPA 只是一套规范而已,不是具体的实现,JPA的实现有很多,
//这里采用hibernate作为我们的实现,所以需要添加hibernate的依赖库
//compile group: 'org.hibernate', name: 'hibernate-core', version: '4.3.9.Final'

//hibernate实体管理者,对接JPA规范的管理员  这是hibernate为了迎合JPA的规范做出来的。
compile group: 'org.hibernate', name: 'hibernate-entitymanager', version: '4.3.9.Final'
  • 定义实体类
@Entity(name="t_user")
public class User {
    private static final String TAG = "User";

    private int id;
    private String name;
    private int age ;


    @Id
    @GeneratedValue
    public int getId() {
        return id;
    }
    
    //剩下的get 和 set方法
    ...
}
    
  • 定义配置文件

必须要说明一点,配置文件的名字需要固定,必须叫:persistence.xml , 而且也必须放在 META-INF下面,不能直接放在resource下面。需要在resource下面,新建META-INF 文件夹,然后在放置 xml文件

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    


    <!--持久化单元,数据库 & 实体类 & 具体实现方式配置单元-->
    <persistence-unit name="user">

        <!--声明提供者,也就是说具体实现,因为JPA 只是规范,这里使用hibernate作为具体实现-->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <!--表示实体类有哪些-->
        <class>com.itheima.bean.User</class>

        <!--数据库连接参数-->
        <properties>
            <!--使用jpa的配置-->
            <!--<property name="javax.persistence.jdbc.url"     
                                value="jdbc:mysql://localhost:3306/test"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="root"/>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/> -->

            <!--也可以使用hibernate的配置为了以后无缝对接,还是建议使用上面的规范化的JPA 配置-->
            <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/test"/>
            <property name="hibernate.connection.username" value="root"/>
            <property name="hibernate.connection.password" value="root"/>
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>

            <!--表示自动建表-->
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>

    </persistence-unit>
</persistence>
  • 测试代码
@Test
    public  void testJPA(){

        //创建实体管理工厂 , 参数的user 来自于配置文件的配置单元名字。
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("user");

        //创建实体管理员
        EntityManager manager = factory.createEntityManager();

        //获取事务对象
        EntityTransaction transaction = manager.getTransaction();

        //开启事务
        transaction.begin();

        //构建实体对象
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);


        //持久化,也就是保存到数据库
        manager.persist(user);
        
        //提交事务
        transaction.commit();
        
         //关闭管理员
        manager.close();

        //关闭工厂,一般不关闭,后面也不用关心这个了。
        factory.close();
    }

4. 入门详解

a. 注解解释

@Entity : 表示该类是一个实体,也就是和表形成映射,可以添加属性来指定对应的表

​ 如:@Entity(name="t_user")

@Table : 指定表名,可以不写,可以在entity注解上直接表示标识表名。

​ 如 : @Table(name="t_user")

@Id : 用于表示主键,指定哪一个属性和主键对应 可以在变量上声明,也可以在get方法上声明,建议统一。

@GeneratedValue : 用于表示主键生成策略 主键生成策略,提供的有4种

  • 主键策略解释
1、AUTO 自动选择一个最适合底层数据库的主键生成策略。如MySQL会自动对应auto increment。这个是默认选项,即如果只写@GeneratedValue,等价于@GeneratedValue(strategy=GenerationType.AUTO)。注意在mysql5 + hibernate 5的版本测试下,会产生另外一张表,用于记录主键值。 可以在核心配资文件中添加此属性来达到native效果
    <property name="hibernate.id.new_generator_mappings">false</property>

2、IDENTITY 表自增长字段,Oracle不支持这种方式。

3、SEQUENCE 通过序列产生主键,MySQL不支持这种方式。

4、TABLE 通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。不同的JPA实现商生成的表名是不同的,如 OpenJPA生成openjpa_sequence_table表,Hibernate生成一个hibernate_sequences表,而TopLink则生成sequence表。这些表都具有一个序列名和对应值两个字段,如SEQ_NAME和SEQ_COUNT。
  • 关于UUID策略

通常我们建表,主键都是int类型,但是其实在开发的时候,有些表也会建成String类型,也就是varchar类型 , 并且有时候我们要主键唯一,这时候需要使用到uuid策略了。JPA并没有提供UUID 策略, 好在hibernate提供了这种策略的支持。

//声明uuid这种策略类型。然后给定一个别名,叫做jpa-uuid  这个注解是hibernate提供的注解, jpa的注解没有
@GenericGenerator(name="jpa-uuid" ,strategy="uuid") 

//表示对应 t_student 这个表
@Entity(name="t_student")
public class Student {

    private String id;
    private String name;
    private int age;
    private String phone;
    private String address;
    
    //指定使用你的主键策略是 jpa-uuid这个名称对一个你的类型,其实就是指定了主键是uuid 。 
    //由hibernate来维护主键。
    @Id 
    @GeneratedValue(generator="jpa-uuid")
    public String getId() {
        return id;
    }
    ... 
}

b. xml配置解释

  • xml必须放在META-INF下面,名字也必须固定是persistence.xml
  • xml其实就是用于表示怎么连接数据库,具体干活的是JPA实现是什么, 以及有哪些映射实体类。

img02

  • 具体的属性名称,可以参考pdf文档。 第八章节

img03

  • 关于hibernate的配置,请参考hibernate的pdf文档

img04

c. 测试代码解释

代码没有什么好解释的了,必须要有实体管理员工厂,然后获取到管理员,后续就可以操作了。 可以稍稍解释下即可

5. CRUD

JPA 有一个要求就是写入操作,需要使用事务,否则数据不会到达数据库。

1. 增加

 @Test
public  void testJPA(){

    //创建实体管理工厂 , 参数的user 来自于配置文件的配置单元名字。
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("user");

    //创建实体管理员
    EntityManager manager = factory.createEntityManager();

    //获取事务对象
    EntityTransaction transaction = manager.getTransaction();

    //开启事务
   transaction.begin();

    //构建实体对象
    User user = new User();
    user.setName("zhangsan");
    user.setAge(28);


    //持久化,也就是保存到数据库
    manager.persist(user);

    //提交事务
    transaction.commit();
    
     //关闭管理员
    manager.close();

    //关闭工厂,一般不关闭,后面也不用关心这个了。
    factory.close();
}

2. 删除

需要删除某条记录,需要先把它查询出来,然后才能删除。

@Test
public  void testDelete(){

    //创建实体管理工厂 , 参数的user 来自于配置文件的配置单元名字。
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("user");

    //创建实体管理员
    EntityManager manager = factory.createEntityManager();

    //获取事务对象
    EntityTransaction transaction = manager.getTransaction();

    //开启事务
    transaction.begin();

    //查询主键为1的用户
    User u = manager.find(User.class,1);

    //删除用户
    manager.remove(u);

    //提交事务
    transaction.commit();
    
    //关闭管理员
    manager.close();

    //关闭工厂,一般不关闭,后面也不用关心这个了。
    factory.close();
}

3. 修改

修改也一样,需要先查询,然后修改后,在持久化。

 @Test
    public  void testUpdate(){

        //创建实体管理工厂 , 参数的user 来自于配置文件的配置单元名字。
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("user");

        //创建实体管理员
        EntityManager manager = factory.createEntityManager();

        //获取事务对象
       EntityTransaction transaction = manager.getTransaction();

        //开启事务
        transaction.begin();


        //查询主键为1的用户
        User u = manager.find(User.class,2);
        u.setAge(102);

        //这句可以不写,因为查询出来的user对象已经是持久态,直接操作它也可以修改数据库
       // manager.persist(u);
       /* //删除用户
        manager.remove(u);*/

        //提交事务
       transaction.commit();
    
        //关闭管理员
        manager.close();

        //关闭工厂,一般不关闭,后面也不用关心这个了。
        factory.close();

    }

4. 查询

 @Test
public  void testQuery(){

    //创建实体管理工厂 , 参数的user 来自于配置文件的配置单元名字。
    EntityManagerFactory factory = Persistence.createEntityManagerFactory("user");

    //创建实体管理员
    EntityManager manager = factory.createEntityManager();

    //不支持写* 
    Query query = manager.createQuery("select t from User t");

    List<User> list = query.getResultList();

    System.out.println("list=" + list);
    
    //关闭管理员
    manager.close();
    
    //关闭工厂,一般不关闭,后面也不用关心这个了。
    factory.close();
}

6. 对象时态

这个状态主要是针对我们操作的持久化类对象。 有四种状态

  • 瞬时态

对象仅仅是创建出来(new对象),没有和EntityManager 产生关系

  • 持久态

和EntityManager建立关系,被持久化,保存到数据库或者刚从数据库查询出来。

  • 脱管态

已经持久化过了,现在要脱离管理。 提交事务,或者清除EntityManager都会走向这个状态。

  • 删除态

该状态只有在JPA 范畴里面才有,单独拿hibernate来说,没有这个状态。只有调用了EntityManager

的remove方法,才会走到这个状态。

img05

7. 多表关系确立

最好使用例子引出关系确立的重要性。

a. 多对一

此处以分类 & 商品的关系来解释

  • 分类 Category
@Entity(name="category")
public class Category {

    private int id;
    private String name;


    @Id
    @GeneratedValue
    public int getId() {
        return id;
    }
    
    //剩下的get &  set方法
    ...
}
  • 商品 Product

商品在这个关系里面扮演的是外键的关系,是多方的角色,也就是一种商品分类,可以有很多件商品。所以从商品的位置出发,它和分类的关系是多对一的关系。

@Entity(name="product")
public class Product {

    private int id ;
    private String name;
    private double price;

    //表示该商品属于哪一种分类。
    private Category category;


    @Id
    @GeneratedValue
    public int getId() {
        return id;
    }

    // @ManyToOne表示是多对一关系 optional =false 表示该外键不能为空
   @ManyToOne(optional = false)
    
    //@JoinColumn name=cid 通俗的意思是: 拿什么列来作为外键,
    //这里指定cid , 表示生成的是外键名称叫做cid
   @JoinColumn(name="cid")
   public Category getCategory() {
        return category;
    }
    
    ...
}

b. 一对多

一对多,比前面的多对一稍微麻烦一点,而且一对多必须双方都配上注解,否则生成的关系会比较乱。

  • 分类 Category

区别只是一方 category 里面多了一个注解 @OneToMany

@Entity(name="category")
public class Category {

    private int id;
    private String name;


    private Set<Product> productSet = new HashSet<Product>();

    @Id
    @GeneratedValue
    public int getId() {
        return id;
    }


    //mappedBy 表示和谁形成映射关系,也表示谁来维护这层关系。
    @OneToMany(mappedBy = "category")
    public Set<Product> getProductSet() {
        return productSet;
    }

    //剩下的get & set 方法
    ...
}
  • 商品 Product
@Entity(name="product")
public class Product {

    private int id ;
    private String name;
    private double price;


    //表示该商品属于哪一种分类。
    private Category category;


    @Id
    @GeneratedValue
    public int getId() {
        return id;
    }


   @ManyToOne(optional = false)
   @JoinColumn(name="cid")
   public Category getCategory() {
        return category;
    }

    //剩下的get & set方法
    ...
}

8 . 查询

  1. 对象导航查询

所谓对象导航查询的意思是: 如果A表和B表存在主外键关系(一对多 | 多对一 ),那么在查询A表的时候, 也会顺便查询与该条记录关联的B表记录信息。如分类和商品的关系, 如果查询手机分类,那么会顺便把商品表中属于手机分类的商品给查询出来。

二、SpringData JPA

1. 介绍

在JavaEE 5.0发布的时候,sun公司就提出了jpa的规范,希望整合ORM技术,实现天下归一 。 虽然我们学过的orm技术只有hibernate、但是其实orm技术还有其他的一些方案,比如Apache的 openJPA。 Spring 作为业务逻辑层框架,起到承上启下的作用。所以它对JPA的这些技术实现做了一套封装。大家只要按照规范来配置即可,甚至你们的dao层实现都不用实现了,它在内部给你实现,如果我们想换到其他的jpa实现方案,那么只需要修改配置即可。 这就是我们要说的Spring Data JPA。

JPA

Hibernate | Open Jap | Toplink ....

Spring Data JPA ---> 对Dao层的代码再一次升级封装 。 统一的JPA的实现,在封装。

2. 入门

1. 搭建环境 (建表)

  • 添加依赖
//mysql驱动
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.17'

//springboot 依赖
compile("org.springframework.boot:spring-boot-starter-web:1.5.10.RELEASE")

//spring data jpa 注意: 不要引入错了,要找的组是springboot的依赖。如下面这条注释的是错误的。
//因为我们使用了SpringBoot,所以采用的库,也要是springboot组下的。
//compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '1.11.3.RELEASE'


compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '1.5.10.RELEASE'
  • 添加配置文件

同样还是那个 application.properties , 位于 resources 下面

#连接数据库
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver


#hibernate配置
# 自动建表 update:表示有表则直接使用,无表就新建
spring.jpa.hibernate.ddl-auto=update

# 表示在操作时,输出sql语句
spring.jpa.show-sql=true
  • 实体类
//name :用于表示构建出来的表名
@Entity(name="t_user")
public class User {
    private static final String TAG = "User";
    private int id;
    private String name;
    private int age ;


  
    @Id
    @GeneratedValue
    public int getId() {
        return id;
    }
    
    //剩余的get & set方法代码
    ...
}    

2. 添加数据

上面仅仅是完成了表的创建工作,实际上还并没有看出来SpringData JPA 有什么厉害之处,现在就要往表里面添加数据了。

  • controller
@RestController
public class UserController {

    //这里要注入业务逻辑层引用
    @Autowired
    private UserService userService;


    @RequestMapping("/save")
    public String save(){

        User user = new User();
        user.setName("奥巴马");
        user.setAge(58);

        //调用业务逻辑层
        userService.save(user);
        return "save success~!";
    }
}
  • service
public interface UserService {

    void save(User user);
}

---------------------------------------------------------------

@Service
@Transactional
public class UserServiceImpl implements UserService {

    //这里要注入UserDao引用
    @Autowired
    private UserDao userDao;

    @Override
    public void save(User user) {
        userDao.save(user);
    }
}
    
  • dao

注意: dao层只有一个接口而已,方法没有,其实是父类CrudRepository 已经定义了一些常用的方法,我们可以直接拿过来用即可。真正我们在业务逻辑层拿到的是 SimpleJPARepository 这个类的实例。它其实就是实现了CrudRepository接口方法。

public interface UserDao extends CrudRepository<User, Integer> {
    
}

三、 通用接口介绍

经过上面的入门例子,大家也看到了,spring data对于我们编程确实方便了许多。我们无需关心dao层的实现,只需要简单的照着规则去写代码即可,spring data jpa 最重要的就是我们的dao层继承的接口。接下来给大家着重说明它的几个接口。

1. Repository

这个接口是所有接口的父接口,也就是它就是老大了。所有我们后面用的接口都是从这位兄弟身上扩展来的。 下图是Repository 这个接口的继承体系,图中的UserDao 和 MyRepository 是我自己写的,大家不用理会。 不过要声明的是: 这个Repository 是一个空接口!!!,里面没有任何方法.

01

package org.springframework.data.repository;

import java.io.Serializable;
/**
 * Central repository marker interface. Captures the domain type to manage as well as the domain type's id type. General
 * purpose is to hold type information as well as being able to discover interfaces that extend this one during
 * classpath scanning for easy Spring bean creation.
 * <p>
 * Domain repositories extending this interface can selectively expose CRUD methods by simply declaring methods of the
 * same signature as those declared in {@link CrudRepository}.
 * 
 * @see CrudRepository
 * @param <T> the domain type the repository manages
 * @param <ID> the type of the id of the entity the repository manages
 * @author Oliver Gierke
 */
public interface Repository<T, ID extends Serializable> {

}

这个repository虽然是个空接口,我们自己来继承它,虽然没有任何要实现的方法,但是我们可以自己写,自己声明方法,这也是允许D~~ ,话不多说,走起~~~

  • 自定义接口 MyRepository
public interface MyRepository extends Repository<User, Integer> {

    /**
     * 根据用户id查询用户
     * @param uid
     * @return
     */
    User findByUid(int uid);

}
  • service层代码
public interface UserService {

    User findByUid(int uid);
}

------------------------------
@Service
@Transactional  //现在仍然是查询这个注解可以不写,为了免得大家记太多,我就索性都打开了。
public class UserServiceImpl implements UserService {   
    @Autowired
    private MyRepository repository;

    @Override
    public User findByUid(int uid) {
        return repository.findByUid(uid);
    }

}  
  
  • 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestJpa {
    
    @Autowired
    private UserService userService;
  
    @Test
    public void testFindByUid(){
        System.out.println(userService.findByUid(1));
        
    }
}
  • 结果:

03

  • 疑问:

写到这,有的同学绝对会有疑问, 这都可以,那这也行的话,我写一个叫做 fUid() 或者叫做 zhaoUserByUid() 这样的方法名,行不行呢? 答案是: NO!

因为我们声明的是接口,具体的实现类还是由人家的spring做出来的。spring胃口比较挑剔,而且为了满足大众化的口味,就做出了一些命名上的要求,大家要慢慢习惯这种要求,这也正是它的另一个框架Spring Boot的口号习惯优于配置 。 下图摘自 Spring Data JPA 文档对方法关键字的描述。

如:我们想按照用户的name 查询, 那么可以写成 findByName 以此类推

04

05

如果觉得这些关键字记不住,那么spring可以允许我们程序员自己定义自己的方法名。

2. 自定义方法名

这个小节主要讲述的是,我们可以自己定义自己的方法名,你爱叫啥叫啥,随意。

  • Dao接口
public interface MyRepository extends Repository<User, Integer> {


    @Query("from User  where uid = ?1") //?1 表示取第一个参数uid 来替代这个?。
    User selectUser(int uid);

}
  • Service
public interface UserService {

    User selectUser(int uid);
}
  • 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestJpa {
    
    @Autowired
    private UserService userService;

    @Test
    public void testSelectUser(){
        System.out.println(userService.selectUser(1));
        
    }
}

虽然我们可以自己定义方法名,但是这种做法稍微有点麻烦,所以建议少用这种写法。 只有在以后执行多表查询的时候,我们才需要这么做。

3. CrudRepository

这个接口是扩展了repository, 在这个接口里面默认给大家声明好了一些增删改查的方法,此处给大家演练一个查询所有用户的操作

  • Dao
public interface UserDao extends CrudRepository<User, Integer> {
    //查询所有的用户
}
  • Service
@Service
//@Transactional
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserDao userDao;

    @Override
    public List<User> findAll() {
        return (List<User>) userDao.findAll();
    }

}
  • 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestJpa {
    
    @Autowired
    private UserService userService;
    
    @Test
    public void testFindAll(){
        List<User> list= userService.findAll();
        System.out.println("list="+list);
    }
}

4.PagingAndSortingRepository

这个接口是是CrudRepository的扩展接口,除了兼备增删改查之外,它还扩展了分页查询&排序的效果。

以下是这个接口的代码声明

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {

    /**
     * Returns all entities sorted by the given options.
     * 
     * @param sort
     * @return all entities sorted by the given options
     */
    Iterable<T> findAll(Sort sort);

    /**
     * Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.
     * 
     * @param pageable
     * @return a page of entities
     */
    Page<T> findAll(Pageable pageable);
}
  • Dao
public interface UserDao extends PagingAndSortingRepository<User, Integer> {
    //查询所有的用户
}
  • Service
public interface UserService {
    
    
    /**
     * 返回值是page 这个类型是springdatajpa定义好的。其实就和
     * 我们自己平常写的PageBean 一样。
     * @param pageable
     * @return
     */
    Page<User> findByPage(Pageable pageable);
}

---------------------------------------------------------
  
  @Service
//@Transactional
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserDao userDao;

    @Override
    public Page<User> findByPage(Pageable pageable) {
        return userDao.findAll(pageable);
    }

}
  • 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestJpa {
    
    @Autowired
    private UserService userService;
    
    @Test
    public void testFindByPage(){
        //这里ctrl + T 看实现类,然后找PageRequest ,别说你不会哈~~
        // page : page表示请求的页码数, 从0开始,0就代表第一页大家。
        //size : 表示每页拿多少条记录
        Pageable pageable = new PageRequest(1, 2);
        Page<User> page = userService.findByPage(pageable);
        
      //当前是第几页 这个页码从0开始的,大家可以加上1表示从1开始。
        System.out.println(page.getNumber()); 
        System.out.println(page.getTotalPages()); //总页数
        System.out.println(page.getSize()); //每页个数
        System.out.println(page.getTotalElements()); //总记录数
        System.out.println(page.getContent()); //总页数
    }
}

重点:

多表关系建立
    @ManyToOne  @OneToMany

SpringDataJPA 完成CRUD
    CrudRepository

猜你喜欢

转载自www.cnblogs.com/xiaocongcong888/p/9436309.html