在java持久层技术体系中,mybatis凭借其灵活的sql映射和强大的动态sql能力,成为企业级应用开发的首选框架。本文从动态sql核心语法、缓存实现原理、性能优化及面试高频问题四个维度,结合源码与工程实践,系统解析mybatis的核心特性与最佳实践。
一、动态sql核心语法与实现原理
1.1 动态sql标签体系
| 标签 | 作用 | 示例场景 |
|---|---|---|
<if> | 条件判断,按需拼接sql片段 | 动态查询(如多条件筛选) |
<choose> | 类似于java的switch语句,多选一执行 | 单条件查询(不同条件互斥) |
<where> | 智能处理where子句,自动剔除多余的and/or | 动态where条件 |
<set> | 智能处理update语句,自动剔除多余的逗号 | 动态更新(部分字段更新) |
<foreach> | 遍历集合,生成批量sql | 批量插入、in条件查询 |
<trim> | 自定义前缀、后缀处理,可替代<where>、<set> | 高级sql片段处理 |
1.2 动态sql执行流程
关键步骤解析:
- sql节点解析:
- xml配置中的动态标签(如
<if>)被解析为sqlnode对象(如ifsqlnode)。
- xml配置中的动态标签(如
- ognl表达式计算:
- 使用ognl(object graph navigation language)计算动态条件(如
#{username} != null)。
- 使用ognl(object graph navigation language)计算动态条件(如
- 参数绑定:
- 通过
typehandler将java对象转换为jdbc类型(如string → varchar)。
- 通过
1.3 高级应用:自定义sql提供器
1. 使用@provider注解
@selectprovider(type = usersqlprovider.class, method = "selectbycondition")
list<user> selectbycondition(map<string, object> params);
// 自定义sql提供器
public class usersqlprovider {
public string selectbycondition(map<string, object> params) {
sql sql = new sql();
sql.select("*").from("users");
if (params.containskey("username")) {
sql.where("username = #{username}");
}
if (params.containskey("age")) {
sql.where("age >= #{age}");
}
return sql.tostring();
}
} 2. 流式sql构建(sql类)
string sql = new sql()
.select("id", "username")
.from("users")
.where("status = 'active'")
.order_by("create_time desc")
.tostring(); 二、缓存机制深度解析
2.1 一级缓存(本地缓存)
1. 核心特性
- 作用域:
sqlsession级别(同一个会话内共享)。 - 生命周期:与
sqlsession一致,会话关闭时缓存清空。 - 实现类:
perpetualcache(基于hashmap)。
2. 源码关键逻辑
public class defaultsqlsession implements sqlsession {
private final executor executor;
@override
public <t> t selectone(string statement, object parameter) {
list<t> list = this.selectlist(statement, parameter);
// 一级缓存逻辑在executor中实现
return list.isempty() ? null : list.get(0);
}
}
public class baseexecutor implements executor {
private final perpetualcache localcache;
@override
public <e> list<e> query(mappedstatement ms, object parameter, rowbounds rowbounds, resulthandler resulthandler) throws sqlexception {
boundsql boundsql = ms.getboundsql(parameter);
cachekey key = createcachekey(ms, parameter, rowbounds, boundsql);
return query(ms, parameter, rowbounds, resulthandler, key, boundsql);
}
@override
public <e> list<e> query(mappedstatement ms, object parameter, rowbounds rowbounds, resulthandler resulthandler, cachekey key, boundsql boundsql) throws sqlexception {
// 先从一级缓存获取
list<e> list = (list<e>) localcache.getobject(key);
if (list != null) {
handlelocallycachedoutputparameters(ms, key, parameter, boundsql);
return list;
} else {
// 缓存未命中,执行数据库查询
list = queryfromdatabase(ms, parameter, rowbounds, resulthandler, key, boundsql);
return list;
}
}
} 2.2 二级缓存(全局缓存)
1. 核心特性
- 作用域:
namespace级别(跨会话共享)。 - 配置方式:
<cache eviction="lru" flushinterval="60000" size="512" readonly="true"/>
- 默认实现:
perpetualcache(可替换为redis、ehcache等)。
2. 缓存配置参数
| 参数 | 作用 |
|---|---|
eviction | 缓存淘汰策略(lru/fifo/soft/weak) |
flushinterval | 刷新间隔(毫秒,默认不刷新) |
size | 缓存最大容量(元素个数) |
readonly | 是否只读(true则返回缓存对象的引用,性能更高) |
2.3 缓存工作流程
关键注意点:
- 缓存失效:
增删改操作(insert/update/delete)默认会清空所在namespace的二级缓存。 - 嵌套查询:
嵌套查询(<association>、<collection>)可能导致二级缓存失效(取决于usecache属性)。
三、缓存集成与性能优化
3.1 第三方缓存集成(redis示例)
1. 添加依赖
<dependency>
<groupid>org.mybatis.caches</groupid>
<artifactid>mybatis-redis</artifactid>
<version>1.0.0-beta2</version>
</dependency> 2. 配置redis缓存
<cache type="org.mybatis.caches.redis.rediscache"/> <!-- redis.properties --> host=127.0.0.1 port=6379 timeout=2000
3.2 性能优化策略
1. 合理使用缓存级别
- 一级缓存:默认开启,适合短期高频查询(如同一请求内多次查询相同数据)。
- 二级缓存:需显式配置,适合全局共享且读多写少的数据(如字典表、配置信息)。
2. 缓存参数调优
<!-- 高并发场景优化配置 -->
<cache
eviction="lru"
flushinterval="300000" <!-- 5分钟刷新一次 -->
size="2048" <!-- 增大缓存容量 -->
readonly="true"/> <!-- 只读模式提升性能 --> 3. 避免缓存穿透与雪崩
- 缓存穿透:
查询不存在的数据导致每次都访问数据库,可通过布隆过滤器或缓存空值解决。 - 缓存雪崩:
大量缓存同时失效导致瞬间数据库压力剧增,可通过设置随机过期时间避免。
四、面试高频问题深度解析
4.1 基础概念类问题
q:mybatis动态sql与hibernate criteria api的区别?a:
| 维度 | mybatis动态sql | hibernate criteria api |
|---|---|---|
| sql控制 | 完全手动控制,灵活性高 | 通过api生成,灵活性低 |
| 学习成本 | 较低(熟悉xml标签即可) | 较高(需掌握对象化查询api) |
| 性能 | 接近原生sql,性能优化空间大 | 可能生成冗余sql,优化难度高 |
| 适用场景 | 复杂sql场景(如多表关联、嵌套查询) | 简单增删改查场景 |
q:mybatis一级缓存与二级缓存的区别?a:
| 特性 | 一级缓存 | 二级缓存 |
|---|---|---|
| 作用域 | sqlsession级别 | namespace级别 |
| 生命周期 | 会话关闭后失效 | 应用启动到关闭 |
| 默认开启 | 是 | 否 |
| 缓存共享 | 同一个会话内共享 | 跨会话共享 |
| 实现类 | perpetualcache | 可自定义(如rediscache) |
4.2 实现原理类问题
q:mybatis如何实现动态sql的条件判断?a:
- 通过ognl表达式计算条件(如
#{username} != null)。 - 解析为对应的
sqlnode实现类(如ifsqlnode)。 - 在sql执行时动态决定是否包含该sql片段。
q:二级缓存的嵌套查询会导致什么问题?如何解决?a:
- 问题:嵌套查询默认不使用二级缓存,可能导致重复查询数据库。
- 解决方案:
设置
usecache="true"和flushcache="false"。使用
<resultmap>的嵌套映射替代嵌套查询。
4.3 实战调优类问题
q:如何解决mybatis缓存与数据库一致性问题?a:
- 更新策略:
- 增删改操作后强制刷新缓存(默认行为)。
- 设置合理的缓存过期时间(如5分钟)。
- 读写分离场景:
- 主库写操作后立即刷新缓存。
- 从库读操作使用缓存,通过数据库主从同步保证最终一致性。
q:mybatis动态sql中<where>标签与<trim>标签的区别?a:
<where>:
自动添加where关键字,并剔除多余的and/or。<trim>:
可自定义前缀、后缀处理,如:更灵活,可替代<trim prefix="where" prefixoverrides="and |or "> ... </trim><where>标签。
总结:动态sql与缓存的最佳实践
动态sql设计原则
- 简洁优先:避免过度复杂的动态sql,优先使用
<if>、<where>等基础标签。 - 参数校验:在java代码中进行参数校验,避免在动态sql中处理复杂逻辑。
- sql片段复用:使用
<sql>标签定义公共sql片段,通过<include>复用。
缓存使用策略
- 读多写少场景:启用二级缓存,如字典表、配置信息。
- 写操作频繁场景:禁用二级缓存,避免频繁刷新影响性能。
- 分布式环境:使用redis等分布式缓存替代默认实现,保证跨节点缓存一致性。
通过系统化掌握mybatis动态sql与缓存机制的核心原理及最佳实践,面试者可在回答中精准匹配问题需求,例如分析“如何优化复杂查询性能”时,能结合动态sql优化与缓存策略,展现对持久层技术的深度理解与工程实践能力。
到此这篇关于mybatis 动态 sql 与缓存机制深度解析的文章就介绍到这了,更多相关mybatis 动态 sql内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论