• SpringBoot 整合 MyBatis,实现 CRUD 示例


    前言

    有 Java Web 应用开发经验的同学应该很熟悉 Controller/Service/Dao 这样的三层结构设计,MyBatis 就是实现 Dao 层的主流方式之一,用于完成数据库的读写操作;Dao 层服务于 Service 层,用于完成完成业务逻辑操作。

    本文聚焦于 SpringBoot 和 MyBatis 的整合使用,考虑到引入 Controller 和 Service 层描述起来会比较复杂,因此仅涉及 Dao 层。

    创建项目/模块

    在 Maven 项目 SpringBoot 中添加模块 mybatis 用于演示,mybatis 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">
      <parent>
        <artifactId>springboot</artifactId>
        <groupId>tech.exchange</groupId>
        <version>0.1</version>
      </parent>
    
      <modelVersion>4.0.0</modelVersion>
    
      <artifactId>mybatis</artifactId>
    </project>
    

    按常规套路,都是 Controller 调用 Service,Service 调用 Dao;如前所述,不引入 Controller 和 Service,那么怎么实现 Dao 的调用呢?

    其实,SpringBoot 不仅可以是一个 Web 应用,也可以是一个 命令行(Console) 应用,类似于一个 Java Main 的应用程序。

    SpringBoot Console Application

    演示如何创建一个 SpringBoot 命令行应用。

    1. 添加依赖
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
        </dependency>
    

    注意:Web 应用需要添加依赖 spring-boot-starter-web,命令行 应用需要添加依赖 spring-boot-starter,两者是不一样的。

    1. 创建 Main
    package tech.exchange.springboot.mybatis;
    
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * @author yurun
     */
    @SpringBootApplication
    public class Main implements CommandLineRunner {
    
      @Override
      public void run(String... args) throws Exception {
    
      }
    
      public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
      }
    }
    
    

    这里的 Main,本质就是一个 Java Main,只不过额外添加注解和实现特定接口方法。

    CommandLineRunner

    CommandLineRunner is a simple Spring Boot interface with a run method. Spring Boot will automatically call the run method of all beans implementing this interface after the application context has been loaded.

    CommandLineRunner 是 SpringBoot 的一个接口,它只有一个 run 方法;SpringBoot 容器加载完成之后,所有实现 CommandLineRunner 接口的 Beans 都会被自动调用 run 方法。

    Main 就相当于实现接口 CommandLineRunner 的一个特殊 Bean,SpringBoot 容器加载完成之后,run 方法会被自动执行。我们可以在 run 方法内部实现 Dao 的调用。

    SpringBoot 集成 MyBatis

    MyBatis 官方提供了 SpringBoot 的集成方案,过程很简单,添加依赖 mybatis-spring-boot-starter 即可:

        <dependency>
          <groupId>org.mybatis.spring.boot</groupId>
          <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
    

    SpringBoot 和 MyBatis 集成完成。

    本文数据库使用 MySQL,添加驱动 mysql-connector-java :

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

    创建数据库/表

    创建演示使用的数据表 mytable:

    CREATE TABLE `mytable` (
      `id` int NOT NULL AUTO_INCREMENT,
      `col1` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
      `col2` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
    

    其中,主键 id 使用自增(AUTO_INCREMENT)策略。

    配置数据源/连接池

    SpringBoot 和 数据库 的交互需要通过数据源(DataSource)实现,数据源仅需要使用配置文件(application.yml)声明相关属性即可,不需要额外操作。MyBatis 也会使用数据源完成自身的初始化。

    数据源

    创建 application.yml(src/main/resources/application.yml):

    spring:
      datasource:
        url: jdbc:mysql://mysql_dev:13306/yurun?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: root
    
    • url:MySQL 连接信息,包括地址、端口、数据库名称和其他参数;其中,建议设置 useUnicode=true 和 characterEncoding=UTF-8,避免出现中文乱码的情况;
    • username:MySQL 用户名;
    • password:MySQL 密码;

    连接池

    连接池就是缓存若干数据库(MySQL)的连接(Connection),避免连接的重复创建销毁,减少 SQL 执行耗时。

    SpringBoot 集成 MyBatis 时,会自动集成连接池 HikariCP,这也是 SpringBoot 官方推荐使用的,连接池这里使用默认配置。

    CRUD

    MyBatis 对于数据库的读写操作是通过 Mapper 实现的,Mapper 有两种形式:

    • 接口(Interface) + XML
    • 接口(Interface) + 注解

    这两种形式并没有绝对意义上的好坏之分,使用 接口 + 注解,只需要编写一个接口,实现方式会更简洁一些,但灵活性会相对弱一些;使用 接口 + XML,除编写接口之外,还需要编写额外的 XML 文件,但灵活性更强。

    本文使用 接口 + XML 的形式,以读写数据表 mytable 为例,创建接口和XML文件。

    创建接口

    接口文件:src/main/java/tech/exchange/springboot/mybatis/dao/MyTableMapper.java

    package tech.exchange.springboot.mybatis.dao;
    
    import org.apache.ibatis.annotations.Mapper;
    
    /**
     * @author yurun
     */
    @Mapper
    public interface MyTableMapper {
    }
    
    

    创建 XML

    XML文件:src/main/resources/mapper/MyTableMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="tech.exchange.springboot.mybatis.dao.MyTableMapper">
    </mapper>
    

    接口文件名称建议和XML文件名称保持一致(非强制)方便查找。

    XML文件位于目录 src/main/resources/mapper,需要配置 application.yml,使得 Mapper 可以被 SpringBoot 正确扫描加载。

    mybatis:
      mapper-locations:
        - classpath:mapper/*.xml
    

    相当于告诉 SpringBoot,到类路径下的 mapper 目录下面扫描加载 Mapper。

    XML namespace 用于绑定 Mapper 的接口和XML文件,两者必须一一对应,namespace 的值必须为接口的全类名。

    创建实体类

    package tech.exchange.springboot.mybatis.model;
    
    /**
     * @author yurun
     */
    public class MyRow {
      private int id;
      private String col1;
      private String col2;
    
      ......
    }
    
    

    实体类 MyRow 的字段与数据库表 MyTable 的字段一一对应(非强制),MyRow 的一个实例表示 MyTable 的一行记录。

    Create

    1. 插入单行记录

      XML插入元素

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="tech.exchange.springboot.mybatis.dao.MyTableMapper">
        <insert id="insertMyRow">
          INSERT INTO
            mytable (col1, col2)
          VALUES
            (#{col1}, #{col2})
        </insert>
      </mapper>
      

      MyBatis XML 可以使用 #{name} 的形式声明参数;其中,name 为参数名称。

      那么,这些参数来源于哪里?元素 insert 有一个 属性 parameterType 用于指定参数类型,如:

        <insert id="insertMyRow" parameterType="tech.exchange.springboot.mybatis.model.MyRow">
          ......
        </insert>
      

      表示会使用 MyRow 的实例来传递元素 insert 所需的参数,#{col1} 和 #{col2} 分别对应着 MyRow (实例)的 字段 col1 和 col2。

      大多数情况下,我们可以省略属性 parameterType,MyBatis 会为我们自动推断这个类型。

      接口方法

      package tech.exchange.springboot.mybatis.dao;
      
      import org.apache.ibatis.annotations.Mapper;
      import tech.exchange.springboot.mybatis.model.MyRow;
      
      import java.util.List;
      
      /**
       * @author yurun
       */
      @Mapper
      public interface MyTableMapper {
        /**
         * 插入一行记录。
         *
         * @param row 一行记录
         * @return 影响行数
         */
        int insertMyRow(MyRow row);
      }
      
      

      接口方法名称需要与XML插入元素id保持一致,调用接口方法时会执行对应名称XML元素中的SQL语句。

      接口方法使用 MyRow 实例传入参数,XML插入元素 INSERT 语句中的参数 #{col1}、#{col2} 需要与实体类 MyRow 中的字段 col1、col2 名称保持一致(字段顺序可任意);也就是说,接口方法执行时,会将 MyRow 实例对象 row 中的字段值按名称赋值给 INSERT 语句中的各个参数。

      接口方法的返回值为影响行数,插入单选记录返回值为1。

      :插入、修改和删除的接口方法返回值均为影响行数。

      插入单行记录

      在 Main 中添加 MyTableMapper 实例 mapper:

        @Autowired
        private MyTableMapper mapper;
      

      @Autowired 表示 SpringBoot 会根据接口类型自动注入相应的实例。

      在 Main run 方法中添加插入记录的代码:

          MyRow row = new MyRow();
      
          row.setCol1("a");
          row.setCol2("b");
      
          int value = mapper.insertMyRow(row);
          System.out.println(value);
      

      因为数据表 mytable 的主键 id 是自增的,所以不需要设置 MyRow id 的值。

      Main 完整代码:

      package tech.exchange.springboot.mybatis;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.CommandLineRunner;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import tech.exchange.springboot.mybatis.dao.MyTableMapper;
      import tech.exchange.springboot.mybatis.model.MyRow;
      
      /**
       * @author yurun
       */
      @SpringBootApplication
      public class Main implements CommandLineRunner {
      
        @Autowired
        private MyTableMapper mapper;
      
        @Override
        public void run(String... args) throws Exception {
          MyRow row = new MyRow();
      
          row.setCol1("a");
          row.setCol2("b");
      
          int value = mapper.insertMyRow(row);
          System.out.println(value);
        }
      
        public static void main(String[] args) {
          SpringApplication.run(Main.class, args);
        }
      }
      
    2. 插入单行记录,且获取已插入记录的主键ID

      如前文所述,数据表 mytable 主键字段 id 是支持自增的,这里所说的就是获取已插入记录 id 的自增值。

      XML插入元素

        <insert id="insertMyRow" useGeneratedKeys="true" keyProperty="id">
          INSERT INTO
          mytable (col1, col2)
          VALUES
          (#{col1}, #{col2})
        </insert>
      
      • useGeneratedKeys 值为 true,表示通知 MyBatis 使用 JDBC getGeneratedKeys 方法获取已插入记录自增主键的值;
      • keyProperty 值为 id,表示 MyBatis 会将获取到的自增主键的值,赋值给 MyRow 实例的字段 id;

      插入单行记录,且获取已插入记录的主键ID

          // 创建记录
          MyRow row = new MyRow();
      
          row.setCol1("a");
          row.setCol2("b");
      
          // 插入
          int value = mapper.insertMyRow(row);
          System.out.println(value);
          
          // 获取主键ID
          System.out.println(row.getId());
      

      插入方法执行完成之后

          mapper.insertMyRow(row)
      

      即可以获取主键ID

          row.getId()
      

      获取主键 ID 是通过 row 记录实例获取的。

    3. 插入多行记录

      插入多行记录和插入单行记录,大体类似,关键在于 INSERT 语句如何表述插入多行操作。

      XML插入元素

        <insert id="insertMyRows" useGeneratedKeys="true" keyProperty="id">
          INSERT INTO
          mytable (col1, col2)
          VALUES
          <foreach item="item" collection="list" separator=",">
            (#{item.col1}, #{item.col2})
          </foreach>
        </insert>
      

      插入多行记录的关键就是需要使用 foreach 元素,循环生成插入多行记录所需的 SQL 语句:

      • collection="list",表示接口方法会通过集合(List)传入多行记录,如:List
      • item="item",表示处理某一行记录时,该行记录的名称(别名)为 item,可以通过 item 获取记录字段的值;如:item.col1,表示获取某个记录(row)字段 col1 的值;
      • separator=",",表示多行记录的处理结果以逗号进行连接;如:(a1, b1), (a2, b2)。

      还可以有另一种写法:

        <insert id="insertMyRows" useGeneratedKeys="true" keyProperty="id">
          <foreach item="item" collection="list" separator=";">
            INSERT INTO
            mytable (col1, col2)
            VALUES
            (#{item.col1}, #{item.col2})
          </foreach>
        </insert>
      

      同样是插入多行记录,第一种写法是通过一次请求(MySQL)执行一条 SQL 语句实现的,第二种写法是通过一次请求执行多条 SQL 语句实现的,这里仅仅是为了演示 foreach 的灵活用法,实际批量场景中不推荐这样使用。

      注意:MySQL 一次请求执行多条 SQL 语句需要数据库连接Url添加 allowMultiQueries=true。

      接口方法

        /**
         * 插入多行记录。
         *
         * @param rows 多行记录
         * @return 影响行数
         */
        int insertMyRows(List<MyRow> rows);
      

      相较于插入单行记录,接口方法的参数为 MyRow(单数);插入多行记录,接口方法的参数为 List(复数)。

      插入多行记录

          MyRow row1 = new MyRow();
      
          row1.setCol1("a1");
          row1.setCol2("b1");
      
          MyRow row2 = new MyRow();
      
          row2.setCol1("a2");
          row2.setCol2("b2");
      
          // 多行记录
          List<MyRow> rows = new ArrayList<>();
      
          rows.add(row1);
          rows.add(row2);
      
          // 插入,返回影响行数
          int value = mapper.insertMyRows(rows);
          System.out.println(value);
      
          rows.forEach(row -> {
            // 获取已插入记录的主键ID
            System.out.println(row.getId());
          });
      

      和插入单行记录类似,多行记录插入完成之后,每一条记录的 id 字段也会被 MyBatis 自动赋予相应的主键ID值。

    Read

    1. 查询单行记录

      指定记录ID,查询相应的一行记录。

      XML查询元素

        <select id="selectMyRow" resultType="tech.exchange.springboot.mybatis.model.MyRow">
          SELECT
            *
          FROM
            mytable
          WHERE
            id = #{id}
        </select>
      

      元素 select 必须使用返回类型属性 resultType 声明查询返回的结果类型,这里为 tech.exchange.springboot.mybatis.model.MyRow,表示 MyBatis 会将查询到的字段值按数据表列名一一映射到 MyRow 实例的各个字段;如果数据表列名与 MyRow 的字段名称不一致,SELECT 语句可以使用别名(AS)重新定义列名:

          SELECT
            id,
            col1 AS col1,
            col2 AS col2
          FROM
            mytable
          WHERE
            id = #{id}
      

      接口方法

        /**
         * 查询一行记录
         *
         * @param id 记录ID
         * @return 记录
         */
        MyRow selectMyRow(int id);
      

      MyBatis 也是支持使用一个或多个基本类型变量传递参数的,注意名称要对应保持一致。

      查询一行记录

          int id = 1;
          // 查询记录
          MyRow row = mapper.selectMyRow(id);
      
          System.out.println(row.getId());
          System.out.println(row.getCol1());
          System.out.println(row.getCol2());
      

      可以看到,这里使用 insert 元素声明的返回类型 MyRow 的实例变量接收查询结果。

    2. 查询多行记录

      以查询数据表 mytable 的全部记录演示查询多行记录,也可以使用参数指定查询条件,参数使用方法如上方所示,不再赘述。

      XML查询元素

      <select id="selectMyRows" resultType="tech.exchange.springboot.mybatis.model.MyRow">
          SELECT
          *
          FROM
          mytable
        </select>
      

      查询多行记录,返回结果应该是一个类似 集合 的类型,但是 MyBatis 要求属性 resultType 声明的不是具体的集合类型(List),而是集合元素的类型,这里仍是 tech.exchange.springboot.mybatis.model.MyRow。

      接口方法

        /**
         * 查询多行记录
         *
         * @return 多行记录
         */
        List<MyRow> selectMyRows();
      

      查询多行记录

          List<MyRow> rows = mapper.selectMyRows();
      
          rows.forEach(System.out::println);
      

    Update

    修改数据表 mytable 指定主键ID的记录。

    XML修改元素

      <update id="updateMyRow">
        UPDATE
          mytable
        SET
          col1 = #{col1},
          col2 = #{col2}
        WHERE
          id = #{id}
      </update>
    

    接口方法

      /**
       * 修改记录
       *
       * @param row 一行记录
       * @return 影响行数
       */
      int updateMyRow(MyRow row);
    

    修改记录

        MyRow row = new MyRow();
        
        // 指定ID
        row.setId(1);
        
        // 指定新的字段值
        row.setCol1("c");
        row.setCol2("d");
    
        // 修改
        int value = mapper.updateMyRow(row);
        System.out.println(value);
    

    Delete

    删除数据表 mytable 指定主键ID的记录。

    XML删除元素

      <delete id="deleteMyRow">
        DELETE FROM
        mytable
        WHERE
        id = #{id}
      </delete>
    

    接口方法

      /**
       * 删除记录
       *
       * @param id 记录ID
       * @return 影响行数
       */
      int deleteMyRow(int id);
    

    删除记录

        int id = 1;
    
        int value = mapper.deleteMyRow(id);
        System.out.println(value);
    

    小结

    本文通过 SpringBoot 的命令行应用,演示 SpringBoot 和 MyBatis 的整体过程,以及实现基本 CRUD 的示例。

    整体实践下来,发现 MyBatis 的使用是有套路可循的,对于某一张数据表的读写操作:

    1. 创建一个或多个实体类,用于数据交互;
    2. 创建一个 MyBatis Mapper,用于封装数据方法,Mapper 由两部分组成:Interface(接口) + XML;
    3. Interface 中的每一个方法(Method)对应着 XML 中的一个元素(Element, insert/select/update/delete);
    4. MyBatis Mapper 方法的调用执行,本质就是 SQL 语句的执行。

    受限于篇幅,只能讨论 MyBatis 最基础的内容,帮助大家入门,详细内容请参考 MyTatis 官方文档

  • 相关阅读:
    选择排序
    插入排序
    冒泡排序
    java实现串的匹配和替换
    动态字符串的存储结构的实现
    静态串的存储结构以及增删改查
    链式队列的操作实现
    java中程序的执行的原理如怎么创建空间,创建对象等
    java中的环境变量的配置
    java的JVM以及java中代码的运行过程
  • 原文地址:https://www.cnblogs.com/yurunmiao/p/15643099.html
Copyright © 2020-2023  润新知