找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2174

积分

0

好友

303

主题
发表于 前天 23:43 | 查看: 2| 回复: 0

一、MyBatis整体架构与设计模式续篇

在之前的文章中,我们探讨了MyBatis中的构建者模式、工厂模式和代理模式。本文将继续深入源码,剖析MyBatis里另外三种至关重要的设计模式:装饰器模式、模板方法模式和策略模式。正是这些模式的应用,赋予了MyBatis强大的功能扩展能力、性能优化潜力以及架构上的灵活性。

1.1 MyBatis整体架构回顾

MyBatis采用了经典的分层架构设计,从用户接口到数据源,主要包括以下几个层次:

  • 应用层:用户应用程序
  • 接口层:SqlSession、Mapper接口
  • 核心处理层:SQL解析、执行、结果映射
  • 基础支撑层:缓存、事务、类型处理、日志等
  • 数据层:数据源、连接池、JDBC驱动

本文将重点聚焦于装饰器、模板方法和策略模式在上述层次中的具体实现与协同。

MyBatis整体架构与设计模式分层图

1.2 本文涉及的设计模式

  1. 装饰器模式(Decorator Pattern):以CachingExecutorCache装饰器链为代表,动态增强对象功能。
  2. 模板方法模式(Template Method Pattern):体现在BaseExecutorBaseTypeHandler中,定义算法的稳定骨架。
  3. 策略模式(Strategy Pattern):如RoutingStatementHandler和不同的Executor类型,封装可互换的算法族。

二、装饰器模式(Decorator Pattern)

装饰器模式是一种结构型设计模式,其核心在于不改变原有对象结构的前提下,动态地为其添加额外的职责或行为。它通过将对象包裹在装饰器类中来实现功能的“即插即用”。

2.1 CachingExecutor

CachingExecutor是MyBatis中运用装饰器模式的典范,它为一个普通的Executor披上了“二级缓存”的外衣。

2.1.1 类定义核心

public class CachingExecutor implements Executor {
    // 关键:持有一个被装饰的Executor实例
    private final Executor delegate;
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    public CachingExecutor(Executor delegate) {
        this.delegate = delegate;
        // 防止重复装饰
        if (delegate instanceof CachingExecutor) {
            throw new IllegalArgumentException(“Cannot wrap a CachingExecutor“);
        }
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        Cache cache = ms.getCache();
        // 如果该MappedStatement配置了缓存
        if (cache != null) {
            flushCacheIfRequired(ms); // 按需清空缓存
            if (ms.isUseCache() && resultHandler == null) {
                ensureNoOutParams(ms, boundSql);
                // 先尝试从缓存中获取
                @SuppressWarnings(“unchecked“)
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    // 缓存未命中,委托给真正的Executor执行查询
                    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    // 将结果放入缓存
                    tcm.putObject(cache, key, list);
                }
                return list;
            }
        }
        // 未配置缓存,直接委托执行
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

    @Override
    public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        // 更新操作需要清空相关缓存
        flushCacheIfRequired(ms);
        return delegate.update(ms, parameterObject);
    }
}

CachingExecutor装饰器模式结构图

2.1.2 装饰器模式的优势

  1. 非侵入式扩展:无需修改SimpleExecutor等原有类,通过包装即可新增缓存功能,符合开闭原则。
  2. 动态组合:可以在运行时根据配置决定是否使用CachingExecutor,功能组合非常灵活。
  3. 职责单一CachingExecutor只关心缓存逻辑,delegate只关心基础SQL执行,各司其职。
  4. 嵌套增强:理论上可以多层装饰,实现功能的叠加(尽管MyBatis中CachingExecutor通常只装饰一层)。

2.2 Cache装饰器链

MyBatis的整个缓存系统都是装饰器模式的杰作。Cache接口拥有众多装饰器实现,它们可以像“俄罗斯套娃”一样层层嵌套,构建出功能强大的缓存链。

2.2.1 Cache接口

public interface Cache {
    String getId();
    void putObject(Object key, Object value);
    Object getObject(Object key);
    Object removeObject(Object key);
    void clear();
    int getSize();
    ReadWriteLock getReadWriteLock();
}

2.2.2 丰富的Cache装饰器实现

MyBatis提供了一系列Cache装饰器,每个都负责一项特定的功能:

  • PerpetualCache:基础实现,内部就是一个HashMap
  • LruCache:装饰后具备LRU(最近最少使用)淘汰策略。
  • FifoCache:装饰后具备FIFO(先进先出)淘汰策略。
  • BlockingCache:防止缓存击穿的阻塞装饰器。
  • LoggingCache:用于统计缓存命中率的日志装饰器。
  • ScheduledCache:定时清理过期货的装饰器。
  • SerializedCache:将值序列化后存储的装饰器,保证线程安全或跨会话。
  • SynchronizedCache:提供同步控制的装饰器。
  • TransactionalCache:支持事务的缓存装饰器,事务提交后才真正写入缓存。

2.2.3 Cache装饰器链的构建者

CacheBuilder类负责将这些装饰器组装成链:

public class CacheBuilder {
    private String id;
    private Class<? extends Cache> implementation;
    private List<Class<? extends Cache>> decorators;

    public Cache build() {
        setDefaultImplementations();
        // 1. 创建基础Cache(如PerpetualCache)
        Cache cache = newBaseCacheInstance(implementation, id);
        setCacheProperties(cache);
        // 2. 按顺序应用装饰器
        for (Class<? extends Cache> decorator : decorators) {
            cache = newCacheDecoratorInstance(decorator, cache);
            setCacheProperties(cache);
        }
        return cache; // 返回装饰完成的最终Cache对象
    }
}

Cache装饰器链结构图

2.3 装饰器模式的应用场景与对比

在MyBatis中,装饰器模式主要应用于功能增强场景,例如为Executor加缓存、为Cache加各种策略和特性。

与继承相比,装饰器模式的优势明显:

对比项 装饰器模式 继承
扩展方式 动态组合,灵活 静态编译,固定
灵活性 高,可运行时增减功能 低,编译时确定
类数量 功能类线性增加,组合更多样 容易导致类爆炸(多种功能组合需多继承)
设计原则 更好遵循“组合优于继承”原则 容易破坏封装性

三、模板方法模式(Template Method Pattern)

模板方法模式是一种行为型设计模式。它在父类中定义了一个操作算法的骨架,而将一些步骤延迟到子类中实现。这使得子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。

3.1 BaseExecutor

BaseExecutor是模板方法模式在MyBatis中的核心体现。它定义了SQL执行的标准流程,并将具体的数据库交互细节留给子类完成。

3.1.1 BaseExecutor核心代码(模板方法)

public abstract class BaseExecutor implements Executor {
    // 模板方法:定义了查询的固定算法骨架
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        // ... 错误检查、清空本地缓存等前置操作 ...
        List<E> list;
        try {
            queryStack++;
            // 步骤1: 检查一级缓存(本地缓存)
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
                // 命中缓存,处理输出参数
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // 步骤2: 缓存未命中,调用抽象方法 `doQuery` 查询数据库
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            queryStack--;
        }
        // ... 延迟加载等后置操作 ...
        return list;
    }

    // 关键抽象方法:由子类实现具体的数据库查询逻辑
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter,
                                          RowBounds rowBounds, ResultHandler resultHandler,
                                          BoundSql boundSql) throws SQLException;
    // 抽象方法:由子类实现具体的更新逻辑
    protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
}

3.1.2 BaseExecutor的子类实现(具体步骤)

不同的子类通过实现 doQuerydoUpdate 来提供不同的执行策略,这正是模板方法模式的威力所在。

  • SimpleExecutor:最简单的实现,每次执行都会创建新的Statement对象,用完后立即关闭。
  • ReuseExecutor:重用执行器。它会缓存预处理过的Statement对象,当执行相同的SQL时直接复用,减少了创建和解析的开销。
  • BatchExecutor:批量执行器。它将多个更新操作缓存起来,最后通过executeBatch()一次性提交,极大提升了批量操作的性能。

BaseExecutor模板方法模式示意图

3.2 BaseTypeHandler

BaseTypeHandler 是另一个应用模板方法模式的典型例子,它定义了Java类型与JDBC类型之间转换的标准流程。

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
    // 模板方法:设置非空参数的标准流程(可能包含空值判断等)
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        setParameter(ps, i, parameter, jdbcType); // 调用具体的设置方法
    }
    // 模板方法:获取可能为空结果的标准流程(处理wasNull)
    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        T result = getNullableResult(rs, columnName); // 调用具体的获取方法
        return rs.wasNull() ? null : result; // 统一的空值处理
    }
    // 抽象方法:由子类(如StringTypeHandler, IntegerTypeHandler)实现具体类型转换
    public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
    public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
}

3.3 模板方法模式的优势

  1. 代码复用:将公共的算法逻辑提升到父类,避免了子类中的代码重复。
  2. 框架稳定:保证了算法的主流程不可变,提高了系统的稳定性和可预测性。
  3. 扩展便捷:子类只需要关注自己需要变化的步骤,易于扩展新的行为。
  4. 便于维护:算法结构的修改只需要在父类中进行,符合“开放-封闭”原则。

四、策略模式(Strategy Pattern)

策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。

4.1 RoutingStatementHandler

RoutingStatementHandler 是一个策略上下文(Context),它本身不处理具体逻辑,而是根据MappedStatement的配置,将请求路由到不同的具体策略(StatementHandler)中去。

4.1.1 RoutingStatementHandler源码

public class RoutingStatementHandler implements StatementHandler {
    private final StatementHandler delegate; // 持有一个具体的策略

    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 根据StatementType选择具体的策略
        switch (ms.getStatementType()) {
            case STATEMENT:
                delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case PREPARED: // 最常见的情况
                delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case CALLABLE:
                delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            default:
                throw new ExecutorException(“Unknown statement type: “ + ms.getStatementType());
        }
    }
    // 所有方法都委托给具体的策略对象执行
    @Override
    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        return delegate.prepare(connection, transactionTimeout);
    }
    // ... 其他委托方法
}

4.2 ExecutorType策略

在创建SqlSession时,我们可以指定ExecutorType,这同样是策略模式的应用。ConfigurationnewExecutor 方法充当了策略工厂和上下文。

public class Configuration {
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        Executor executor;
        // 根据类型选择策略
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction); // 默认策略
        }
        // 装饰器模式再次介入:如果启用缓存,用CachingExecutor装饰
        if (cacheEnabled) {
            executor = new CachingExecutor(executor);
        }
        // 代理模式介入:应用所有插件
        return (Executor) interceptorChain.pluginAll(executor);
    }
}

4.2.2 三种Executor策略对比

Executor类型 核心特点 适用场景 性能特点
SimpleExecutor 每次执行都创建新Statement,执行完毕立即关闭。 通用场景,单条操作。 中等,有重复创建开销。
ReuseExecutor 在会话内复用相同SQL的Statement对象。 短时间内需要反复执行相同SQL的场景。 较好,减少了创建和预编译开销。
BatchExecutor 缓存多个更新语句,最后统一执行executeBatch() 大批量插入、更新、删除操作。 最佳(批量场景),极大减少网络IO。

Executor策略选择流程图

4.3 策略模式的优势与对比

策略模式的优势

  1. 算法独立:策略类之间彼此独立,新增或修改算法不影响客户端。
  2. 消除条件判断:避免了大量的if-elseswitch语句,代码更清晰。
  3. 便于测试:每个策略都可以单独进行单元测试。
策略模式 vs 模板方法模式 对比项 策略模式 模板方法模式
关系 组合关系(Has-a) 继承关系(Is-a)
重点 封装可互换的完整算法 定义算法的骨架,部分步骤可变
灵活性 更高,运行时动态替换策略 稍低,编译时确定子类关系

五、设计模式的综合应用

在MyBatis的实际运行中,这些设计模式并非孤立存在,而是精妙地协同工作,共同构建了其稳定而灵活的内核。

5.1 一个SQL的执行旅程

以一个简单的查询为例,其执行过程融合了多种模式:

  1. 策略模式:根据ExecutorType选择具体的Executor(如SimpleExecutor)。
  2. 装饰器模式:如果配置了二级缓存,CachingExecutor会装饰选中的Executor
  3. 代理模式:通过Plugin机制(动态代理),为Executor挂载拦截器链。
  4. 模板方法模式:执行查询时,进入BaseExecutor定义的模板流程(先查一级缓存)。
  5. 策略模式:在BaseExecutor内部,调用子类实现的doQuery方法(具体策略)。
  6. 策略模式doQuery中创建StatementHandler时,由RoutingStatementHandler根据SQL类型路由到PreparedStatementHandler等具体策略。

5.2 Cache的构建旅程

// 这个构建过程是装饰器模式与工厂模式的结合
Cache cache = new CacheBuilder(“myCache”)
    .implementation(PerpetualCache.class) // 基础组件
    .addDecorator(LruCache.class)         // 装饰:LRU淘汰
    .addDecorator(LoggingCache.class)     // 装饰:命中率日志
    .addDecorator(SynchronizedCache.class)// 装饰:线程安全
    .build(); // 工厂方法返回装饰链的最终产物

MyBatis中设计模式的综合应用关系图

六、总结与最佳实践

6.1 关键要点回顾

  • 装饰器模式:用于功能增强。CachingExecutorCache装饰器链是典型代表,实现了功能的动态、透明叠加。
  • 模板方法模式:用于定义算法骨架。BaseExecutorBaseTypeHandler通过固定主流程、开放具体步骤,实现了代码的高度复用和架构稳定。
  • 策略模式:用于封装算法族。RoutingStatementHandlerExecutorType让不同的SQL执行策略和语句处理策略可以灵活替换。

6.2 如何选择设计模式?

  • 需要动态、透明地添加功能 → 优先考虑装饰器模式
  • 算法整体流程固定,但部分步骤需变化 → 使用模板方法模式
  • 有多种算法实现,且需要在运行时切换 → 采用策略模式
  • 面对复杂对象的构建 → 使用建造者模式(MyBatis配置解析)。
  • 需要控制对象访问,增强功能 → 使用代理模式(MyBatis插件)。

6.3 避免过度设计

设计模式是解决特定问题的优秀方案,但切忌“为模式而模式”。应始终以代码的简洁性、可读性和实际需求为首要目标。在初期,可以优先实现功能;在重构或需求明确扩展时,再引入合适的设计模式进行优化。

通过对MyBatis源码中这些经典设计模式的剖析,我们不仅能更好地理解和使用MyBatis,更能深刻体会如何将这些模式应用于自己的程序开发中,设计出高内聚、低耦合、易扩展的优雅代码。希望本文能为你深入理解MyBatis及设计模式带来帮助。如果你想了解更多关于框架源码或系统设计的深度内容,欢迎在云栈社区交流探讨。




上一篇:SpringBoot官宣停止维护3.2.x-3.4.x:4.0升级指南及避坑要点
下一篇:Kubernetes 1.33 生产环境节点操作:工作节点安全下线与标准新增流程
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-12 01:10 , Processed in 0.201679 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表