基于MyBatis实现ORM框架:模板方法+代理+工厂+建造者

说实话,很多Java开发者天天用MyBatis,但真问起它底层用了哪些设计模式,能说全的人不多。我当年带团队做ORM框架选型时,专门把MyBatis源码翻了一遍,发现它简直就是设计模式的教科书级应用。今天咱们就拆开看看,这四个模式是怎么配合的。

一、整体架构:四个模式各司其职

先画张图,让你对整体有个感觉。MyBatis的核心流程其实不复杂:加载配置→解析SQL→执行SQL→映射结果。但每个环节都藏着设计模式的影子。

MyBatis核心流程与设计模式 配置加载 建造者模式 SQL解析 模板方法模式 执行SQL 代理模式 结果 映射 🏗️ 建造者模式 - SqlSessionFactoryBuilder • 解析mybatis-config.xml和Mapper XML • 构建Configuration对象(200+属性) • 支持链式调用:.build(inputStream) • 屏蔽复杂构建过程,对外提供简洁API 📋 模板方法模式 - BaseExecutor • 定义执行SQL的骨架:doUpdate/doQuery • 子类实现:SimpleExecutor、BatchExecutor • 一级缓存逻辑在父类统一处理 • 子类只需关注JDBC操作细节 🔄 代理模式 - MapperProxy • JDK动态代理生成Mapper接口实现 • 拦截所有方法调用,路由到SqlSession • 实现:MapperProxyFactory + MapperProxy • 用户只需定义接口,无需写实现类 🏭 工厂模式 - SqlSessionFactory • 生产SqlSession对象(数据库会话) • 默认实现:DefaultSqlSessionFactory • 管理事务、连接池、执行器类型 • 配合建造者模式完成对象创建

你看,这四个模式不是孤立的,它们像齿轮一样咬合在一起。我个人习惯把这种组合叫做「模式链」——每个模式解决一个特定环节的问题,串联起来就是完整的ORM框架。

二、建造者模式:SqlSessionFactoryBuilder

先说说建造者模式。MyBatis的配置解析有多复杂?我数过,Configuration类有200多个属性。如果直接用构造器,那参数列表得写到天荒地老。

核心思想:将复杂对象的构建与表示分离。同一个构建过程可以创建不同的表示。

看看MyBatis是怎么做的:

// 用户视角:一行代码搞定
SqlSessionFactory factory = new SqlSessionFactoryBuilder()
    .build(Resources.getResourceAsStream("mybatis-config.xml"));

// 内部实现:建造者模式
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
    }
    
    public SqlSessionFactory build(InputStream inputStream, 
                                   String environment, 
                                   Properties properties) {
        try {
            // 1. 创建XMLConfigBuilder(解析器)
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, 
                                                          environment, 
                                                          properties);
            // 2. 解析配置,构建Configuration对象
            Configuration config = parser.parse();
            // 3. 返回工厂
            return build(config);
        } catch (Exception e) {
            throw new RuntimeException("Error building SqlSessionFactory.", e);
        }
    }
    
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
}

这里有个细节:XMLConfigBuilder其实也是个建造者,它负责把XML配置一步步解析成Configuration对象。我在项目中遇到过有人试图自己写配置解析,结果搞了三天还没搞定事务管理器的配置——嗯,这就是为什么我们要用现成的建造者。

避坑指南:我曾经见过有人直接new DefaultSqlSessionFactory(config),跳过了建造者。结果配置里有个typeAliases没解析,运行时疯狂报ClassNotFoundException。记住:建造者模式不只是为了「好看」,它保证了对象创建的正确性。

三、模板方法模式:BaseExecutor

接下来是模板方法模式。MyBatis支持三种执行器:SimpleExecutor(简单)、ReuseExecutor(复用Statement)、BatchExecutor(批量)。但它们执行SQL的流程是一样的:

  1. 获取数据库连接
  2. 创建Statement
  3. 设置参数
  4. 执行SQL
  5. 处理结果
  6. 关闭资源

这不就是模板方法模式的典型场景吗?

public abstract class BaseExecutor implements Executor {
    // 模板方法:定义执行骨架
    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        // 1. 检查一级缓存
        if (ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        // 2. 执行子类实现的doUpdate
        return doUpdate(ms, parameter);
        // 3. 缓存处理(由父类统一管理)
    }
    
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, 
                              RowBounds rowBounds, ResultHandler resultHandler) 
            throws SQLException {
        // 1. 从缓存获取
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        List<E> list = localCache.getObject(key);
        if (list != null) {
            return list; // 缓存命中
        }
        // 2. 子类实现查询
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        // 3. 存入缓存
        localCache.putObject(key, list);
        return list;
    }
    
    // 抽象方法:子类实现
    protected abstract int doUpdate(MappedStatement ms, Object parameter) 
            throws SQLException;
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter,
                                           RowBounds rowBounds, 
                                           ResultHandler resultHandler,
                                           BoundSql boundSql) 
            throws SQLException;
}

// 子类实现
public class SimpleExecutor extends BaseExecutor {
    @Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, 
                parameter, RowBounds.DEFAULT, null, null);
            stmt = prepareStatement(handler);
            return handler.update(stmt);
        } finally {
            closeStatement(stmt);
        }
    }
}

你想想看,如果没有模板方法模式,每个执行器都要自己写缓存逻辑,那得多容易出bug?我记得有一次排查线上问题,发现某个查询没走缓存,就是因为自定义执行器忘了调父类的缓存方法。模板方法模式强制子类遵循统一流程,这就是它的价值。

注意:模板方法模式的关键是「好莱坞原则」——不要调用我们,我们会调用你。子类只负责实现细节,流程控制权在父类手里。如果你发现子类需要重写父类的流程,那说明模板方法设计得有问题。

四、代理模式:MapperProxy

代理模式是MyBatis最让开发者感到「神奇」的地方。你明明只定义了一个接口,没有写实现类,但MyBatis就能帮你执行SQL。秘密就在MapperProxy里。

// 用户定义的接口
public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(int id);
}

// MyBatis内部:JDK动态代理
public class MapperProxy<T> implements InvocationHandler {
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable {
        // 如果是Object的方法,直接调用
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        // 核心:将接口方法调用转为SqlSession操作
        return sqlSession.getMapper(mapperInterface)
            .selectOne(method.getName(), args);
    }
}

// 工厂类:创建代理对象
public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    
    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
        return (T) Proxy.newProxyInstance(
            mapperInterface.getClassLoader(),
            new Class[]{mapperInterface},
            mapperProxy
        );
    }
}

说白了,代理模式就是「中间人」。你调用userMapper.selectById(1),实际上被MapperProxy拦截,转成了sqlSession.selectOne("UserMapper.selectById", 1)。我刚开始学MyBatis时也觉得这很黑魔法,后来看了源码才发现,其实就是InvocationHandler的invoke方法做了路由。

个人经验:代理模式有个坑——如果接口方法有重载,invoke里要根据方法名+参数类型来路由。MyBatis 3.5之后改进了这个逻辑,但早期版本确实有人踩过这个坑。所以写Mapper接口时,尽量别重载方法名。

五、工厂模式:SqlSessionFactory

工厂模式在MyBatis里相对简单,但很重要。SqlSessionFactory负责创建SqlSession,而SqlSession是数据库会话的抽象。

public interface SqlSessionFactory {
    SqlSession openSession();
    SqlSession openSession(boolean autoCommit);
    SqlSession openSession(Connection connection);
    SqlSession openSession(TransactionIsolationLevel level);
    SqlSession openSession(ExecutorType execType);
}

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private final Configuration configuration;
    
    @Override
    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), 
                                         null, false);
    }
    
    private SqlSession openSessionFromDataSource(ExecutorType execType, 
                                                  TransactionIsolationLevel level,
                                                  boolean autoCommit) {
        Transaction tx = null;
        try {
            // 1. 获取环境配置
            Environment environment = configuration.getEnvironment();
            // 2. 创建事务工厂
            TransactionFactory transactionFactory = 
                environment.getTransactionFactory();
            // 3. 创建事务
            tx = transactionFactory.newTransaction(
                environment.getDataSource(), level, autoCommit);
            // 4. 创建执行器
            Executor executor = configuration.newExecutor(tx, execType);
            // 5. 返回SqlSession
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            closeTransaction(tx);
            throw new RuntimeException("Error opening session.", e);
        }
    }
}

工厂模式的好处是:你不需要知道SqlSession是怎么创建的。要事务?要自动提交?要批量执行?工厂方法帮你搞定。我见过有人直接new DefaultSqlSession(...),结果参数传错导致连接泄漏——这就是不用工厂的代价。

六、四个模式的协作关系

最后,咱们看看这四个模式是怎么配合的:

阶段 设计模式 核心类 职责
启动阶段 建造者模式 SqlSessionFactoryBuilder 解析配置,构建Configuration
工厂阶段 工厂模式 SqlSessionFactory 生产SqlSession对象
执行阶段 模板方法模式 BaseExecutor 定义SQL执行骨架
调用阶段 代理模式 MapperProxy 接口方法路由到SQL执行

整个流程是这样的:

  1. 启动时,建造者模式解析XML,构建出Configuration对象
  2. 通过工厂模式的SqlSessionFactory,创建SqlSession
  3. 调用Mapper接口时,代理模式拦截方法调用
  4. 代理转发给SqlSession,最终由模板方法模式的Executor执行SQL

你看,每个模式各司其职,又环环相扣。这就是设计模式的魅力——不是炫技,而是解决实际问题。我做了十几年架构,越来越觉得:设计模式不是背出来的,是用出来的。当你真正理解了每个模式解决什么问题,自然就知道什么时候该用、怎么组合。

总结一下:MyBatis的ORM实现,本质上是「建造者搭骨架、工厂造对象、代理做路由、模板定流程」。这四个模式缺一不可,少了任何一个,框架的扩展性、易用性都会大打折扣。

嗯,今天就聊到这儿。设计模式这东西,光看不行,得动手。建议你找个周末,把MyBatis源码拉下来,对着这四个模式逐行看一遍。相信我,看完之后你对设计模式的理解会上一个台阶。