SpringBoot学习笔记八:分页插件、通用Mapper及MBG的集成

插件简介

PageHelper 与通用 Mapper 作者为同一人 abel533 ,使用这两个插件可以极大地简化 MyBatis 的开发,如果你想深入了解Mybatis以及插件开发可以购买作者的书籍。

MyBatis从入门到精通

分页插件 PageHelper

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

如果你也在用 MyBatis,建议尝试该分页插件,这一定是最方便使用的分页插件

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

通用Mapper

GitHub地址:https://github.com/abel533/Mapper/
文档地址:https://github.com/abel533/Mapper/wiki
gitee地址:https://gitee.com/free/Mapper

通用Mapper都可以极大的方便开发人员。可以随意的按照自己的需要选择通用方法,还可以很方便的开发自己的通用方法。

极其方便的使用MyBatis单表的增删改查。

支持单表操作,不支持通用的多表联合查询。

添加依赖

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

<!-- MyBatis与Spring Boot整合starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

<!-- MyBatis 通用Mapper -->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.0.3</version>
</dependency>

<!-- MyBatis 分页插件pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.5</version>
</dependency>

<!-- mysql 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- devtools -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

相关配置

spring:
  application:
    name: spring-boot-mbg
  datasource:
    url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
    username: root
    password: mysql123
    driver-class-name: com.mysql.jdbc.Driver
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml       # mybatis配置文件位置
  mapper-locations: classpath:mybatis/mappers/*.xml           # mapper映射文件位置
  type-aliases-package: com.example.springbootmybatis.entity  # 别名包

mapper:
  mappers:
    - com.example.springbootmbg.common.MyBaseMapper
  identity: MYSQL # 取回主键的方式 MYSQL: SELECT LAST_INSERT_ID()
  order: AFTER    # 还可设置为 mapper.before = false
  not-empty: true # insertSelective 和 updateByPrimaryKeySelective 中,是否判断字符串类型 !=''
  style: camelhumpAndLowercase # 实体和表转换时的默认规则:驼峰转下划线小写形式
  enableMethodAnnotation: true # 是否支持(getter 和 setter)在方法上使用注解,默认false
  enumAsSimpleType: true # 枚举类型当成基本类型对待

pagehelper:
  helper-dialect: mysql
  reasonable: false
  support-methods-arguments: true
  params: count=countSql

# mybatis sql日志
logging:
  level:
    com:
      example:
        springbootmbg:
          mapper: debug

属性解释

通用Mapper

通用Mapper 属性配置上面的注释已经很清楚了,这里不再做过多解释,唯一一点是 mappers 配置的是自定义的 MyBaseMapper 接口,具体下面介绍。
更多 通用Mapper 属性配置可参考官方文档 3.配置介绍部分

PageHelper

helperDialect:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。 你可以配置 helperDialect 属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:
oracle , mysql , mariadb , sqlite , hsqldb , postgresql , db2 , sqlserver , informix , h2 , sqlserver2012 , derby
特别注意:使用 SqlServer2012 数据库时,需要手动指定为 sqlserver2012,否则会使用 SqlServer2005 的方式进行分页。

reasonable:分页合理化参数,默认值为 false ,直接根据参数进行查询 。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。

supportMethodsArguments:支持通过 Mapper 接口参数来传递分页参数,默认值 false ,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。 使用方法可以参考测试代码中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTestArgumentsObjTest

params:为了支持 startPage(Object params) 方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum , pageSize , count , pageSizeZero , reasonable ,不配置映射的用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero

更多 PageHelper 属性配置可参考官方使用方法部分

MyBatis sql日志

对于 MyBatis sql 日志可以像上面那样在 application.yml 文件中配置,但在项目中往往使用 logback 日志框架,因此可以在 logback-spring.xml 配置文件中添加如下 logger

<logger name="com.example.springbootmbg.mapper" level="DEBUG" additivity="false"></logger>

MyBatis配置文件 mybatis-config.xml

src/main/resource/mybatis/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>
    <!--<typeAliases>
        <package name="com.example.springbootmbg.entity"/>
    </typeAliases>-->
</configuration>

Spring DevTools 配置

在使用 DevTools 时,通用Mapper 经常会出现 class x.x.A cannot be cast to x.x.A。

同一个类如果使用了不同的类加载器,就会产生这样的错误,所以解决方案就是让 通用Mapper 和实体类使用相同的类加载器即可。

DevTools 默认会对 IDE 中引入的所有项目使用 restart 类加载器,对于引入的 jar 包使用 base 类加载器,因此只要保证 通用Mapperjar 包使用 restart 类加载器 即可。

src/main/resources 中创建 META-INF 目录,在此目录下添加 spring-devtools.properties 配置,内容如下:

restart.include.mapper=/mapper-[\\w-\\.]+jar
restart.include.pagehelper=/pagehelper-[\\w-\\.]+jar

使用这个配置后,就会使用 restart 类加载器 加载 include 进去的 jar 包。

以上 Spring DevTools 配置 内容引自官方Spring 集成示例——集成Spring Boot部分

通用Mapper MBG代码生成

Maven集成MBG插件

pom.xml

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.6</version>
    <configuration>
        <configurationFile>
            ${basedir}/src/main/resources/generator/generatorConfig.xml
        </configurationFile>
        <overwrite>true</overwrite>
        <verbose>true</verbose>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>4.0.0</version>
        </dependency>
    </dependencies>
</plugin>

MBG配置文件

详细配置参数可参考http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md官方文档 4.1专用代码生成器部分在此不作过多解释

src/main/resources/generator/generatorConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>

    <properties resource="application.yml"/>

    <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">

        <property name="autoDelimitKeywords" value="true"/>
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="com.example.springbootmbg.common.MyBaseMapper"/>
        </plugin>

        <commentGenerator>
            <!-- suppressAllComments:阻止生成注释,默认为false -->
            <property name="suppressAllComments" value="false"/>
            <!-- suppressDate:阻止生成的注释包含时间戳,默认为false -->
            <property name="suppressDate" value="true"/>
        </commentGenerator>

        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/test_db"
                        userId="root"
                        password="mysql123">
        </jdbcConnection>

        <!-- 对应生成的pojo所在包 -->
        <javaModelGenerator targetPackage="com.example.springbootmbg.entity" targetProject="src/main/java"/>

        <!-- 对应生成的mapper所在目录 -->
        <sqlMapGenerator targetPackage="mybatis/mappers" targetProject="src/main/resources"/>

        <!-- 对应生成的mapper接口所在包 -->
        <javaClientGenerator targetPackage="com.example.springbootmbg.mapper" targetProject="src/main/java"
                             type="XMLMAPPER"/>

        <table tableName="tb_user" domainObjectName="User">
            <!--mysql 配置-->
            <!--<generatedKey column="id" sqlStatement="Mysql" />-->
            <!--oracle 配置-->
            <!--<generatedKey column="id" sqlStatement="select SEQ_{1}.nextval from dual" identity="false" type="pre"/>-->
        </table>

    </context>
</generatorConfiguration>

<table> 标签需要根据数据库表自行增添

com.example.springbootmbg.common.MyBaseMapper

package com.example.springbootmbg.common;

import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;

public interface MyBaseMapper<T> extends Mapper<T>, MySqlMapper<T> {
    //TODO
    //FIXME 特别注意,该接口不能被扫描到,否则会出错
}

很简单的一个接口,继承了 通用Mapper 提供的 tk.mybatis.mapper.common.Mappertk.mybatis.mapper.common.MySqlMapper 接口,MBG 生成的 Mapper 接口都会继承这个接口,从而间接继承前面提到的两个接口,从而获得 通用Mapper 的魔力(其实就是获得了一系列 通用Mapper 提供的单表增、删、改、查方法而已),具体方法可以查看那两个接口。

扫描Mapper接口所在的包

需要在启动类上添加 @MapperScan 注解,但请注意这个注解不是 MyBatis 提供的 org.mybatis.spring.annotation.MapperScan 而是 通用Mapper 提供的 tk.mybatis.spring.annotation.MapperScan

package com.example.springbootmbg;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@MapperScan(basePackages = {"com.example.springbootmbg.mapper"})
public class SpringBootMbgApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootMbgApplication.class, args);
    }

}

注意:这里有个坑就是这个自定义 MyBaseMapper 接口不能被扫描到,否则会报错,因此上面将该接口放在了别的包下。

不嫌麻烦的话可以去 MBG 生成的每个 Mapper 接口上添加 @Mapper 注解

使用MBG生成实体类、Mapper接口及xml映射文件

在项目 Maven 插件中找到 mybatis-generator 插件,点击 mybatis-generator:generate 一下就可以了,如果数据库连接信息配置正确的话,不出意外就会顺利在配置的目录下生成对应的实体类、Mapper接口及xml映射文件啦。

MBG使用

为示例这里创建一张用户表

CREATE TABLE `tb_user` (
  `id` varchar(64) NOT NULL COMMENT '用户id',
  `username` varchar(64) NOT NULL COMMENT '用户名',
  `password` varchar(64) NOT NULL COMMENT '用户密码',
  `face_icon` varchar(255) DEFAULT NULL COMMENT '用户头像',
  `nickname` varchar(64) NOT NULL COMMENT '用户昵称',
  `fans_counts` int(11) DEFAULT '0' COMMENT '用户粉丝数',
  `follow_counts` int(11) DEFAULT '0' COMMENT '关注其他用户数',
  `receive_like_counts` int(11) DEFAULT '0' COMMENT '收到的喜欢数目',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';

生成的代码大致像下面这样

com.example.springbootmbg.entity.user

package com.example.springbootmbg.entity;

import java.util.Date;
import javax.persistence.*;

@Table(name = "tb_user")
public class User {
    /**
     * 用户id
     */
    @Id
    private String id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 用户密码
     */
    private String password;

    /**
     * 用户头像
     */
    @Column(name = "face_icon")
    private String faceIcon;

    /**
     * 用户昵称
     */
    private String nickname;

    /**
     * 用户粉丝数
     */
    @Column(name = "fans_counts")
    private Integer fansCounts;

    /**
     * 关注其他用户数
     */
    @Column(name = "follow_counts")
    private Integer followCounts;

    /**
     * 收到的喜欢数目
     */
    @Column(name = "receive_like_counts")
    private Integer receiveLikeCounts;

    /**
     * 创建时间
     */
    @Column(name = "create_time")
    private Date createTime;

    /**
     * 更新时间
     */
    @Column(name = "update_time")
    private Date updateTime;

    // setters and getters
}

通用Mapper 会用到 Jpa 的几个简单注解,这里不做解释

com.example.springbootmbg.mapper.UserMapper

package com.example.springbootmbg.mapper;

import com.example.springbootmbg.common.MyBaseMapper;
import com.example.springbootmbg.entity.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper extends MyBaseMapper<User> {
}

生成的代码不带 @Repository 注解

src/main/resource/mybatis/mappers/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.example.springbootmbg.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="com.example.springbootmbg.entity.User">
    <!--
      WARNING - @mbg.generated
    -->
    <id column="id" jdbcType="VARCHAR" property="id" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="password" jdbcType="VARCHAR" property="password" />
    <result column="face_icon" jdbcType="VARCHAR" property="faceIcon" />
    <result column="nickname" jdbcType="VARCHAR" property="nickname" />
    <result column="fans_counts" jdbcType="INTEGER" property="fansCounts" />
    <result column="follow_counts" jdbcType="INTEGER" property="followCounts" />
    <result column="receive_like_counts" jdbcType="INTEGER" property="receiveLikeCounts" />
    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
  </resultMap>
</mapper>

测试

com.example.springbootmbg.mapper.UserMapperTest

package com.example.springbootmbg.mapper;

import com.example.springbootmbg.entity.User;
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.Date;
import java.util.List;
import java.util.UUID;

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

    @Autowired
    private UserMapper userMapper;

    private static final Logger logger = LoggerFactory.getLogger(UserMapperTest.class);

    @Test
    public void testInsert() {
        User user = new User();
        user.setId(UUID.randomUUID().toString().replaceAll("-", ""));
        user.setUsername("aaa");
        user.setPassword("123456");
        user.setNickname("aaa");
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        userMapper.insertSelective(user);

        User user2 = new User();
        user2.setId(UUID.randomUUID().toString().replaceAll("-", ""));
        user2.setUsername("bbb");
        user2.setPassword("123456");
        user2.setNickname("bbb");
        user2.setCreateTime(new Date());
        user2.setUpdateTime(new Date());
        userMapper.insertSelective(user2);
    }

    @Test
    public void testPage() {
        PageHelper.startPage(1, 10);
        List<User> userList = userMapper.selectAll();
        PageInfo<User> pageInfo = new PageInfo<>(userList);
        logger.info("pageInfo: {}", pageInfo);
    }

}
测试结果

推荐阅读更多精彩内容