系列文章目录
目录
动态表名插件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
表名注解,用于标识实体类对应的表,使用位置:实体类。
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 表名 |
schema | String | 否 | "" | schema |
keepGlobalPrefix | boolean | 否 | false | 是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时) |
resultMap | String | 否 | "" | xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定) |
autoResultMap | boolean | 否 | false | 是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入) |
excludeProperty | String[] | 否 | {} | 需要排除的属性名 @since 3.3.1 |
@TableId 主键注解
属性性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 主键字段名 |
type | Enum | 否 | IdType.NONE | 指定主键类型 |
IdType
值 | 描述 |
---|---|
AUTO | 数据库 ID 自增 |
NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT | insert 前自行 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 长整型类型(please use ASSIGN_ID ) | |
32 位 UUID 字符串(please use ASSIGN_UUID ) | |
分布式全局唯一 ID 字符串类型(please use ASSIGN_ID ) |
@TableField
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 数据库字段名 |
exist | boolean | 否 | true | 是否为数据库表字段 |
condition | String | 否 | "" | 字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s} ,参考(opens new window) |
update | String | 否 | "" | 字段 update set 部分注入,例如:当在version字段上注解update="%s+1" 表示更新时会 set version=version+1 (该属性优先级高于 el 属性) |
insertStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_NULLinsert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>) |
updateStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:IGNOREDupdate table_a set column=#{columnProperty} |
whereStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_EMPTYwhere <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if> |
fill | Enum | 否 | FieldFill.DEFAULT | 字段自动填充策略 |
select | boolean | 否 | true | 是否进行 select 查询 |
keepGlobalFormat | boolean | 否 | false | 是否保持使用全局的 format 进行处理 |
jdbcType | JdbcType | 否 | JdbcType.UNDEFINED | JDBC 类型 (该默认值不代表会按照该值生效) |
typeHandler | Class<? extends TypeHandler> | 否 | UnknownTypeHandler.class | 类型处理器 (该默认值不代表会按照该值生效) |
numericScale | String | 否 | "" | 指定小数点后保留的位数 |
实体类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 在日常开发中做下总结和记录。
参考: