1. 配置

在MP中有大量的配置,其中有一部分是Mybatis原生的配置,另一部分是MP的配置,

详情:MP官网

1.1 基本配置

1. configLocation

MyBatis 配置文件位置,如果您有单独的 MyBatis 配置,请将其路径配置到 configLocation 中。

MyBatis Configuration 的具体内容请参考MyBatis 官方文档

Spring Boot:

1
mybatis-plus.config-location = classpath:mybatis-config.xml

Spring MVC:

1
2
3
4
<bean id="sqlSessionFactory"
class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>

2. mapperLocations

MyBatis Mapper 所对应的 XML 文件位置,如果您在 Mapper 中有自定义方法(XML 中有自定义实现),需要进行该配置,告诉 Mapper 所对应的 XML 文件位置。

Spring Boot:

1
mybatis-plus.mapper-locations = classpath*:mybatis/*.xml

Spring MVC:

1
2
3
4
<bean id="sqlSessionFactory"
class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="mapperLocations" value="classpath*:mybatis/*.xml"/>
</bean>

Maven 多模块项目的扫描路径需以 classpath*: 开头 (即加载多个 jar 包下的 XML 文件)

3. typeAliasesPackage

MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件中可以直接使用类名,而不用使用全限定的类名(即 XML 中调用的时候不用包含包名)。

Spring Boot:

1
mybatis-plus.type-aliases-package = cn.jyw.mp.pojo

Spring MVC:

1
2
3
4
<bean id="sqlSessionFactory"
class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="typeAliasesPackage" value="com.baomidou.mybatisplus.samples.quickstart.entity"/>
</bean>

1.2 进阶配置

本部分(Configuration)的配置大都为 MyBatis 原生支持的配置,这意味着您可以通过 MyBatis XML 配置文件的形式进行配置

1. mapUnderscoreToCamelCase

  • 类型:boolean
  • 默认值:true

是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射。

注意: 此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body 如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名

示例(SpringBoot):

1
2
#关闭自动驼峰映射,该参数不能和mybatis-plus.config-location同时存在
mybatis-plus.configuration.map-underscore-to-camel-case=false

2. cacheEnabled

  • 类型: boolean
  • 默认值: true

全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为 true。

1
mybatis-plus.configuration.cache-enabled=false

1.3 DB 策略配置

1. idType

  • 类型: com.baomidou.mybatisplus.annotation.IdType
  • 默认值: ID_WORKER

全局默认主键类型,设置后,即可省略实体对象中的@TableId(type = IdType.AUTO)配置。

SpringBoot:

1
mybatis-plus.global-config.db-config.id-type=auto

SpringMVC:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--这里使用MP提供的sqlSessionFactory,完成了Spring与MP的整合-->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="globalConfig">
<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
<property name="dbConfig">
<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
<property name="idType" value="AU0TO"/>
</bean>
</property>
</bean>
</property>
</bean>

2. tablePrefix

  • 类型: String
  • 默认值: null

表名前缀,全局配置后可省略@TableName()配置。

SpringBoot:

1
mybatis-plus.global-config.db-config.table-prefix=tb_

SpringMVC:

1
2
3
4
5
6
7
8
9
10
11
12
13
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="globalConfig">
<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
<property name="dbConfig">
<bean class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
<property name="idType" value="AUTO"/>
<property name="tablePrefix" value="tb_"/>
</bean>
</property>
</bean>
</property>
</bean>

2. 插件

2.1mybatis的插件机制

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2. ParameterHandler (getParameterObject, setParameters)
  3. ResultSetHandler (handleResultSets, handleOutputParameters)
  4. StatementHandler (prepare, parameterize, batch, update, query)

我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的 一些方法等。

总体概括为:

  1. 拦截执行器的方法
  2. 拦截参数的处理
  3. 拦截结果集的处理
  4. 拦截Sql语法构建的处理

拦截器示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class MyInterceptor implements Interceptor {

@Override
public Object intercept(Invocation invocation) throws Throwable {
//拦截方法,具体业务逻辑编写的位置
return invocation.proceed();
}

@Override
public Object plugin(Object target) {
//创建target对象的代理对象,目的是将当前拦截器加入到该对象中
return Plugin.wrap(target, this);
}

@Override
public void setProperties(Properties properties) {
//属性设置
}
}

注入到Spring容器:

1
2
3
4
5
6
7
/**
* 自定义拦截器
*/
@Bean
public MyInterceptor myInterceptor(){
return new MyInterceptor();
}

或者通过xml配置,mybatis-config.xml:

1
2
3
4
5
<configuration>
<plugins>
<plugin interceptor="cn.jyw.mp.plugins.MyInterceptor"></plugin>
</plugins>
</configuration>

2.2 执行分析插件

在MP中提供了对SQL执行的分析的插件,可用作阻断全表更新、删除的操作,

该插件仅适用于开发环境,不适用于生产环境。

SpringBoot配置:

1
2
3
4
5
6
7
8
9
10
@Bean
public SqlExplainInterceptor sqlExplainInterceptor(){
SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
// 攻击 SQL 阻断解析器、加入解析链
sqlParserList.add(new BlockAttackSqlParser());
sqlExplainInterceptor.setSqlParserList(sqlParserList);

return sqlExplainInterceptor;
}

当执行全表更新时,会抛出异常,这样有效防止了一些误操作

2.3 性能分析插件

性能分析拦截器,用于输出每条 SQL 语句及其执行时间,可以设置最大执行时间,超过时间会抛出异常。

该插件只用于开发环境,不建议生产环境使用。

配置:

1
2
3
4
5
6
7
8
9
10
<configuration>
<plugins>
<!-- SQL 执行性能分析,开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长 -->
<plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor">
<property name="maxTime" value="100" />
<!--SQL是否格式化 默认false-->
<property name="format" value="true" />
</plugin>
</plugins>
</configuration>

2.4 乐观锁插件

1. 主要适用场景

意图:当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时,set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

2. 插件配置

spring xml:

1
<bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/>

spring boot:

1
2
3
4
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}

3. 注解实体字段

需要为实体字段添加@Version注解。

第一步,为表添加version字段,并且设置初始值为1:

1
2
3
ALTER TABLE `tb_user`
ADD COLUMN `version` int(10) NULL AFTER `email`;
UPDATE `tb_user` SET `version`='1';

第二步,为User实体对象添加version字段,并且添加@Version注解:

1
2
@Version
private Integer version;

4. 测试

1
2
3
4
5
User user = new User();
user.setAge(30);
user.setId(2L);
user.setVersion(1); //获取到version为1
int result = this.userMapper.updateById(user);

在日志中可以看见更新的条件中有version条件,并且更新的version为2。

如果再次执行,更新则不成功。这样就避免了多人同时更新时导致数据的不一致。

5. 特别说明

  • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity 中
  • 仅支持 updateById(id) 与 update(entity, wrapper) 方法
  • 在 update(entity, wrapper) 方法下, wrapper 不能复用!!!

3. Sql 注入器

3.1 SQL注入的原理

前面我们已经知道,MP在启动后会将BaseMapper中的一系列的方法注册到meppedStatements中,那么究竟是如 何注入的呢?流程又是怎么样的?下面我们将一起来分析下。

在MP中,ISqlInjector负责SQL的注入工作,它是一个接口,AbstractSqlInjector是它的实现类

在AbstractSqlInjector中,主要是由inspectInject()方法进行注入的,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
Class<?> modelClass = this.extractModelClass(mapperClass);
if (modelClass != null) {
String className = mapperClass.toString();
Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
if (!mapperRegistryCache.contains(className)) {
List<AbstractMethod> methodList = this.getMethodList(mapperClass);
if (CollectionUtils.isNotEmpty(methodList)) {
TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
//循环注入自定义方法
methodList.forEach((m) -> {
m.inject(builderAssistant, mapperClass, modelClass, tableInfo);
});
} else {
logger.debug(mapperClass.toString() + ", No effective injection method was found.");
}

mapperRegistryCache.add(className);
}
}

}

在实现方法中, methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); 是关键,循环遍历方法,进行注入

最终调用抽象方法injectMappedStatement进行真正的注入:

1
2
3
4
5
6
7
8
9
/**
* 注入自定义 MappedStatement
*
* @param mapperClass mapper 接口
* @param modelClass mapper 泛型
* @param tableInfo 数据库表反射信息
* @return MappedStatement
*/
public abstract MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo);

查看该方法的实现:有很多已经写好的MP方法

以SelectById为例查看:

1
2
3
4
5
6
7
8
9
public class SelectById extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.LOGIC_SELECT_BY_ID;
SqlSource sqlSource = new RawSqlSource(configuration,String.format(sqlMethod.getSql(),
sqlSelectColumns(tableInfo, false),tableInfo.getTableName(), tableInfo.getKeyColumn(),tableInfo.getKeyProperty(),tableInfo.getLogicDeleteSql(true, false)), Object.class);
return this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(),sqlSource, modelClass, tableInfo);
}
}

可以看到,生成了SqlSource对象,再将SQL通过addSelectMappedStatement方法添加到meppedStatements中。

3.2 自定义MyBaseMapper

我们已经知道,在MP中,通过AbstractSqlInjector将BaseMapper中的方法注入到了Mybatis容器,这样这些方法才可以正常执行。

那么,如果我们需要扩充BaseMapper中的方法,又该如何实现呢?

下面我们以扩展findAll方法为例进行学习。

1. 编写MyBaseMapper

1
2
3
public interface MyBaseMapper<T> extends BaseMapper<T> {
List<T> findAll();
}

其他的Mapper都可以继承该Mapper,这样实现了统一的扩展。

1
2
3
public interface UserMapper extends MyBaseMapper<User> {
User findById(Long id);
}

2. 编写MySqlInjector

如果直接继承AbstractSqlInjector的话,原有的BaseMapper中的方法将失效,所以我们选择继承DefaultSqlInjector 进行扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MySqlInjector extends DefaultSqlInjector {

@Override
public List<AbstractMethod> getMethodList() {
List<AbstractMethod> methodList = super.getMethodList();

methodList.add(new FindAll());
// 再扩充自定义的方法
list.add(new FindAll());

return methodList;
}
}

3. 编写FindAll

1
2
3
4
5
6
7
8
9
10
11
public class FindAll extends AbstractMethod {

@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sqlMethod = "findAll";
String sql = "select * from " + tableInfo.getTableName();
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql,modelClass);
return this.addSelectMappedStatement(mapperClass, sqlMethod, sqlSource,modelClass, tableInfo);
}

}

4. 注册到Spring容器

1
2
3
4
5
6
7
/**
* 自定义SQL注入器
*/
@Bean
public MySqlInjector mySqlInjector(){
return new MySqlInjector();
}

5. 测试

1
2
3
4
5
6
7
@Test
public void testFindAll(){
List<User> users = this.userMapper.findAll();
for (User user : users) {
System.out.println(user);
}
}

输出的SQL:

1
2
3
4
[main] [cn.jyw.mp.mapper.UserMapper.findAll]-[DEBUG] ==> Preparing: select * from
tb_user
[main] [cn.jyw.mp.mapper.UserMapper.findAll]-[DEBUG] ==> Parameters:
[main] [cn.jyw.mp.mapper.UserMapper.findAll]-[DEBUG] <== Total: 10

至此,我们实现了全局扩展SQL注入器。

4. 自动填充功能

有些时候我们可能会有这样的需求,插入或者更新数据时,希望有些字段可以自动填充数据,比如密码、version 等。

在MP中提供了这样的功能,可以实现自动填充。

4.1 添加@TableField注解

1
2
@TableField(fill = FieldFill.INSERT) //插入数据时进行填充
private String password;

为password添加自动填充功能,在新增数据时有效。

FieldFill提供了多种模式选择:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum FieldFill {

// 默认不处理
DEFAULT,

// 插入时填充字段
INSERT,

// 更新时填充字段
UPDATE,

// 插入和更新时填充字段
INSERT_UPDATE
}

4.2 编写MyMetaObjectHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

@Override
public void insertFill(MetaObject metaObject) {
Object password = getFieldValByName("password", metaObject);
if(null == password){
//字段为空,可以进行填充
setFieldValByName("password", "123456", metaObject);
}
}
@Override
public void updateFill(MetaObject metaObject) {

}
}

5. 逻辑删除

开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,
所谓逻辑删除就是将数据标记为删除,而并非真正的物理删除(非DELETE操作),
查询时需要携带状态条件,确保被标记的数据不被查询到。这样做的目的就是避免数据被真正的删除。

MP就提供了这样的功能,方便使用

5.1 修改表结构

为tb_user表增加deleted字段,用于表示数据是否被删除,1代表删除,0代表未删除。

1
2
ALTER TABLE `tb_user`
ADD COLUMN `deleted` int(1) NULL DEFAULT 0 COMMENT '1代表删除,0代表未删除' AFTER `version`;

同时,也修改User实体,增加deleted属性并且添加@TableLogic注解:

1
2
@TableLogic
private Integer deleted;

5.2 配置

application.properties:

1
2
3
4
# 逻辑已删除值(默认为 1)
mybatis-plus.global-config.db-config.logic-delete-value=1
# 逻辑未删除值(默认为 0)
mybatis-plus.global-config.db-config.logic-not-delete-value=0

5.3 测试

1
2
3
4
@Test
public void testDeleteById(){
this.userMapper.deleteById(2L);
}

从表的变动可以看出只是把deleted字段变为了1 再次查询已经查不到了

6. 通用枚举

解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!

6.1 修改表结构

1
2
ALTER TABLE `tb_user`
ADD COLUMN `sex` int(1) NULL DEFAULT 1 COMMENT '1-男,2-女' AFTER `deleted`;

6.2 定义枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public enum SexEnum implements IEnum<Integer> {

MAN(1,"男"),
WOMAN(2,"女");

private int value;
private String desc;

SexEnum(int value, String desc) {
this.value = value;
this.desc = desc;
}

@Override
public Integer getValue() {
return this.value;
}

@Override
public String toString() {
return this.desc;
}
}

6.3 配置

1
2
# 枚举包扫描
mybatis-plus.type-enums-package=cn.jyw.mp.enums

6.4 修改实体

1
private SexEnum sex;

6.5 测试

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testInsert(){
User user = new User();
user.setName("貂蝉");
user.setUserName("diaochan");
user.setAge(20);
user.setEmail("123@qq.cn");
user.setVersion(1);
user.setSex(SexEnum.WOMAN);

int result = this.userMapper.insert(user);
}

查询条件时也是有效的:

1
2
3
4
5
6
@Test
public void testSelectBySex() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("sex", SexEnum.WOMAN);
List<User> users = this.userMapper.selectList(wrapper);
}