1. 问题背景:MyBatis源码中的条件处理差异
第一次阅读MyBatis源码时,很多开发者都会发现一个有趣的现象:我们自己写的业务代码里充斥着各种if-else条件判断,但MyBatis的核心源码中却很少见到这种传统分支结构。这种差异背后隐藏着框架设计的重要思想。
以动态SQL处理为例,当我们需要根据不同条件拼接SQL语句时,新手可能会写出这样的代码:
if (param.getUsername() != null) { sql += " AND username = '" + param.getUsername() + "'"; } if (param.getStartTime() != null) { sql += " AND create_time >= " + param.getStartTime(); } // 更多条件判断...而MyBatis内部处理同样需求时,采用的是完全不同的实现方式。这种差异不是偶然的,而是框架设计者有意为之的架构选择。
2. 设计模式的应用:为什么避免if-else
2.1 策略模式替代条件分支
MyBatis大量使用策略模式来处理原本需要if-else的场景。以SQL语句生成器为例,框架定义了SqlNode接口,不同节点类型实现各自的apply方法:
public interface SqlNode { boolean apply(DynamicContext context); } // 例如IfSqlNode的实现 public class IfSqlNode implements SqlNode { private ExpressionEvaluator evaluator; private String test; private SqlNode contents; @Override public boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; } }这种设计将条件判断封装在各个策略实现中,外部只需调用统一的apply接口,避免了复杂的条件嵌套。
2.2 责任链模式处理流程
MyBatis的插件机制采用责任链模式实现,InterceptorChain维护了所有拦截器:
public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<>(); public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } }相比用if判断每个拦截条件,这种设计使得拦截器的添加和移除更加灵活,完全符合开闭原则。
3. 动态SQL的实现原理
3.1 OGNL表达式引擎
MyBatis使用OGNL(Object-Graph Navigation Language)处理动态SQL中的条件判断:
<select id="findUsers"> SELECT * FROM users <where> <if test="name != null"> AND name = #{name} </if> <if test="age != null"> AND age = #{age} </if> </where> </select>在解析阶段,这些if标签会被转换为IfSqlNode对象,test属性作为OGNL表达式在运行时求值。
3.2 组合模式构建SQL树
整个动态SQL被解析为一棵由SqlNode组成的语法树:
MixedSqlNode ├── StaticTextSqlNode ("SELECT * FROM users") └── WhereSqlNode ├── IfSqlNode (name != null) │ └── TextSqlNode ("AND name = ?") └── IfSqlNode (age != null) └── TextSqlNode ("AND age = ?")这种结构使得SQL的生成过程变成对树的遍历,完全避免了过程式代码中的条件判断。
4. 源码中的设计取舍
4.1 可扩展性优先
MyBatis作为框架,必须考虑各种扩展场景。例如XML配置的解析使用不同的Handler:
public class XMLMapperBuilder { private void bindMapperForNamespace() { // 使用不同的Builder处理不同元素 configurationElement(parser.evalNode("/mapper")); } private void configurationElement(XNode context) { buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } }这种设计允许在不修改核心代码的情况下,通过新增Handler支持新的XML元素。
4.2 性能优化考虑
在SQL参数处理时,MyBatis使用类型处理器注册表:
public final class TypeHandlerRegistry { private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class); private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>(); public <T> TypeHandler<T> getTypeHandler(Class<T> type, JdbcType jdbcType) { // 直接从Map获取处理器 } }相比链式if判断,这种注册表方式在大量类型处理时性能更优。
5. 实际开发中的启示
5.1 何时该用if-else
虽然MyBatis源码避免if-else,但在业务代码中合理使用条件判断是必要的:
- 简单业务逻辑:当条件分支少于3个时
- 非扩展性需求:确定不会新增条件的情况
- 性能敏感路径:需要极致优化的代码段
5.2 何时考虑重构
当出现以下情况时,建议参考MyBatis的设计模式:
- 嵌套层级超过3层
- 经常需要新增条件分支
- 相同条件判断在多处重复
- 需要动态添加/移除处理逻辑
5.3 重构示例
将传统条件判断改为策略模式:
重构前:
public String generateReport(ReportType type) { if (type == ReportType.EXCEL) { return generateExcel(); } else if (type == ReportType.PDF) { return generatePDF(); } else if (type == ReportType.HTML) { return generateHTML(); } throw new UnsupportedOperationException(); }重构后:
public interface ReportGenerator { boolean supports(ReportType type); String generate(); } public class ReportGeneratorFactory { private List<ReportGenerator> generators; public String generate(ReportType type) { return generators.stream() .filter(g -> g.supports(type)) .findFirst() .orElseThrow(UnsupportedOperationException::new) .generate(); } }6. 常见问题与解决方案
6.1 动态SQL安全问题
使用${}导致的SQL注入问题:
<!-- 不安全的写法 --> <select id="findByTable"> SELECT * FROM ${tableName} </select> <!-- 安全的写法 --> <select id="findByTable"> SELECT * FROM <choose> <when test="tableName == 'users'">users</when> <when test="tableName == 'orders'">orders</when> <otherwise>products</otherwise> </choose> </select>6.2 复杂条件处理
对于特别复杂的条件逻辑,可以考虑:
- 使用