MyBatis-Plus
MyBatis-Plus 是一个基于 MyBatis的增强工具,旨在简化开发过程,减少重复代码。它在MyBatis的基础上增加了CRUD操作封装,条件构造器、代码生成器等功能。
一、核心特性与优势
1. 核心特性
- 无侵入:只做增强不做改变,完全兼容MyBatis
- 自动CRUD:内置通用Mapper,单表操作无需编写sql
- 条件构造器:通过Lambda表达式构建复杂查询条件
- 代码生成器:自动生成Entity、Mapper、Service等代码
- 分页插件:内置分页功能,支持多种数据库。
- 乐观锁:内置乐观锁插件,防止脏数据更新。
- 逻辑删除:支持逻辑删除,避免物理删除数据。
- 多租户
2. 与原生 MyBatis 对比
功能 | 原生 MyBatis | MyBatis-Plus |
---|---|---|
单表 CRUD | 手动编写 XML 或注解 | 内置通用 Mapper,零 SQL |
条件查询 | 手动拼接 SQL 或 XML | Lambda 条件构造器 |
分页 | 手动实现分页插件 | 内置分页插件,一行代码搞定 |
代码生成 | 需自定义模板 | 内置代码生成器 |
性能分析 | 需集成第三方插件 | 内置性能分析插件 |
二、高级功能
1. 乐观锁插件
// 1. 配置乐观锁插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 乐观锁插件return interceptor;
}// 2. 实体类字段上添加 @Version 注解
@Data
public class Product {private Long id;private String name;@Versionprivate Integer version;
}// 3. 使用
Product product = productService.getById(1L);
product.setPrice(100);
productService.updateById(product); // 自动带上 version 条件
2. 逻辑删除
// 1. 配置逻辑删除插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new LogicSqlInjector()); // 逻辑删除插件return interceptor;
}// 2. 配置 application.yml
mybatis-plus:global-config:db-config:logic-not-delete-value: 0 # 未删除值(默认)logic-delete-value: 1 # 已删除值(默认)// 3. 实体类字段上添加 @TableLogic 注解
@Data
public class User {private Long id;private String name;@TableLogicprivate Integer deleted;
}// 4. 使用
userService.removeById(1L); // 实际执行 UPDATE user SET deleted=1 WHERE id=1 AND deleted=0
3. 性能分析插件
// 配置性能分析插件(开发环境使用)
@Bean
@Profile({"dev","test"}) // 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {PerformanceInterceptor interceptor = new PerformanceInterceptor();interceptor.setMaxTime(100); // 最大执行时间,单位毫秒interceptor.setFormat(true); // SQL 是否格式化return interceptor;
}
三、整体架构与核心组件
1. 架构分层
┌───────────────────────────────────────────────────────────┐
│ 用户层 │
│ (Service/Controller 通过 BaseMapper/IService 调用方法) │
├───────────────────────────────────────────────────────────┤
│ 接口层 │
│ (BaseMapper<T>, IService<T>, ServiceImpl<M extends │
│ BaseMapper<T>, T>) │
├───────────────────────────────────────────────────────────┤
│ 核心层 │
│ (SqlRunner, SqlHelper, MybatisMapperMethod, │
│ MybatisMapperProxy) │
├───────────────────────────────────────────────────────────┤
│ 插件层 │
│ (PaginationInnerInterceptor, OptimisticLockerInnerInterceptor,│
│ LogicSqlInjector) │
├───────────────────────────────────────────────────────────┤
│ 配置层 │
│ (MybatisPlusAutoConfiguration, MybatisPlusProperties) │
├───────────────────────────────────────────────────────────┤
│ MyBatis 原生层 │
│ (SqlSession, Executor, StatementHandler, ResultSetHandler)│
└───────────────────────────────────────────────────────────┘
2. 核心组件
- BaseMapper:提供基础 CRUD 方法的接口。
- ServiceImpl:Service 层的默认实现,封装常用业务逻辑。
- SqlHelper:SQL 执行工具类,与 MyBatis 交互。
- MybatisMapperMethod:增强版 Mapper 方法调用处理器。
- MybatisMapperProxy:Mapper 接口的代理实现。
- InnerInterceptor:插件机制,用于分页、乐观锁等功能。
3.执行流程总结
四、自动配置原理
1. 自动配置入口
// MybatisPlusAutoConfiguration.java
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {@Bean@ConditionalOnMissingBeanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {// 创建 SqlSessionFactorySqlSessionFactoryBean factory = new SqlSessionFactoryBean();factory.setDataSource(dataSource);// 配置 MyBatis-Plus 专属设置Configuration configuration = new MybatisConfiguration();factory.setConfiguration(configuration);// 注册自定义类型处理器if (!ObjectUtils.isEmpty(this.properties.getTypeHandlersPackage())) {factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());}// 添加插件(分页、乐观锁等)if (!ObjectUtils.isEmpty(this.interceptors)) {factory.setPlugins(this.interceptors.toArray(new Interceptor[0]));}return factory.getObject();}// 其他 Bean 定义...
}
2. 插件注册流程
// MybatisPlusAutoConfiguration.java
@Bean
@ConditionalOnMissingBean
public MybatisPlusInterceptor mybatisPlusInterceptor() {return new MybatisPlusInterceptor();
}// 在需要使用插件的地方注入并配置
@Autowired
private MybatisPlusInterceptor interceptor;// 添加分页插件
@PostConstruct
public void addPageInterceptor() {interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
}
五、通用 CRUD 实现
1. BaseMapper 接口
public interface BaseMapper<T> extends Mapper<T> {int insert(T entity);int deleteById(Serializable id);int deleteByMap(@Param("cm") Map<String, Object> columnMap);int delete(@Param("ew") Wrapper<T> queryWrapper);int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);int updateById(@Param("et") T entity);int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);T selectById(Serializable id);List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);T selectOne(@Param("ew") Wrapper<T> queryWrapper);Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);Page<T> selectPage(Page<T> page, @Param("ew") Wrapper<T> queryWrapper);// 其他方法...
}
2. SQL 注入器(SqlInjector)
MP 通过 SqlInjector
将通用方法对应的 SQL 注入到 MyBatis 中:
// DefaultSqlInjector.java
public class DefaultSqlInjector extends AbstractSqlInjector {@Overridepublic List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {List<AbstractMethod> methodList = new ArrayList<>();// 添加基础 CRUD 方法methodList.add(new Insert());methodList.add(new Delete());methodList.add(new DeleteByMap());methodList.add(new DeleteById());methodList.add(new DeleteBatchIds());methodList.add(new Update());methodList.add(new UpdateById());methodList.add(new SelectById());methodList.add(new SelectBatchIds());methodList.add(new SelectByMap());methodList.add(new SelectOne());methodList.add(new SelectCount());methodList.add(new SelectList());methodList.add(new SelectPage());return methodList;}
}
3. 方法执行流程
// MybatisMapperMethod.java (关键方法)
public Object execute(SqlSession sqlSession, Object[] args) {Object result;// 根据方法类型执行不同逻辑switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional() &&(result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");}return result;
}
六、条件构造器(Wrapper)源码
1. 核心接口与类
// Wrapper 接口定义
public interface Wrapper<T> extends Serializable {String getSqlSegment();Object getEntity();Map<String, Object> getParamNameValuePairs();// 其他方法...
}// AbstractWrapper 实现基本逻辑
public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>>implements Wrapper<T> {protected T entity;protected LinkedHashMap<String, Object> paramNameValuePairs = new LinkedHashMap<>();protected StringBuilder sqlWhere = new StringBuilder();// 条件方法实现public Children eq(R column, Object val) {return addCriterion("=", column, val);}public Children ne(R column, Object val) {return addCriterion("<>", column, val);}public Children gt(R column, Object val) {return addCriterion(">", column, val);}// 其他条件方法...
}// QueryWrapper 具体实现
public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>> {// 构造方法和特定方法
}// LambdaQueryWrapper 使用 Lambda 表达式
public class LambdaQueryWrapper<T> extends AbstractWrapper<T, SFunction<T, ?>, LambdaQueryWrapper<T>> {// Lambda 条件方法public <V> LambdaQueryWrapper<T> eq(SFunction<T, V> column, V val) {return super.eq(getColumn(column), val);}// 其他 Lambda 条件方法...
}
2. Lambda 表达式解析
MP 通过 com.baomidou.mybatisplus.core.toolkit.support.SFunction
和 LambdaUtils
解析 Lambda 表达式:
// LambdaUtils.java (关键方法)
public static <T> String getColumn(SFunction<T, ?> fn) {// 解析 Lambda 表达式获取字段名LambdaMeta meta = LambdaUtils.extract(fn);String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName());TableInfo tableInfo = TableInfoHelper.getTableInfo(meta.getInstantiatedType());// 根据字段名获取表列名if (tableInfo != null) {TableFieldInfo fieldInfo = tableInfo.getFieldList().stream().filter(f -> f.getProperty().equals(fieldName)).findFirst().orElse(null);if (fieldInfo != null) {return fieldInfo.getColumn();}}// 如果找不到,使用默认映射规则return StringUtils.camelToUnderline(fieldName);
}
七、分页插件实现
1. 核心拦截器
// PaginationInnerInterceptor.java
public class PaginationInnerInterceptor implements InnerInterceptor {@Overridepublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {// 获取分页参数IPage<?> page = findPage(parameter).orElse(null);if (page == null || page.getSize() < 0) {return;}// 获取数据库类型DbType dbType = this.dbType;if (dbType == null) {JdbcConnection connection = executor.getTransaction().getConnection();dbType = JdbcUtils.getDbType(connection.getMetaData().getURL());}// 根据不同数据库类型生成不同的分页 SQLDialect dialect = DialectFactory.getDialect(dbType);String buildSql = concatOrderBy(boundSql.getSql(), page);String pageSql = dialect.buildPaginationSql(buildSql, page.offset(), page.getSize());// 修改原始 SQLMetaObject metaObject = SystemMetaObject.forObject(boundSql);metaObject.setValue("sql", pageSql);}// 其他方法...
}
2. 方言实现
// MySQLDialect.java
public class MySQLDialect implements IDialect {@Overridepublic String buildPaginationSql(String originalSql, long offset, long limit) {StringBuilder sql = new StringBuilder(originalSql);sql.append(" LIMIT ").append(offset).append(",").append(limit);return sql.toString();}
}// OracleDialect.java
public class OracleDialect implements IDialect {@Overridepublic String buildPaginationSql(String originalSql, long offset, long limit) {StringBuilder sql = new StringBuilder();sql.append("SELECT * FROM (SELECT TMP.*, ROWNUM RN FROM (");sql.append(originalSql);sql.append(") TMP WHERE ROWNUM <= ").append(offset + limit);sql.append(") WHERE RN > ").append(offset);return sql.toString();}
}
八、面试题
1. 乐观锁和逻辑删除的实现原理是什么?
-
乐观锁
:
- 原理:通过版本号(
@Version
注解)实现,更新时检查版本号是否一致。 - 流程:查询时获取版本号 → 更新时带上版本号条件 → 成功后版本号 + 1。
- 原理:通过版本号(
-
逻辑删除
:
- 原理:通过
@TableLogic
注解标记字段,将 DELETE 转换为 UPDATE。 - 配置:设置
logic-not-delete-value=0
和logic-delete-value=1
。
- 原理:通过
2. 如何自定义 MyBatis-Plus 的插件?
- 实现
InnerInterceptor
接口。 - 重写
beforeQuery
、beforeUpdate
等方法,在 SQL 执行前后进行拦截。 - 将插件注册到
MybatisPlusInterceptor
中。
3. MyBatis 的核心组件有哪些?各自的职责是什么?
- SqlSessionFactory:创建 SqlSession 的工厂,通过
SqlSessionFactoryBuilder
构建。 - SqlSession:数据库操作的核心接口,提供执行 SQL 的方法。
- Executor:SQL 执行器,负责处理缓存、事务和 SQL 执行。
- StatementHandler:处理 SQL 语句的预编译和参数设置。
- ParameterHandler:处理 SQL 参数。
- ResultSetHandler:处理查询结果集,映射到 Java 对象。
4. MyBatis 中 #{} 和 ${} 的区别是什么?
-
#{}:预编译处理,将参数替换为占位符
?
,调用PreparedStatement 的set 方法来赋值,防止 SQL 注入。 -
${}:字符串替换,直接将参数插入 SQL,存在 SQL 注入风险。
-
示例
// #{} 生成:SELECT * FROM user WHERE name = ? @Select("SELECT * FROM user WHERE name = #{name}")// ${} 生成:SELECT * FROM user WHERE name = '张三' @Select("SELECT * FROM user WHERE name = '${name}'")
5. MyBatis 的缓存机制是怎样的?
-
一级缓存
:基于SqlSessionSqlSession的本地缓存,默认开启。
- 生命周期:与 SqlSession 一致,Session 关闭时缓存清空。
-
二级缓存
:全局缓存,基于namespace隔离。
- 配置方式:在映射文件中添加 标签或使用
@CacheNamespace
注解。
- 配置方式:在映射文件中添加 标签或使用
-
流程:查询时优先从二级缓存获取 → 再从一级缓存获取 → 最后查询数据库。
6. 如何配置 MyBatis 的二级缓存?
(1) MyBatis 的缓存分为一级缓存和 二级缓存。
一级缓存是 SqlSession 级别的缓存,默认开启。
二级缓存是 NameSpace 级别(Mapper)的缓存,多个 SqlSession 可以共享,使用时需要进行配置开启。
(2) 缓存的查找顺序:二级缓存 => 一级缓存 => 数据库
-
在mybatis-config.xml中启用二级缓存:
<settings><setting name="cacheEnabled" value="true"/> </settings>
-
在映射文件中配置缓存:
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
-
实体类实现
Serializable
接口。
7. MyBatis 的插件机制是如何工作的?
参考答案:
- 基于 责任链模式 和 动态代理。
- 插件可以拦截
Executor
、StatementHandler
、ParameterHandler
、ResultSetHandler
的方法。 - 自定义插件需实现
Interceptor
接口,使用@Intercepts
和@Signature
注解指定拦截点。
8.MyBatis如何获取自动生成的(主)键值
在标签中使用 useGeneratedKeys 和 keyProperty 两个属性来获取自动生成的主键值。
<insert id=”insertname” usegeneratedkeys=”true” keyproperty=”id”>
insert into names (name) values (#{name})
</insert>
9.简述Mybatis的动态SQL,列出常用的6个标签及作用
-
Mybatis 动态sql可以让我们在xml映射文件中,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能。
-
Mybatis 提供了9中动态sql标签:
: 进行条件的判断
:在判断后的 SQL 语句前面添加 WHERE 关键字,并处理 SQL 语句开始位置的 AND 或者 OR 的问题
:可以在 SQL 语句前后进行添加指定字符 或者去掉指定字符.
: 主要用于修改操作时出现的逗号问题
:类似于 java 中的 switch 语句.在所有的条件中选择其一
:迭代操作
-
其执行原理:使用OGNL 从sql参数对象中计算表达式的值,更具表达式的值动态拼接sql,以此来完成动态sql功能
10.Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?
不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置namespace,那么 id 不能重复;毕竟 namespace 不是必须的,只是最佳实践而已。原因就是 namespace+id 是作为 Map<String, MappedStatement>的 key 使用的,如果没有namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。
11.Mybatis 都有哪些 Executor 执行器?它们之间的区别是什么?
Mybatis 有三种基本的 Executor 执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
1)SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。
2)ReuseExecutor:执行 update 或 select,以 sql 作为
key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map
3)BatchExecutor:完成批处理。
12.Mybatis 中如何指定使用哪一种 Executor 执行器?
在 Mybatis 配置文件中,可以指定默认的 ExecutorType 执行器类型,也可以手动给DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数。