MyBatis 核心组件剖析:架构、协作与源码解读

发布于:2025-05-27 ⋅ 阅读:(17) ⋅ 点赞:(0)

MyBatis 作为一款经典的持久层框架,其设计精妙之处在于通过几个核心组件的协作,将 SQL 操作与 Java 对象优雅地结合起来。本文将深入剖析 MyBatis 的核心组件,包括它们的作用、相互关系以及底层实现原理。

1.MyBatis 核心组件概览

MyBatis 的核心组件主要包括以下几个部分:

  1. SqlSessionFactoryBuilder:负责从 XML 配置文件或 Java 代码中构建 SqlSessionFactory。
  2. SqlSessionFactory:工厂模式的实现,负责创建 SqlSession 实例。
  3. SqlSession:提供了执行 SQL 命令的方法,是应用与 MyBatis 之间的主要编程接口。
  4. Executor:SqlSession 内部使用 Executor 来执行 SQL 语句。
  5. Mapper 接口与映射文件:定义 SQL 语句与 Java 方法的映射关系。
  6. TypeHandler:负责 Java 类型与 JDBC 类型之间的转换。
  7. ParameterHandler:处理 SQL 参数。
  8. ResultSetHandler:处理 SQL 查询结果集。

这些组件相互协作,形成了 MyBatis 的核心架构。下面我们通过一个整体架构图来直观地了解它们之间的关系:

+-------------------+     +-------------------+     +-------------------+
|                   |     |                   |     |                   |
|  SqlSessionFactory |<--->|     SqlSession    |<--->|      MapperProxy  |
|                   |     |                   |     |                   |
+-------------------+     +-------------------+     +-------------------+
          ^                      |     ^                      ^
          |                      |     |                      |
          |                      v     |                      |
+-------------------+     +-------------------+     +-------------------+
|                   |     |                   |     |                   |
|    Configuration  |     |     Executor      |     |   MapperRegistry  |
|                   |     |                   |     |                   |
+-------------------+     +-------------------+     +-------------------+
          ^                      |     ^
          |                      |     |
          |                      v     |
+-------------------+     +-------------------+
|                   |     |                   |
|   MappedStatement |     |    TypeHandler    |
|                   |     |                   |
+-------------------+     +-------------------+

2.核心组件详解

1. SqlSessionFactoryBuilder

作用:SqlSessionFactoryBuilder 是 MyBatis 的入口点,负责解析配置文件并构建 SqlSessionFactory 实例。

源码关键代码

public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
    }
    
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // 解析 XML 配置文件
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 构建 Configuration 对象
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException e) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }
    
    public SqlSessionFactory build(Configuration config) {
        // 创建 DefaultSqlSessionFactory 实例
        return new DefaultSqlSessionFactory(config);
    }
}

使用示例

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

2. SqlSessionFactory

作用:SqlSessionFactory 是一个工厂接口,负责创建 SqlSession 实例。它是线程安全的,可以被多个线程共享。

核心方法

  • openSession():创建一个新的 SqlSession 实例。
  • openSession(boolean autoCommit):创建一个带有自动提交功能的 SqlSession。
  • openSession(ExecutorType execType):创建一个指定执行器类型的 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;
    
    public DefaultSqlSessionFactory(Configuration configuration) {
        this.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 {
            final Environment environment = configuration.getEnvironment();
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 创建执行器
            final Executor executor = configuration.newExecutor(tx, execType);
            // 创建 DefaultSqlSession 实例
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            closeTransaction(tx); // may have fetched a connection so lets call close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
}

3. SqlSession

作用:SqlSession 是 MyBatis 的核心接口,提供了执行 SQL 命令的方法。它是线程不安全的,应该在方法内部使用,用完后及时关闭。

核心方法

  • selectOne(String statement, Object parameter):查询单个结果。
  • selectList(String statement, Object parameter):查询多个结果。
  • insert(String statement, Object parameter):插入数据。
  • update(String statement, Object parameter):更新数据。
  • delete(String statement, Object parameter):删除数据。
  • commit():提交事务。
  • rollback():回滚事务。
  • getMapper(Class<T> type):获取 Mapper 接口的代理对象。

源码关键代码

public interface SqlSession extends Closeable {
    <T> T selectOne(String statement);
    <T> T selectOne(String statement, Object parameter);
    <E> List<E> selectList(String statement);
    <E> List<E> selectList(String statement, Object parameter);
    int insert(String statement);
    int insert(String statement, Object parameter);
    int update(String statement);
    int update(String statement, Object parameter);
    int delete(String statement);
    int delete(String statement, Object parameter);
    void commit();
    void commit(boolean force);
    void rollback();
    void rollback(boolean force);
    <T> T getMapper(Class<T> type);
    Configuration getConfiguration();
    Connection getConnection();
}

public class DefaultSqlSession implements SqlSession {
    private final Configuration configuration;
    private final Executor executor;
    private final boolean autoCommit;
    private boolean dirty;
    
    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.autoCommit = autoCommit;
        this.dirty = false;
    }
    
    @Override
    public <T> T selectOne(String statement, Object parameter) {
        // Popular vote was to return null on 0 results and throw exception on too many.
        List<T> list = this.selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }
    
    @Override
    public int insert(String statement, Object parameter) {
        return update(statement, parameter);
    }
    
    @Override
    public int update(String statement, Object parameter) {
        try {
            dirty = true;
            // 通过执行器执行更新操作
            MappedStatement ms = configuration.getMappedStatement(statement);
            return executor.update(ms, wrapCollection(parameter));
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
    
    @Override
    public <T> T getMapper(Class<T> type) {
        // 通过 Configuration 获取 Mapper 代理
        return configuration.getMapper(type, this);
    }
}

4. Executor

作用:Executor 是 MyBatis 的执行器,负责 SQL 语句的执行和缓存的维护。

主要实现类

  • SimpleExecutor:简单执行器,每次执行都会创建新的预处理语句。
  • ReuseExecutor:可重用执行器,会重用预处理语句。
  • BatchExecutor:批处理执行器,用于批量操作。
  • CachingExecutor:缓存执行器,用于二级缓存的管理。

源码关键代码

public interface Executor {
    ResultHandler NO_RESULT_HANDLER = null;
    
    int update(MappedStatement ms, Object parameter) throws SQLException;
    
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
    
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
    
    List<BatchResult> flushStatements() throws SQLException;
    
    void commit(boolean required) throws SQLException;
    
    void rollback(boolean required) throws SQLException;
    
    CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
    
    boolean isCached(MappedStatement ms, CacheKey key);
    
    void clearLocalCache();
    
    void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
    
    Transaction getTransaction();
    
    void close(boolean forceRollback);
    
    boolean isClosed();
    
    void setExecutorWrapper(Executor executor);
}

public abstract class BaseExecutor implements Executor {
    // 实现 Executor 接口的方法
    // 包含事务管理、缓存管理等通用逻辑
}

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, ms.getStatementLog());
            // 执行更新
            return handler.update(stmt);
        } finally {
            closeStatement(stmt);
        }
    }
    
    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 准备语句
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 执行查询
            return handler.query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
}

5. Mapper 接口与映射文件

作用:Mapper 接口定义了数据库操作的方法,映射文件(或注解)定义了这些方法对应的 SQL 语句。

映射文件示例

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    
    <select id="getUserById" parameterType="int" resultType="com.example.entity.User">
        SELECT * FROM users WHERE id = #{id}
    </select>
    
    <insert id="insertUser" parameterType="com.example.entity.User">
        INSERT INTO users (username, email, age)
        VALUES (#{username}, #{email}, #{age})
    </insert>
    
    <!-- 其他 SQL 映射... -->
</mapper>

Mapper 接口示例

package com.example.mapper;

import com.example.entity.User;
import java.util.List;

public interface UserMapper {
    User getUserById(int id);
    void insertUser(User user);
    void updateUser(User user);
    void deleteUser(int id);
    List<User> getAllUsers();
}

MapperProxy 实现

MyBatis 使用动态代理实现 Mapper 接口:

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -642454039855972983L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethodInvoker> methodCache;
    
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else {
                return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
    
    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            return methodCache.computeIfAbsent(method, m -> {
                if (m.isDefault()) {
                    try {
                        if (privateLookupInMethod == null) {
                            return new DefaultMethodInvoker(getMethodHandleJava8(method));
                        } else {
                            return new DefaultMethodInvoker(getMethodHandleJava9(method));
                        }
                    } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                             | NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    }
                } else {
                    // 创建 MapperMethod 实例
                    return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
                }
            });
        } catch (RuntimeException re) {
            Throwable cause = re.getCause();
            throw cause == null ? re : cause;
        }
    }
}

3.核心组件协作流程

下面通过一个查询操作的时序图,展示 MyBatis 核心组件的协作流程:

Client                SqlSession        Executor    MappedStatement    JDBC
  |                      |                |              |               |
  |  getUserById(1)      |                |              |               |
  |--------------------->|                |              |               |
  |                      |  getMappedStatement("...") |               |
  |                      |---------------------------->|               |
  |                      |                |              |               |
  |                      |  query(ms, 1)  |              |               |
  |                      |-------------->|              |               |
  |                      |                |  getBoundSql() |               |
  |                      |                |------------->|               |
  |                      |                |              |               |
  |                      |                |  prepareStatement()          |
  |                      |                |----------------------------->|
  |                      |                |              |  Connection   |
  |                      |                |              |<-------------|
  |                      |                |              |               |
  |                      |                |  executeQuery()              |
  |                      |                |----------------------------->|
  |                      |                |              |  ResultSet    |
  |                      |                |<-------------|               |
  |                      |                |              |               |
  |                      |                |  handleResultSets()          |
  |                      |                |<-------------|               |
  |                      |<---------------|              |               |
  |<---------------------|                |              |               |

4.总结

通过深入剖析 MyBatis 的核心组件,我们可以看到其设计的精妙之处:

  1. 工厂模式:SqlSessionFactoryBuilder 构建 SqlSessionFactory,SqlSessionFactory 创建 SqlSession。
  2. 代理模式:MapperProxy 实现 Mapper 接口的动态代理,将方法调用转换为 SQL 执行。
  3. 策略模式:Executor 提供多种执行策略(SimpleExecutor、ReuseExecutor、BatchExecutor)。
  4. 模板方法模式:BaseExecutor 实现了通用的执行逻辑,具体实现由子类完成。

这种设计使得 MyBatis 既保持了灵活性,又提供了简单易用的 API。开发者可以通过配置文件或注解定义 SQL 映射,然后通过 Mapper 接口进行数据库操作,无需编写繁琐的 JDBC 代码。

在实际开发中,理解 MyBatis 的核心组件和工作原理,有助于我们更好地使用 MyBatis 进行开发,也能够在遇到问题时更快地定位和解决问题。


网站公告

今日签到

点亮在社区的每一天
去签到