【原理】MyBatis工作流程

95 阅读8分钟

原文链接:mp.weixin.qq.com/s?__biz=MzU…

MyBatis的功能架构

MyBatis功能架构.png

我们一般把Mybatis的功能架构分为三层:

  • API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。

  • 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。

  • 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

MyBatis工作原理

MyBatis工作原理可以分为两大步:生成会话工厂、会话运行。

MyBatis工作原理.png

构建会话工厂

构造会话工厂.png 构造会话工厂也可以分为两步:

  • 获取配置,获取配置这一步经过了几步转化,最终由生成了一个配置类Configuration实例,这个配置类实例非常重要,主要作用包括:

    • 读取配置文件,包括基础配置文件和映射文件

    • 初始化基础配置,比如MyBatis的别名,还有其它的一些重要的类对象,像插件、映射器、ObjectFactory等等

    • 提供一个单例,作为会话工厂构建的重要参数

    • 它的构建过程也会初始化一些环境变量,比如数据源

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        SqlSessionFactory var5;
        //省略异常处理
            //xml配置构建器
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            //通过转化的Configuration构建SqlSessionFactory
            var5 = this.build(parser.parse());
 }
  • 构建SqlSessionFactory
// SqlSessionFactory只是一个接口,构建出来的实际上是它的实现类的实例,一般我们用的都是它的实现类DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
}

会话运行

会话运行是MyBatis最复杂的部分,它的运行离不开四大组件的配合:

SqlSession四大组件.png

MyBatis会话运行四大关键组件

1 Executor(执行器)

Executor起到了至关重要的作用,SqlSession只是一个门面,相当于客服,真正干活的是是Executor,就像是默默无闻的工程师。它提供了相应的查询和更新方法,以及事务方法。

    Environment environment = this.configuration.getEnvironment();
    TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //通过Configuration创建executor
    Executor executor = this.configuration.newExecutor(tx, execType);
    var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);

2 StatementHandler(数据库会话器)

StatementHandler,顾名思义,处理数据库会话的。我们以SimpleExecutor为例,看一下它的查询方法,先生成了一个StatementHandler实例,再拿这个handler去执行query。

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {

        Statement stmt = null;
 
        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, 
                                        parameter, rowBounds, resultHandler, boundSql);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }
 
        return var9;
    }

再以最常用的PreparedStatementHandler看一下它的query方法,其实在上面的prepareStatement已经对参数进行了预编译处理,到了这里,就直接执行sql,使用ResultHandler处理返回结果。

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement)statement;
        ps.execute();
        return this.resultSetHandler.handleResultSets(ps);
}

3 ParameterHandler (参数处理器)

PreparedStatementHandler里对sql进行了预编译处理

public void parameterize(Statement statement) throws SQLException {
        this.parameterHandler.setParameters((PreparedStatement)statement);
    }

这里用的就是ParameterHandler,setParameters的作用就是设置预编译SQL语句的参数。

里面还会用到typeHandler类型处理器,对类型进行处理。

public interface ParameterHandler {
    Object getParameterObject();
 
    void setParameters(PreparedStatement var1) throws SQLException;
}

4 ResultSetHandler(结果处理器)

我们前面也看到了,最后的结果要通过ResultSetHandler来进行处理,handleResultSets这个方法就是用来包装结果集的。Mybatis为我们提供了一个DefaultResultSetHandler,通常都是用这个实现类去进行结果的处理的。

public interface ResultSetHandler {
    <E> List<E> handleResultSets(Statement var1) throws SQLException;
 
    <E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;
 
    void handleOutputParameters(CallableStatement var1) throws SQLException;
}

它会使用typeHandle处理类型,然后用ObjectFactory提供的规则组装对象,返回给调用者。

会话运行总结

会话运行的简单示意图.png

工作流程总结

MyBatis整体工作原理图.png

  • 读取 MyBatis 配置文件——mybatis-config.xml 、加载映射文件——映射文件即 SQL 映射文件,文件中配置了操作数据库的 SQL 语句。最后生成一个配置对象。

  • 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。

  • 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

  • Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

  • StatementHandler:数据库会话器,串联起参数映射的处理和运行结果映射的处理。

  • 参数处理:对输入参数的类型进行处理,并预编译。

  • 结果处理:对返回结果的类型进行处理,根据对象映射规则,返回相应的对象。

MyBatis使用过程

MyBatis基本使用的过程大概可以分为这么几步:

MyBatis使用过程.png

1 创建SqlSessionFactory

// 可以从配置或者直接编码来创建SqlSessionFactory
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

2 通过SqlSessionFactory创建SqlSession

// SqlSession(会话)可以理解为程序和数据库之间的桥梁
SqlSession session = sqlSessionFactory.openSession();

3 通过sqlsession执行数据库操作

// 可以通过 SqlSession 实例来直接执行已映射的 SQL 语句:
Blog blog = (Blog)session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

// 更常用的方式是先获取Mapper(映射),然后再执行SQL语句:
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);

4 调用session.commit()提交事务

如果是更新、删除语句,我们还需要提交一下事务。

5 调用session.close()关闭会话

最后一定要记得关闭会话。

MyBatis生命周期

MyBatis生命周期.png

  • SqlSessionFactoryBuilder

    一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的生命周期只存在于方法的内部。

  • SqlSessionFactory

    SqlSessionFactory 是用来创建SqlSession的,相当于一个数据库连接池,每次创建SqlSessionFactory都会使用数据库资源,多次创建和销毁是对资源的浪费。所以SqlSessionFactory是应用级的生命周期,而且应该是单例的。

  • SqlSession

    SqlSession相当于JDBC中的Connection,SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的生命周期是一次请求或一个方法。

  • Mapper

    映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的,它的生命周期在sqlsession事务方法之内,一般会控制在方法级

Executor执行器

Executor执行器.png Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

  • SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。

  • ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。

  • BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

Executor执行器使用

  • 在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数,如SqlSession openSession(ExecutorType execType)。

  • 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新

Mapper接口动态代理

获取Mapper的过程: Mapper代理.png 定义的Mapper接口是没有实现类的,因为Mapper映射其实是通过JDK动态代理实现的。

BlogMapper mapper = session.getMapper(BlogMapper.class);

获取Mapper

获取Mapper需要先获取MapperProxyFactory——Mapper代理工厂。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }
public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    ……
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }
 
    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

这里可以看到动态代理对接口的绑定,它的作用就是生成动态代理对象(占位),而代理的方法被放到了MapperProxy中。

MapperProxy里,通常会生成一个MapperMethod对象,它是通过cachedMapperMethod方法对其进行初始化的,然后执行excute方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

MapperMethod

MapperMethod里的excute方法,会真正去执行sql。这里用到了命令模式,其实绕一圈,最终它还是通过SqlSession的实例去运行对象的sql。

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        ……
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
           ……
    }

MapperProxy

MapperProxyFactory

MapperProxyFactory的作用是生成MapperProxy(Mapper代理对象)。