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
是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射。
注意: 此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body 如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名
示例(SpringBoot):
1 2
| mybatis-plus.configuration.map-underscore-to-camel-case=false
|
2. cacheEnabled
全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为 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
| <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
表名前缀,全局配置后可省略@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 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的 一些方法等。
总体概括为:
- 拦截执行器的方法
- 拦截参数的处理
- 拦截结果集的处理
- 拦截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) { 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<>(); sqlParserList.add(new BlockAttackSqlParser()); sqlExplainInterceptor.setSqlParserList(sqlParserList); return sqlExplainInterceptor; }
|
当执行全表更新时,会抛出异常,这样有效防止了一些误操作
2.3 性能分析插件
性能分析拦截器,用于输出每条 SQL 语句及其执行时间,可以设置最大执行时间,超过时间会抛出异常。
该插件只用于开发环境,不建议生产环境使用。
配置:
1 2 3 4 5 6 7 8 9 10
| <configuration> <plugins> <plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor"> <property name="maxTime" value="100" /> <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); 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
|
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
|
@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 }
|
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
| mybatis-plus.global-config.db-config.logic-delete-value=1
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 修改实体
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); }
|