SpringBoot 2.x系列:整合Mybatis(上)

1,369 阅读7分钟

概览

上一节介绍了Spring Boot是如何访问数据库的,首先介绍JDBC以及如何通过原生的JDBC API访问数据库,我们通过Demo看到了原生JDBC使用起来非常的麻烦,需要我们自己维护JDBC的核心对象、事务的管理等,进而我们引入了Spring Boot提供的封装JdbcTemplate工具类,虽然JdbcTemplate通过模板设计模式简化了共性的代码逻辑,不过使用起来后还是有很多不方便的地方。比如:

  • SQL语句硬编码到代码修改SQL不方便
  • 对位符和参数设置映射也是硬编码,不利于后续的维护
  • 动态SQL语句拼装,既麻烦又很容易出错
  • ...

这篇文章我们引入一个全新的ORM框架—MyBatis,MyBatis的前身是Apache基金会中的iBatis项目,由于各种原因,2010年脱离Apache,并且更名为MyBatis,从此发展迅速,并且在2013年,MyBatis将源代码迁移到了GitHub。

关于MyBatis的使用,有几种方式可以组合使用,分别是:

  • MyBatis + XML
  • MyBatis + 注解
  • MyBatisPlus
  • TkMyBatis

前两年开发项目的时候,我们团队使用的MyBatis +XML组合,开发过程中每个表的CURD操作,都需要手写代码和SQL,为了减少了开发工作量,我们引入MyBatis Generator代码生成器,虽然减少了开发工作量,不过也造成了项目中很多无用的代码。

在做上个项目的时候,我把tkMyBatis引进了配合PageHelper插件,大大提升开发效率,同时也没引入无用的代码片段。

目前公司的这个项目用上了MyBatisPlus,相比tkMyBatis,MyBatisPlus提供了更多的实用的功能,比如支持物理删除、可以配置多数据源等

下面,我们上面列的四种方式,逐个来入门。

MyBatis+XML

实例代码对应的仓库地址:github.com/dragon8844/…

引入依赖

在pom.xml的文件中引入相关依赖:

<!-- 实现对数据库连接池的自动化配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 本示例,我们使用 MySQL -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- 实现对 MyBatis 的自动化配置 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

<!-- 方便用单元测试验证-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- lombok简化代码-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

添加配置

  • 应用配置

    在resources目录下创建应用的配置文件application.yml,添加如下配置内容:

    spring:
      # datasource 数据源配置内容
      datasource:
        url: jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: root
    
    # mybatis 配置内容
    mybatis:
      config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径
      mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址
      type-aliases-package: com.dragon.mybatisxml.entity # 配置数据库实体包路径
     
    

    这里指定了mybatis配置文件,在类根路径下mybatis-config.xml文件,同时也指定了存放SQL文件的路径,类根路径下的mapper目录

  • mybatis的配置

    在resources目录下创建mybatis-config.xml,并且添加如下配置:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <!-- 使用驼峰命名法转换字段。 -->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
        <typeAliases>
            <typeAlias alias="Integer" type="java.lang.Integer"/>
            <typeAlias alias="Long" type="java.lang.Long"/>
            <typeAlias alias="HashMap" type="java.util.HashMap"/>
            <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap"/>
            <typeAlias alias="ArrayList" type="java.util.ArrayList"/>
            <typeAlias alias="LinkedList" type="java.util.LinkedList"/>
        </typeAliases>
    </configuration>
    

    在config包下创建MyBatisConfig配置类,添加如下配置内容:

  /**
 * @author lilong
   */
  @MapperScan(basePackages = "com.dragon.mybatisxml.mapper")
  @Configuration
  public class MybatisConfig {
  }

这个配置类用来指定mapper接口的包路径

编写代码

  • 编写实体类

    @Data
    public class User {
        /**
         * 主键
         */
        private Integer id;
        /**
         * 用户名
         */
        private String username;
        /**
         * 密码
         */
        private String password;
        /**
         * 创建时间
         */
        private Date createTime;
    }
    

    实体类对应的DDL语句:

    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `username` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
      `password` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密码',
      `create_time` datetime DEFAULT NULL COMMENT '创建时间',
      `deleted` tinyint(1) DEFAULT NULL COMMENT '是否删除  0-未删除;1-删除',
      PRIMARY KEY (`id`),
      UNIQUE KEY `idx_username` (`username`)
    ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
    
  • 编写Mapper类

    com.dragon.mybatisxml.mapper包下创建接口UserMapper

    @Repository
    public interface UserMapper {
        /**
         * 新增
         *
         * @param user
         * @return
         */
        Integer insert(User user);
        /**
         * 根据ID查询
         *
         * @param id
         * @return
         */
        User selectById(Integer id);
        /**
         * 根据username查询
         *
         * @param username
         * @return
         */
        User selectByUsername(String username);
        /**
         * 根据ID更新
         *
         * @param user
         * @return
         */
        Integer updateById(User user);
        /**
         * 根据ID删除
         *
         * @param id
         * @return
         */
        Integer deleteById(Integer id);
    }
    

    在 resources/mapper 路径下,创建 UserMapper.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="com.dragon.mybatisxml.mapper.UserMapper">
        <sql id="FIELDS">
            id, username, password, create_time
        </sql>
        <insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
            INSERT INTO user (
            username, password, create_time
            ) VALUES (
            #{username}, #{password}, #{createTime}
            )
        </insert>
        <select id="selectById" parameterType="Integer" resultType="User">
            SELECT
            <include refid="FIELDS"/>
            FROM user
            WHERE id = #{id}
        </select>
        <select id="selectByUsername" parameterType="String" resultType="User">
            SELECT
            <include refid="FIELDS"/>
            FROM user
            WHERE username = #{username}
            LIMIT 1
        </select>
        <update id="updateById" parameterType="User">
            UPDATE user
            <set>
                <if test="username != null">
                    , username = #{username}
                </if>
                <if test="password != null">
                    , password = #{password}
                </if>
            </set>
            WHERE id = #{id}
        </update>
        <delete id="deleteById" parameterType="Integer">
            DELETE FROM user WHERE id = #{id}
        </delete>
    </mapper>
    
  • 对于绝大多数查询,我们是返回统一字段,所以可以使用 <sql /> 标签,定义 SQL 段。对于性能或者查询字段比较大的查询,按需要的字段查询。

  • 为了阅读,对于数据库的关键字,使用大写。例如说,SELECTWHERE 等等

单元测试

创建 UserMapperTest 测试类,我们来测试一下简单的 UserMapper 的每个操作。代码如下:

@SpringBootTest
@Slf4j
class UserMapperTest {
    @Resource
    private UserMapper userMapper;
    @Test
    void insert() {
        User user = new User();
        user.setUsername("张三");
        user.setPassword("123456");
        user.setCreateTime(new Date());
        Integer count = userMapper.insert(user);
        log.info("count:{}", count);
    }
    @Test
    void selectById() {
        User user = userMapper.selectById(8);
        log.info("user:{}", user.toString());
    }
    @Test
    void selectByUsername() {
        User user = userMapper.selectByUsername("张三");
        log.info("user:{}", user.toString());
    }
    @Test
    void updateById() {
        User user = new User();
        user.setUsername("李四");
        user.setPassword("111111");
        Integer count = userMapper.updateById(user);
        log.info("count:{}", count);
    }
    @Test
    void deleteById() {
        Integer count = userMapper.deleteById(8);
        log.info("count:{}", count);
    }
}

到此,基于MyBatis + XML入门的小例子就已经搞完了,配置不算复杂,不过单表通用SQL写起来比较麻烦,强烈推荐大家使用MyBatis Generator,自动生成单表的SQL。

MyBatis + 注解

MyBatis同时可以支持使用注解编写SQL,不过在实际项目开发过程,并不推荐大家这样做,一方面SQL在Java代码中很难排版,另外在编写复杂的SQL的时候,会造成Mapper接口很乱。总之虽然可以不用再写SQL的xml文件了,但是强烈不推荐大家使用,另外我也询问过一些朋友,大家也很少使用这种组合方式,不过鉴于内容,我还是写了一个Demo

实例代码对应的仓库地址:github.com/dragon8844/…

差异部分

  • application.yml

    application.yml配置文件中,可以移除 mapper-locations 配置项。

  • UserMapper.xml

    可以删除resources/mapper 路径下SQL的配置文件,同时需要使用注解把SQL声明到UserMapper的接口上。

编写代码

在com.dragon.mybatisannotation包下创建接口UserMapper,同时我们使用注解的方式将SQL声明到相应的方法上去,代码如下:

@Repository
public interface UserMapper {
    /**
     * 新增
     *
     * @param user
     * @return
     */
    @Insert("INSERT INTO user(username, password, create_time) VALUES(#{username}, #{password}, #{createTime})")
    @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
    Integer insert(User user);
    /**
     * 根据ID查询
     *
     * @param id
     * @return
     */
    @Select("SELECT username, password, create_time FROM user WHERE id = #{id}")
    User selectById(Integer id);
    /**
     * 根据username查询
     *
     * @param username
     * @return
     */
    @Select("SELECT username, password, create_time FROM user WHERE username = #{username}")
    User selectByUsername(String username);
    /**
     * 根据ID更新
     *
     * @param user
     * @return
     */
    @Update(value = {
            "<script>",
            "UPDATE user",
            "<set>",
            "<if test='username != null'>, username = #{username}</if>",
            "<if test='password != null'>, password = #{password}</if>",
            "</set>",
            "</script>"
    })
    Integer updateById(User user);
    /**
     * 根据ID删除
     *
     * @param id
     * @return
     */
    @Insert("DELETE FROM user WHERE id = #{id}")
    Integer deleteById(Integer id);
}

单表的CRUD,使用注解的方式还算可以,如何要是很复杂的SQL,那看起来就很乱。特别是在处理动态SQL的时候,需要引入script标签来处理,在编写和阅读的时候都十分的费劲。

单元测试

走一个单元测试,代码如下:

@Slf4j
@SpringBootTest
class UserMapperTest {

    @Resource
    private UserMapper userMapper;

    @Test
    void insert() {
        User user = new User();
        user.setUsername("张三");
        user.setPassword("123456");
        user.setCreateTime(new Date());
        Integer count = userMapper.insert(user);
        log.info("count:{}", count);
    }

    @Test
    void selectById() {
        User user = userMapper.selectById(12);
        log.info("user:{}", user.toString());
    }

    @Test
    void selectByUsername() {
        User user = userMapper.selectByUsername("张三");
        log.info("user:{}", user.toString());
    }

    @Test
    void updateById() {
        User user = new User();
        user.setUsername("李四");
        user.setPassword("111111");
        Integer count = userMapper.updateById(user);
        log.info("count:{}", count);
    }

    @Test
    void deleteById() {
        Integer count = userMapper.deleteById(12);
        log.info("count:{}", count);
    }

}

小结

MyBatis的这4种使用方式全部介绍完的话内容比较多,所以本篇先介绍两种方式,即MyBatis+XML和MyBatis+注解,通过这两种方式我们已经对MyBatis的基本使用有了一个大体的了解。不过在使用过程中,对应单表的操作我们还是会用上代码生成器,代码生成器会产生很多冗余代码,下一篇我们将介绍MyBatisPlus和TkMyBatis,这两个框架在MyBatis的基础上做了一定封装和扩展,减少冗余的代码,同时能够极大的提升我们的开发效率,我们下篇见~

最后说一句

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。

此外,关注公众号:黑色的灯塔,专注Java后端技术分享,涵盖Spring,Spring Boot,SpringCloud,Docker,Kubernetes中间件等技术。