本文共 19287 字,大约阅读时间需要 64 分钟。
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性:
MyBatis-Plus 官网:
MyBatis-Plus 示例工程:
MyBatis 官网:
Maven 仓库:
MyBatis-Plus入门和使用实践:
pom.xml
引入依赖 mybatis-plus、lombok、spring-bootspring-boot-starter-parent org.springframework.boot 2.1.3.RELEASE org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.projectlombok lombok true com.baomidou mybatis-plus-boot-starter 3.4.2 com.baomidou mybatis-plus 3.4.2 mysql mysql-connector-java junit junit 4.12 test
application.yml
# mysql 连接配置spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp-advanced-db?useSSL=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8 username: root password: 123456# 输入日志,查看 sql 语句logging: level: root: warn org.example.dao: trace pattern: console: '%p%m%n'
App.java
启动 spring-bootpackage org.example;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/** * spring-boot 启动入口 * */@SpringBootApplication@MapperScan({ "org.example.dao"})public class App { public static void main( String[] args ) { SpringApplication.run(App.class, args); }}
#创建用户表CREATE TABLE user ( id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键', name VARCHAR(30) DEFAULT NULL COMMENT '姓名', age INT(11) DEFAULT NULL COMMENT '年龄', email VARCHAR(50) DEFAULT NULL COMMENT '邮箱', manager_id BIGINT(20) DEFAULT NULL COMMENT '上级领导id', create_time DATETIME DEFAULT NULL COMMENT '创建时间', update_time DATETIME DEFAULT NULL COMMENT '修改时间', version INT(11) DEFAULT '1' COMMENT '版本', deleted INT(1) DEFAULT '0' COMMENT '逻辑删除标识(0.未删除,1.已删除)', CONSTRAINT manager_fk FOREIGN KEY (manager_id) REFERENCES user (id)) ENGINE=INNODB CHARSET=UTF8;#初始化数据:INSERT INTO user (id, name, age, email, manager_id , create_time)VALUES (1087982257332887553, '大boss', 40, 'boss@baomidou.com', NULL , '2019-01-11 14:20:20'), (1088248166370832385, '王天风', 25, 'wtf@baomidou.com', 1087982257332887553 , '2019-02-05 11:12:22'), (1088250446457389058, '李艺伟', 28, 'lyw@baomidou.com', 1088248166370832385 , '2019-02-14 08:31:16'), (1094590409767661570, '张雨琪', 31, 'zjq@baomidou.com', 1088248166370832385 , '2019-01-14 09:15:15'), (1094592041087729666, '刘红雨', 32, 'lhm@baomidou.com', 1088248166370832385 , '2019-01-14 09:48:16');
entity/User.java
package org.example.entity;import com.baomidou.mybatisplus.extension.activerecord.Model;import lombok.Data;import java.time.LocalDateTime;/*** * 用户实体类 * */@Datapublic class User extends Model { // 用户 ID private Long id; // 姓名 private String name; // 年龄 private Integer age; // 邮箱 private String email; // 上级领导 ID private Long managerId; // 创建时间 private LocalDateTime createTime; // 更新时间 private LocalDateTime updateTime; // 版本 private Integer version; // 逻辑删除标识(0.未删除,1.已删除) private Integer deleted;}
dao/UserMapper.java
(接口)package org.example.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import org.example.entity.User;public interface UserMapper extends BaseMapper{ }
AppTest.java
package org.example;import org.example.dao.UserMapper;import org.example.entity.User;import org.junit.Assert;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.util.List;@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTestpublic class AppTest { @Autowired private UserMapper userMapper; /** * 查询列表 * */ @Test public void select() { Listusers = userMapper.selectList(null); // 判断预期值与实际值是否相同 // Assert.assertEquals(5, users.size()); // 循环输出 users.forEach(System.err::println); }}
逻辑删除-MP官网:
使用场景: 1.保留业务数据; 2.主外键关联,不方便删除
# mybatis-plusmybatis-plus: # 全局主键配置 global-config: db-config: # 逻辑删除:标记为正常的值 logic-not-delete-value: 0 # 逻辑删除:标记为删除的值 logic-delete-value: 1
@TableLogic
:标记逻辑删除属性
@TableField(select = false)
:查询时排除掉当前属性
public class User { ...... // 逻辑删除标识(0.未删除,1.已删除) @TableLogic @TableField(select = false) private Integer deleted;}
@Testpublic void deleteById(){ Long userid = 1088250446457389058L; // 根据 ID 删除数据 Integer num = userMapper.deleteById(userid); System.out.println("受影响行数:"+num); // 查询删除结果 Listusers = userMapper.selectList(null); users.forEach(System.err::println);}
结果:
1.删除方法,最终执行的是 update 语句
2.查询方法,最终执行的查询 SQL 中添加了逻辑删除的属性,只查询未删除的数据
注意事项:
自动填充-MP官网:
使用场景: 自动填充创建时间和修改时间
@TableField(fill = FieldFill.INSERT)
:执行新增操作时,进行填充
@TableField(fill = FieldFill.UPDATE)
:执行修改操作时,进行填充
public class User { ...... // 创建时间 @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; // 更新时间 @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime;}
实现:
com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
接口
package org.example.componect;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;import org.apache.ibatis.reflection.MetaObject;import org.springframework.stereotype.Component;import java.time.LocalDateTime;@Componentpublic class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { // 当属性存在时,在执行自动填充操作 boolean hasSetter = metaObject.hasSetter("createTime"); // 当属性未设置值时,在执行自动填充操作 Object val = getFieldValByName("createTime", metaObject); if(hasSetter && val == null){ // 属性存在,且未设置值 setFieldValByName("createTime", LocalDateTime.now(), metaObject); } } @Override public void updateFill(MetaObject metaObject) { // 当属性存在时,在执行自动填充操作 boolean hasSetter = metaObject.hasSetter("updateTime"); // 当属性未设置值时,在执行自动填充操作 Object val = getFieldValByName("updateTime", metaObject); if(hasSetter && val == null){ // 属性存在,且未设置值 setFieldValByName("updateTime", LocalDateTime.now(), metaObject); } }}
@Testpublic void insert(){ User user = new User(); user.setName("李小龙"); user.setAge(22); user.setEmail("lxl@163.com"); user.setManagerId(1088248166370832385L); // 插入数据 int num = userMapper.insert(user); System.err.println("影响记录数据:"+num); System.err.println("主键:"+user.getId());}@Testpublic void updateById(){ User user = new User(); user.setId(1360237954211033089L); user.setAge(23); // 更新数据 int num = userMapper.updateById(user); System.err.println("影响记录数据:"+num);}
乐观锁-维基百科:
乐观锁-MP官网:
使用场景: 防止更新冲突
package org.example.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.transaction.annotation.EnableTransactionManagement;@Configuration@EnableTransactionManagementpublic class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 乐观锁插件 return interceptor; }}
public class User { ...... // 版本 @Version private Integer version;}
@Testpublic void updateById(){ // 查询数据,获取当前版本 Long userid = 1360237954211033089L; User user = userMapper.selectById(userid); // 更新数据 user.setAge(23); int num = userMapper.updateById(user); System.err.println("影响记录数据:"+num);}
执行 SQL 分析-MP官网:
p6spy-GitHub:
使用场景: 输出每条 SQL 语句执行时间
注意: 该插件有性能损耗,不建议生产环境使用。
建议: 开发环境和测试环境配置 p6spy 驱动;生产环境配置正常的数据库连接驱动
p6spy p6spy 3.9.1
数据源配置修改:driver-class-name 和 url
spring: datasource: # driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.p6spy.engine.spy.P6SpyDriver # url: jdbc:mysql://localhost:3306 ...... url: jdbc:p6spy:mysql://localhost:3306 ...... ......
#3.2.1以上使用modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory#3.2.1以下使用或者不配置#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory# 自定义日志打印logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger#日志输出到控制台appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger# 使用日志系统记录 sql#appender=com.p6spy.engine.spy.appender.Slf4JLogger# 设置 p6spy driver 代理deregisterdrivers=true# 取消JDBC URL前缀useprefix=true# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.excludecategories=info,debug,result,commit,resultset# 日期格式dateformat=yyyy-MM-dd HH:mm:ss# 实际驱动可多个#driverlist=org.h2.Driver# 是否开启慢SQL记录outagedetection=true# 慢SQL记录标准 2 秒outagedetectioninterval=2# 输出到日志文件# logfile=spylog.log
@Testpublic void select(){ Listusers = userMapper.selectList(null); // 循环输出 users.forEach(System.err::println);}
多租户技术-维基百科:
多租户-MP官网:
使用场景: 多用户的环境下共享相同的数据库,例如:当前用户只能查询自己创建的数据
package org.example.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;import net.sf.jsqlparser.expression.Expression;import net.sf.jsqlparser.expression.LongValue;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.transaction.annotation.EnableTransactionManagement;import java.util.Arrays;import java.util.List;@Configuration@EnableTransactionManagementpublic class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 多租户 sql 解析器 interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { @Override public String getTenantIdColumn() { return "manager_id"; // 实现多租户的数据库表字段名 } @Override public Expression getTenantId() { // 一般从 session 或 配置文件中获取 Long managerId = 1088248166370832385L; return new LongValue(managerId); // 传入的多租户值 } @Override public boolean ignoreTable(String tableName) { Listlist = Arrays.asList("rols", "power"); return list.contains(tableName); // return false;// 表示当前表需要添加多租户过滤;如果不需要,判断表名后,返回 true 即可 } })); // 分页插件 // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor // 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false // PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); // interceptor.addInnerInterceptor(paginationInnerInterceptor); return interceptor; }}
实际情况:CURD 都会生效
方法级取消多租户信息:Mapper 方法上添加注解
@InterceptorIgnore(tenantLine = "true")
@Testpublic void select(){ Listusers = userMapper.selectList(null); // 循环输出 users.forEach(System.err::println);}
动态表-MP官网:
使用场景: 不同的表,表结构相同(数据量大);例如:日志表,根据月份进行拆分
config/MybatisPlusConfig.java
package org.example.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.transaction.annotation.EnableTransactionManagement;import java.util.HashMap;import java.util.Random;@Configuration@EnableTransactionManagementpublic class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 动态表(这里需要先创建 user_2018 和 user_2019 两张表) DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor(); HashMapmap = new HashMap (2) { { put("user", (sql, tableName) -> { String year = "_2018"; int random = new Random().nextInt(10); if (random % 2 == 1) { year = "_2019"; } return tableName + year; }); }}; dynamicTableNameInnerInterceptor.setTableNameHandlerMap(map); interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor); return interceptor; }}
方法级取消动态表名替换:Mapper 方法上添加注解
@InterceptorIgnore(dynamicTableName= "true")
@Testpublic void select(){ Listusers = userMapper.selectList(null); // 循环输出 users.forEach(System.err::println);}
SQL 注入器-MP官网:
使用场景: 自定义通用方法;例如:BaseMapper 的方法
步骤:
继承
com.baomidou.mybatisplus.core.injector.AbstractMethod
package org.example.method;import com.baomidou.mybatisplus.core.injector.AbstractMethod;import com.baomidou.mybatisplus.core.metadata.TableInfo;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.mapping.SqlSource;public class DeleteAllMethod extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { // 定义 sql 语句 String sql = "delete from " + tableInfo.getTableName(); // 定义方法名 String method = "deleteAll"; // sql 源 SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); // delete 方法 return addDeleteMappedStatement(mapperClass, method, sqlSource); }}
package org.example.injector;import com.baomidou.mybatisplus.core.injector.AbstractMethod;import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;import org.example.method.DeleteAllMethod;import java.util.List;@Componentpublic class MySqlInjector extends DefaultSqlInjector { @Override public ListgetMethodList(Class mapperClass) { List methods = super.getMethodList(mapperClass); // 获取之前已添加的方法类 methods.add(new DeleteAllMethod()); // 添加自定义的方法类 return methods; }}
继承
com.baomidou.mybatisplus.core.mapper.BaseMapper
package org.example.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;public interface MyMapperextends BaseMapper { /** * 删除所有数据 * @return 受影响行数 * */ public int deleteAll();}
继承
MyMapper
package org.example.dao;import com.baomidou.mybatisplus.core.conditions.Wrapper;import com.baomidou.mybatisplus.core.toolkit.Constants;import org.apache.ibatis.annotations.Param;import org.apache.ibatis.annotations.Select;import org.example.entity.User;import java.util.List;public interface UserMapper extends MyMapper{ ......}
方法级取消动态表名替换:Mapper 方法上添加注解
@InterceptorIgnore(dynamicTableName= "true")
@Testpublic void deleteAll(){ Integer num = userMapper.deleteAll(); System.out.println("受影响行数:"+num);}
选装件-MP官网:
injector/MySqlInjector.java
中添加选装件目录:com.baomidou.mybatisplus.extension.injector.methods
类名 | 说明 |
---|---|
InsertBatchSomeColumn | 批量插入,可排除某些字段 |
LogicDeleteByIdWithFill | 根据 id 逻辑删除数据,并带字段填充功能;例如:逻辑删除时,修改操作人字段 |
AlwaysUpdateSomeColumnById | 根据 id 更新固定的某些字段 |
InsertBatchSomeColumn 为例:
package org.example.injector;import com.baomidou.mybatisplus.core.injector.AbstractMethod;import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;import org.example.method.DeleteAllMethod;import java.util.List;@Componentpublic class MySqlInjector extends DefaultSqlInjector { @Override public ListgetMethodList(Class mapperClass) { List methods = super.getMethodList(mapperClass); // 获取之前已添加的方法类 methods.add(new DeleteAllMethod()); // 添加自定义的方法类 methods.add(new InsertBatchSomeColumn(t->!t.isLogicDelete())); // 不是逻辑删除的字段都包含在内 return methods; }}
继承
com.baomidou.mybatisplus.core.mapper.BaseMapper
package org.example.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import java.util.List;public interface MyMapperextends BaseMapper { /** * 删除所有数据 * @return 受影响行数 * */ public int deleteAll(); /** * 批量插入 * @return 受影响行数 * */ public int insertBatchSomeColumn(List list);}
继承
MyMapper
方法级取消动态表名替换:Mapper 方法上添加注解
@InterceptorIgnore(dynamicTableName= "true")
@Testpublic void insertBatchSomeColumn(){ User user = new User(); user.setName("李小龙"); user.setAge(22); user.setEmail("lxl@163.com"); user.setManagerId(1088248166370832385L); User user2 = new User(); user2.setName("李二龙"); user2.setAge(25); user2.setEmail("lel@163.com"); user2.setManagerId(1088248166370832385L); Listusers = Arrays.asList(user, user2); // 插入数据 int num = userMapper.insertBatchSomeColumn(users); System.err.println("影响记录数据:"+num); System.err.println("主键:"+user.getId());}
转载地址:http://qznws.baihongyu.com/