Bootstrap

微服务学习系列六:MyBatis-Plus用法

系列文章目录


目录

系列文章目录

前言

一、环境准备

一、为什么要使用utf8mb4字符集

 二、依赖准备

 三、配置准备

二、使用步骤

@TableName

@TableId 主键注解

IdType

@TableField

实体类UserInfoDO

Mapper层CRUD

Service层CRUD 

分页

多数据源 

 @DS注解

 动态表名插件DynamicTableNameInnerInterceptor

总结


前言

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

一、环境准备

本文基于Spring Boot、maven、jdk1.8、mySQL开发,所以开始前我们需要准备好这套环境。

mySQL 数据表 我采用utf8mb4这种字符集,做过微信的同学应该会知道,微信用户名称的表情,是需要这种字符集才能存储的。

一、为什么要使用utf8mb4字符集

低版本的MySQL支持的utf8编码,最大字符长度为 3 字节,如果遇到 4 字节的字符就会出现错误了。三个字节的 UTF-8 最大能编码的 Unicode 字符是 0xFFFF,也就是 Unicode 中的基本多文平面(BMP)。也就是说,任何不在基本多文平面的 Unicode字符,都无法使用MySQL原有的 utf8 字符集存储。这些不在BMP中的字符包括哪些呢?最常见的就是Emoji 表情(Emoji 是一种特殊的 Unicode 编码,常见于 ios 和 android 手机上)和一些不常用的汉字,以及任何新增的 Unicode 字符等等。
那么utf8mb4比utf8多了什么的呢?
✔ 多了emoji编码支持
如果实际用途上来看,可以给要用到emoji的库或者说表,设置utf8mb4,比如评论要支持emoji可以用到。

一 问题描述

插入sql时,Mybatis报错插入的值为错误的,发现数据中存在表情符号,其编码为四字节,默认的utf8编码为三字节。

二 修改MySQL编码

修改表编码

ALTER TABLE `table` DEFAULT CHARACTER SET utf8mb4;
修改字段编码

ALTER TABLE `tablename` CHANGE `字段名1` `字段名2` `类型` CHARACTER SET utf8mb4;
再次启动发现依旧报同样的错误。

三 修改依赖

版本要高,如果版本过低还会出现其问题。

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

 二、依赖准备

         <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

         <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>

         <!-- mybatis-plus 多数据源 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.5.0</version>
        </dependency>

 三、配置准备

Spring Boot启动类。配置@MapperScan注解,用于扫描Mapper文件位置:

/**
 * 用户微服务启动类
 *
 * @author yangyanping
 * @date 2022-02-28
 */
@SpringBootApplication
@EnableTransactionManagement
@ComponentScan("com.yyp.user")
@MapperScan("com.yyp.user.infra.gatewayImpl.database")
public class WebApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebApplication.class);
    }
}

二、使用步骤

mybatis-plus为使用者封装了很多的注解,方便我们使用,我们首先看下实体类中有哪些注解。

@TableName

表名注解,用于标识实体类对应的表,使用位置:实体类。

属性类型必须指定默认值描述
valueString""表名
schemaString""schema
keepGlobalPrefixbooleanfalse是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时)
resultMapString""xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定)
autoResultMapbooleanfalse是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入)
excludePropertyString[]{}需要排除的属性名 @since 3.3.1

@TableId 主键注解

属性性类型必须指定默认值描述
valueString""主键字段名
typeEnumIdType.NONE指定主键类型

IdType

描述
AUTO数据库 ID 自增
NONE无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUTinsert 前自行 set 主键值
ASSIGN_ID分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法)
ID_WORKER分布式全局唯一 ID 长整型类型(please use ASSIGN_ID)
UUID32 位 UUID 字符串(please use ASSIGN_UUID)
ID_WORKER_STR分布式全局唯一 ID 字符串类型(please use ASSIGN_ID)

@TableField

属性类型必须指定默认值描述
valueString""数据库字段名
existbooleantrue是否为数据库表字段
conditionString""字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s}参考(opens new window)
updateString""字段 update set 部分注入,例如:当在version字段上注解update="%s+1" 表示更新时会 set version=version+1 (该属性优先级高于 el 属性)
insertStrategyEnumFieldStrategy.DEFAULT举例:NOT_NULL
insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)
updateStrategyEnumFieldStrategy.DEFAULT举例:IGNORED
update table_a set column=#{columnProperty}
whereStrategyEnumFieldStrategy.DEFAULT举例:NOT_EMPTY
where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
fillEnumFieldFill.DEFAULT字段自动填充策略
selectbooleantrue是否进行 select 查询
keepGlobalFormatbooleanfalse是否保持使用全局的 format 进行处理
jdbcTypeJdbcTypeJdbcType.UNDEFINEDJDBC 类型 (该默认值不代表会按照该值生效)
typeHandlerClass<? extends TypeHandler>UnknownTypeHandler.class类型处理器 (该默认值不代表会按照该值生效)
numericScaleString""指定小数点后保留的位数

实体类UserInfoDO

/**
 * 用户信息表
 *
 * @author yangyanping
 * @date 2022-11-17
 */
@Getter
@Setter
@TableName("user_info")
public class UserInfoDO {

    /**
     * 主键ID,递增
     */
    @TableId(type = IdType.AUTO)
    private Integer id;

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

    /**
     * 用户状态(0启用,1停用,2 注销中)
     */
    private Integer status;

    /**
     * 用户手机号
     */
    private String mobile;

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

Mapper层CRUD

代码如下(示例):

/**
 * 用户信息表
 *
 * @author yangyanping
 * @date 2023-03-18
 */
@DS("user")
public interface UserInfoMapper extends BaseMapper<UserInfoDO> {
}

Service层CRUD 

/**
 * 用户服务接口
 *
 * @author yangyanping
 * @date 2023-03-18
 */
public interface UserInfoService extends IService<UserInfoDO> {
}

Service 实现层 

/**
 * 用户服务实现类
 *
 * @author yangyanping
 * @date 2023-03-18
 */
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfoDO> implements UserInfoService {
}

分页

分页插件 PaginationInnerInterceptor,使用分页话需要增加分页插件的配置。

Page

该类继承了 IPage 类,实现了 简单分页模型 如果你要实现自己的分页模型可以继承 Page 类或者实现 IPage 类

/**
 * MyBatis Plus 配置类
 *
 * @author yangyanping
 * @date 2023-03-18
 */
public class MyBatisPlusConfig {
    /**
     * 插件集合
     */
    @Bean
    @ConditionalOnMissingBean(MybatisPlusInterceptor.class)
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        //  插件
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MARIADB);
        // 添加分页插件
        interceptor.addInnerInterceptor(paginationInnerInterceptor);

        return interceptor;
    }
}

如果返回类型是 IPage 则入参的 IPage 不能为null,因为 返回的IPage == 入参的IPage; 如果想临时不分页,可以在初始化IPage时size参数传 <0 的值;
如果返回类型是 List 则入参的 IPage 可以为 null(为 null 则不分页),但需要你手动 入参的IPage.setRecords(返回的 List);
如果 xml 需要从 page 里取值,需要 page.属性 获取 


多数据源 

多数据源既动态数据源,项目开发逐渐扩大,单个数据源、单一数据源已经无法满足需求项目的支撑需求。

使用方法

  • 引入dynamic-datasource-spring-boot-starter。
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>${version}</version>
</dependency>
  • 配置数据源
spring:
  datasource:
    dynamic:
      primary: book  #设置默认的数据源或者数据源组,默认值即为book
      strict: false  #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        book:
          url: jdbc:mysql://172.0.0.1:3306/book?zeroDateTimeBehavior=convertToNull&autoReconnect=true&generateSimpleParameterMetadata=true
          username: root
          password: root
        user:
          url: jdbc:mysql://172.0.0.41:3306/user?zeroDateTimeBehavior=convertToNull&autoReconnect=true&generateSimpleParameterMetadata=true
          username: root
          password: root
  • 使用 @DS 切换数据源

@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。 

注解结果
没有@DS默认数据源
@DS("dsName")dsName可以为组名也可以为具体某个库的名称
/**
 * 用户信息表
 *
 * @author yangyanping
 * @date 2023-03-18
 */
@DS("user")
public interface UserInfoMapper extends BaseMapper<UserInfoDO> {
}

 动态表名插件DynamicTableNameInnerInterceptor

注意事项:

  • 原理为解析替换设定表名为处理器的返回表名,表名建议可以定义复杂一些避免误替换
  • 例如:真实表名为 user 设定为 mp_dt_user 处理器替换为 user_2019 等
  • 动态表名处理器 
/**
 * 动态表名处理器-----根据日期分表
 * mybatis-plus提供了动态表名处理器接口TableNameHandler,
 * 只需要在系统中实现该接口,并作为插件加载到mybatis-plus中就可以使用
 *
 * @author yangyanping
 * @date 2023-02-15
 */
public class DateTableNameHandler implements TableNameHandler {
    @Override
    public String dynamicTableName(String sql, String tableName) {
        // 模拟获取月份参数,实际应该从参数中获取
        String spiltName = DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
        String dynamicTableName = tableName + "_" + spiltName;

        return dynamicTableName;
    }
}
/**
 * 动态表名处理器---根据用户ID分表
 * mybatis-plus提供了动态表名处理器接口TableNameHandler,
 * 只需要在系统中实现该接口,并作为插件加载到mybatis-plus中就可以使用
 *
 * @author yangyanping
 * @date 2023-02-15
 */
@Slf4j
public class UserAscribeTableNameHandler implements TableNameHandler {
    //设置请求线程的用户ID数据
    private static final ThreadLocal<Long> USER_ID_CONTEXT = new ThreadLocal<>();

    @Override
    public String dynamicTableName(String sql, String tableName) {
        //表名增加hash值
        String tabSuffix = tableName + "_" + DbTableUtil.getTableIndex(USER_ID_CONTEXT.get(), 256);
   

        return tabSuffix;
    }

    public static void put(Long userId) {
        USER_ID_CONTEXT.set(userId);
    }

    public static void remove() {
        USER_ID_CONTEXT.remove();
    }
}

 

 

  • 注册动态表名拦截器 
/**
 * @author miemie
 * @since 2018-08-10
 */
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());

        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MARIADB);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);

        return interceptor;
    }


      /**
     * 注册动态表名拦截器
     *
     * @return 动态表名拦截器
     */
    private DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {
        DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor =
                new DynamicTableNameInnerInterceptor();

        Map<String, TableNameHandler> tableNameHandlerMap = new HashMap<>();

        TableNameHandler tableNameHandler = new DateTableNameHandler();
        tableNameHandlerMap.put("user_history", tableNameHandler); 

        TableNameHandler testingPutUserIdTableNameHandler = new UserAscribeTableNameHandler();
        tableNameHandlerMap.put("user", testingPutUserIdTableNameHandler);

        dynamicTableNameInnerInterceptor.setTableNameHandlerMap(tableNameHandlerMap);

        return dynamicTableNameInnerInterceptor;
    }
}

MyBatis-Plus打印日志

在 logback.xml 中 自定义logger ,日志级别设置为 degug 

    <!-- 自定义logger -->
    <logger name="com.yyp.user.infra.gatewayImpl.database.user" level="debug" additivity="false">
        <appender-ref ref="file-all"/>
    </logger>

总结

以上主要是对MyBatis-Plus 在日常开发中做下总结和记录。

参考:

简介 | MyBatis-Plus

卷王必备学习的MyBatis-Plus用法,不来瞧瞧吗~~-阿里云开发者社区

MyBatisPlus如何进行分表查询? - 知乎

;