springboot集成MyBatis

1. 前言

MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。相对于Hibernate和ApacheOJB等“一站式”ORM解决方案而言,MyBatis是一款“半自动化”的ORM实现。

  • 本文较为简略,主要是入门参考,深入学习请参考Mapper、PageHelper插件作者刘增辉书籍《MyBatis从入门到精通》

2. MyBatis简介

2.1 三款框架的ORM对比图

以下针对Spring JDBCSpring Data JpaMybatis**三款框架做了个粗略的对比。一般应用的性能瓶颈并不是在于ORM,所以这三个框架技术选型应该考虑项目的场景团队的技能掌握情况开发周期(开发效率)

框架对比 Spring JDBC Spring Data Jpa Mybatis
性能 性能最好 性能最差 居中
代码量
学习成本 居中
推荐指数 ❤❤❤ ❤❤❤❤❤ ❤❤❤❤❤
  • 个人观点:
    抛开学习成本而言,相对于中小型企业来说jpa开发无异于是最快速的,但鉴于国内市场环境而言,学习MyBatis是最佳选择。低学习成本和动态SQL解耦的特点使得更容易被人们所接受。对于业务复杂且对性能要求较高的项目来说Mybatis往往能更好的胜任,可以自己进行SQL优化,同时更让我喜欢的是Mybatis分页插件与通用Mapper(单表CURD无需自己手写)有了这两款插件的支持,怎么能不选择MyBatis?

2.2 MyBatis

作为一款使用广泛的开源软件,它的特点有哪些呢?

优点

  • SQL 被统一提取出来,便于统一管理和优化
  • SQL 和代码解耦,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰、更易维护、更易单元测试
  • 提供映射标签,支持对象与数据库的 ORM 字段关系映射
  • 提供对象关系映射标签,支持对象关系组件维护
  • 灵活书写动态 SQL,支持各种条件来动态生成不同的 SQL

缺点

  • 编写 SQL 语句时工作量很大,尤其是字段多、关联表多时,更是如此
  • SQL 语句依赖于数据库,导致数据库移植性差

springboot集成MyBatis_第1张图片

MyBatis 几个重要的概念

Mapper 配置可以使用基于 XML 的 Mapper 配置文件来实现,也可以使用基于 Java 注解的 MyBatis 注解来实现,甚至可以直接使用 MyBatis 提供的 API 来实现。

Mapper 接口是指自行定义的一个数据操作接口,类似于通常所说的 DAO 接口。早期的 Mapper 接口需要自定义去实现,现在 MyBatis 会自动为 Mapper 接口创建动态代理对象。Mapper 接口的方法通常与 Mapper 配置文件中的 select、insert、update、delete 等 XML 结点存在一一对应关系。

Executor,MyBatis 中所有的 Mapper 语句的执行都是通过 Executor 进行的,Executor 是 MyBatis 的一个核心接口。

SqlSession,是 MyBatis 的关键对象,是执行持久化操作的独享,类似于 JDBC 中的 Connection,SqlSession 对象完全包含以数据库为背景的所有执行 SQL 操作的方法,它的底层封装了 JDBC 连接,可以用 SqlSession 实例来直接执行被映射的 SQL 语句。

SqlSessionFactory,是 MyBatis 的关键对象,它是单个数据库映射关系经过编译后的内存镜像。SqlSessionFactory 对象的实例可以通过SqlSessionFactoryBuilder 对象类获得,而SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出。

MyBatis 的工作流程如下:

springboot集成MyBatis_第2张图片

  • 首先加载 Mapper 配置的 SQL 映射文件,或者是注解的相关 SQL 内容。
  • 创建会话工厂,MyBatis 通过读取配置文件的信息来构造出会话工厂(SqlSessionFactory)。
  • 创建会话。根据会话工厂,MyBatis 就可以通过它来创建会话对象(SqlSession),会话对象是一个接口,该接口中包含了对数据库操作的增、删、改、查方法。
  • 创建执行器。因为会话对象本身不能直接操作数据库,所以它使用了一个叫做数据库执行器(Executor)的接口来帮它执行操作。
  • 封装 SQL 对象。在这一步,执行器将待处理的 SQL 信息封装到一个对象中(MappedStatement),该对象包括 SQL 语句、输入参数映射信息(Java 简单类型、HashMap 或 POJO)和输出结果映射信息(Java 简单类型、HashMap 或 POJO)。
  • 操作数据库。拥有了执行器和 SQL 信息封装对象就使用它们访问数据库了,最后再返回操作结果,结束流程。

在我们具体的使用过程中,就是按照上述的流程来执行。

3. springboot集成MyBatis

导入依赖

在pom.xml文件中添加MyBatis 依赖 MyBatis-Spring-Boot-Starter

<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.0.0version>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

注: 在IDEA新建项目时,可以直接勾选上MyBatis和MySQL,IDEA会自动导入依赖,但是在MySQL依赖中记得去除runtime,这个会导致你的MySQL驱动无法导入com.mysql.cj.jdbc.Driver报错。

mybatis-spring-boot-starter 是 MyBatis 帮助我们快速集成 Spring Boot 提供的一个组件包,使用这个组件可以做到以下几点:

  • 构建独立的应用
  • 几乎可以零配置
  • 需要很少的 XML 配置

初始化脚本(创建表)

CREATE TABLE `t_user` (
  `id` int(8) NOT NULL AUTO_INCREMENT COMMENT '主键自增',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(50) NOT NULL COMMENT '密码',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

application 配置

application.yml 添加相关配置:

#数据库配置 spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT&useSSL=true
    username: root
    password: rootroot
    driver-class-name: com.mysql.cj.jdbc.Driver   mybatis: 
  mapper-locations: classpath:com/demo/mapper/*.xml
  type-aliases-package: com.demo.entity
# 驼峰命名规范 如:数据库字段是  order_id 那么 实体字段就要写成 orderId
  configuration.map-underscore-to-camel-case: true

其中:

  • mybatis.config-location,配置 mybatis-config.xml 路径,mybatis-config.xml 中配置 MyBatis 基础属性;
  • mybatis.mapper-locations,配置 Mapper 对应的 XML 文件路径;
  • mybatis.type-aliases-package,配置项目中实体类包路径;
  • spring.datasource.*,数据源配置。
  • application.properties 配置文件改成yml时,一定要把原来的properties 文件删掉再新建yml文件不然启动类会产生扫描找不到而报错。
  • 上述项目目录如下图:
    springboot集成MyBatis_第3张图片

创建实体类

package com.demo.entity;

import java.io.Serializable;

/**
 * @Author: MaoLin
 * @Date: 2019/2/8 16:33
 * @Version 1.0
 */
public class User implements Serializable {

    private static final long serialVersionUID = 8655851615465363473L;

    private Long id;
    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

持久层

MyBatis 以前只有 XML 配置这种使用的形式,到了后来注解使用特别广泛,MyBatis 也顺应潮流提供了注解的支持,从这里可以看出 MyBatis 一直都跟随着主流技术的变化来完善自己。接下来给大家介绍一下两种方法的使用。

UserMapper

package com.demo.mapper;

import com.demo.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * @Author: MaoLin
 * @Date: 2019/2/8 16:35
 * @Version 1.0
 */
@Mapper
public interface UserMapper {

    /**
     * 注解方法
     * 根据用户名查询用户结果集
     * @param username 用户名
     * @return 查询结果
     */
    @Select("SELECT * FROM t_user WHERE username = #{username}")
    List<User> findByUsername(@Param("username") String username);


    /**
     * xml配置方法
     * 保存用户信息
     * @param user 用户信息
     * @return 成功 1 失败 0
     */
     
    int insert(User user);
}

UserMapper 映射文件





  
    INSERT INTO `t_user`(`username`,`password`) VALUES (#{username},#{password})
  


注:其中Select查询为注解的方法,不需要使用映射文件,直接注解就能使用,insert是采用的xml方法,SQL语句需要在映射文件中写,具体操作的使用,请参考mybatis 官方文档。

测试

junit测试类来检验代码的正确性。直接在IDEA项目的test测试文件里面操作即可。

package com.demo;

import com.demo.entity.User;
import com.demo.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

    private static final Logger log = LoggerFactory.getLogger(DemoApplicationTests.class);

    @Autowired
    private UserMapper userMapper;

    @Test
    public void test1() throws Exception {
        final int row1 = userMapper.insert(new User("u1", "p1"));
        log.info("[添加结果] - [{}]", row1);
        final int row2 = userMapper.insert(new User("u2","p2"));
        log.info("[添加结果] - [{}]", row2);
        final int row3 = userMapper.insert(new User("u1", "p3"));
        log.info("[添加结果] - [{}]", row3);
        final List<User> u1 = userMapper.findByUsername("u1");
        log.info("[根据用户名查询] - [{}]", u1);
    }
}

4. 通用Mapper与分页插件的集成

插件介绍

这两款插件作者均是同一个人,如果你想深入了解Mybatis以及插件开发可以购买作者的书籍

springboot集成MyBatis_第4张图片

分页插件

  • GIT地址: https://github.com/pagehelper/Mybatis-PageHelper

在没有分页插件之前,写一个分页需要两条SQL语句,一条查询一条统计,然后才能计算出页码,这样的代码冗余而又枯燥,更重要的一点是**数据库迁移**,众所周知不同的数据库分页写法是不同的,而Mybatis不同于Hibernate的是它只提供动态SQL和结果集映射。值得庆幸的是,它虽然没有为分页提供良好的解决方案,但却提供了Interceptor以供开发者自己扩展,这也是这款分页插件的由来….

通用Mapper

  • GIT地址:https://github.com/abel533/Mapper

通用 Mapper 是一个可以实现任意 MyBatis 通用方法的框架,项目提供了常规的增删改查操作以及 Example 相关的单表操作。通用 Mapper 是为了解决 MyBatis 使用中 90% 的基本操作,使用它可以很方便的进行开发,可以节省开发人员大量的时间。

导入依赖

你只需要添加通用 Mapper 提供的 starter 就完成了最基本的集成,依赖如下:


  tk.mybatis
  mapper-spring-boot-starter
  版本号

此时最新版本号2.1.5,你也可以从官方地址查看:

http://mvnrepository.com/artifact/tk.mybatis/mapper-spring-boot-starter

注意:引入该 starter 时,和 MyBatis 官方的 starter 没有冲突,但是官方的自动配置不会生效!

application 配置

# 通用mapper配置
mapper:
  mappers: tk.mybatis.mapper.common.Mapper
  not-empty: false
  # 主键自增回写,默认为 MYSQL
  identity: MYSQL
# 分页插件配置
pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  params: count=countSql
  • 参数解释及其他参数
    参考官方文档:https://github.com/abel533/Mapper/wiki/3.config

实体类

通用Mapper采用了JPA规范包中的注解,这种的设计避免了重复造轮子,更是让Spring Data Jpa的应用可以轻松切换到Mybatis

和上面实体大体一致,只是增加了注解。

package com.demo.entity;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;

/**
 * @Author: MaoLin
 * @Date: 2019/2/8 16:33
 * @Version 1.0
 */
@Table(name = "t_user")
public class User implements Serializable {

    private static final long serialVersionUID = 8655851615465363473L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

持久层

UserMapper

  • 继承 BaseMapper 就可以了,这点是不是有点类似 JpaRepository,同时也可以根据自己需要扩展出更适合自己项目的BaseMapper,它的灵活也是众多开发者喜爱的因素之一
package com.demo.mapper;

import com.demo.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import tk.mybatis.mapper.common.BaseMapper;

import java.util.List;

/**
 * @Author: MaoLin
 * @Date: 2019/2/8 16:35
 * @Version 1.0
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {

    /**
     * 根据用户名查询用户结果集
     *
     * @param username 用户名
     * @return 查询结果
     */
    @Select("SELECT * FROM t_user WHERE username = #{username}")
    List<User> findByUsername(@Param("username") String username);


    /**
     * 保存用户信息
     *
     * @param user 用户信息
     * @return 成功 1 失败 0
     */

    @Insert("INSERT INTO `t_user`(`username`,`password`) VALUES (#{username},#{password})")
    int insert(User user);

    /**
     * 根据用户名统计(TODO 假设它是一个很复杂的SQL)
     *
     * @param username 用户名
     * @return 统计结果
     */
    int countByUsername(String username);
}

UserMapper 映射文件





    

测试

package com.demo;

import com.demo.entity.User;
import com.demo.mapper.UserMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

    private static final Logger log = LoggerFactory.getLogger(DemoApplicationTests.class);

    @Autowired
    private UserMapper userMapper;

    @Test
    public void test1() throws Exception {
        final int row1 = userMapper.insert(new User("u1", "p1"));
        log.info("[添加结果] - [{}]", row1);
        final int row2 = userMapper.insert(new User("u2","p2"));
        log.info("[添加结果] - [{}]", row2);
        final int row3 = userMapper.insert(new User("u1", "p3"));
        log.info("[添加结果] - [{}]", row3);
        final List<User> u1 = userMapper.findByUsername("u1");
        log.info("[根据用户名查询] - [{}]", u1);

        final User user1 = new User("u1", "p1");
        final User user2 = new User("u1", "p2");
        final User user3 = new User("u3", "p3");
        userMapper.insertSelective(user1);
        log.info("[user1回写主键] - [{}]", user1.getId());
        userMapper.insertSelective(user2);
        log.info("[user2回写主键] - [{}]", user2.getId());
        userMapper.insertSelective(user3);
        log.info("[user3回写主键] - [{}]", user3.getId());
        final int count = userMapper.countByUsername("u1");
        log.info("[调用自己写的SQL] - [{}]", count);

        // TODO 模拟分页
        for (int i = 0;i<10;i++){
            userMapper.insertSelective(new User("u"+i, "p"+i));
        }

        // TODO 分页 + 排序 this.userMapper.selectAll() 这一句就是我们需要写的查询,有了这两款插件无缝切换各种数据库
        final PageInfo<Object> pageInfo = PageHelper.startPage(1, 10).setOrderBy("id desc").doSelectPageInfo(() -> this.userMapper.selectAll());
        log.info("[lambda写法] - [分页信息] - [{}]", pageInfo.toString());

        PageHelper.startPage(1, 10).setOrderBy("id desc");
        final PageInfo<User> userPageInfo = new PageInfo<>(this.userMapper.selectAll());
        log.info("[普通写法] - [{}]", userPageInfo);
    }
}

结果

控制台结果:

springboot集成MyBatis_第5张图片

5. 小结&参考资料

小结

要多查官方文档,一直在更新,你在使用的时候,可能有点已经不能再继续使用了,所以要多看官方文档。
本篇博客也只是简单入门,如果要深入研究,可以看一下作者的书籍。
源码GIT地址:https://github.com/chapaofan/MyBatis

参考资料

  • 一起来学SpringBoot | 第七篇:整合Mybatis
  • springboot系列文章之整合mybatis
  • mybatis 官方文档
  • MyBatis 通用 Mapper4
  • 《Spring Boot 开发实战》机械工业出版社
  • 《MyBatis从入门到精通》

你可能感兴趣的:(Java)