MyBatis的解析和运行原理

MyBatis的解析和运行原理

  1. 了解MyBatis解析配置文件的大致过程
  2. 掌握MyBatis底层映射保存的数据结构(MappedStatement,SqlSource,BoundSql)及其内容
  3. 了解MyBatis Mapper的运行原理
  4. 掌握SqlSession运行原理
  5. 掌握SqlSession下四大对象的设计原理和具体方法的作用

MyBatis的运行过程分为两大步:第1步,读取配置文件缓存到Configuration对象,用以
创建SqlSessionFactory;第2步,SqlSession的执行过程。

构建SqlSessionFactory过程

最重要的功能是提供MyBatis的核心接口SqlSession

采用Builder模式去创建SqlSessionFactory,在实际中可以通过SqlSessionFactoryBuilder去构建,其构建分为两步:

  1. 通过 org.apache.ibatis.builder.xml.XMLConfigBuilder 解析配置的 XML 文件,
    读出所配置的参数,并将读取的内容存入 org.apache.ibatis.session.Configuration 类对象中。而Configuration 采用的是单例模式,几乎所有 MyBatis 配置内容都会存放在这个单例对象中,以便后续将这些内容读出。
  2. 使用 Configuration 对象去创建 SqlSessionFactory。MyBatis中的SqlSessionFactory
    是一个接口,而不是一个实现类,为此 MyBatis 提供了一个默认的实现类
    org.apache.ibatis.session.defaults.DefaultSqlSessionFactory 。在大部分情况下都没有必要自己去创建新 SqlSessionFactory 实现类。

构建Configuration

Configuration是提供XMLConfigBuilder去构建的,首先读出所以的XML配置的信息,然后把它们解析并保存在Configuration单例中。

构建映射器的内部组成

一般而言,在MyBatis中一条SQL与它相关的配置是由3个部分组成的,它们分别是MappedStatement,
SqlSource和BoundSql

  • MappedStatement作用是保存一个映射器节点(select | insert | delete | update)的内容。它还有一个重要的属性sqlSource。MyBatis通过读取它来获得某条SQL配置的所有信息。
  • SqlSource是提供BoundSql对象的地方,它是MappedStatement的一个属性。
    他的作用是根据上下文和参数解析生成需要的SQL。
  • BoundSql是一个结果对象,也就是SqlSource通过对SQL和参数的联合解析得到的SQL和参数,它是建立SQL和参数的地方。主要会提供三个属性:parameterMappings、parameterObject和sql。
    • parameterObject为参数本身,可以传递简单对象,POJO或者Map,@Param
      注解的参数。
    • parameterMappings是一个List,他的每个元素都是ParameterMapping对象。对象会描述参数,参数包括属性名称、表达式、javaType、jdbcType、typeHandler等重要信息。
    • sql属性就是书写在映射器里面的一条被SqlSource解析后的SQL。

构建SqlSessionFactory

SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSession运行过程

映射器(Mapper)的动态代理

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class)
MyBatis源码是如何实现getMapper方法的:

public  T getMapper(Class type) {
        return this.configuration.getMapper(type, this);
    }

显然运用到了Configuration对象的getMapper方法,追踪这个方法:

public  T getMapper(Class type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

运用了映射器的注册器MapperRegistry来获取对应的接口对象,如下:

public  T getMapper(Class type, SqlSession sqlSession) {
        MapperProxyFactory 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);
            }
        }
    }

首先判断是否注册一个Mapper,如果没有则会抛出异常;如果有,就会启用MapperProxyFactory工厂来生成一个代理实例,为此再追踪代码:

public class MapperProxyFactory {
    private final Class mapperInterface;
    private final Map methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class getMapperInterface() {
        return this.mapperInterface;
    }

    public Map getMethodCache() {
        return this.methodCache;
    }

    protected T newInstance(MapperProxy mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

Mapper映射是通过动态代理来实现的。这里可以看到动态代理对接口的绑定,它的作用是生成
动态代理对象(占位)。而代理的方法则放到了MapperProxy类中。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

可以看到这里的invoke方法逻辑。如果Mapper是一个JDK动态代理对象,那么它就会运行到invoke方法里面。invoke首先判断是否是一个类,这里的Mapper是一个接口不是类,所以判定失败,然后生成MapperMethod对象,通过cachedMapperMethod方法来初始化。最后执行execute方法,把SqlSession和当前运行的参数传递进去。

execute源码如下:

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        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;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

至此,相信大家都知道MyBatis为什么只用Mapper接口便能够运行了。
因为Mapper的XML文件的命名空间对应的是这个接口的全限定名,而方法就是那条SQL的id,这样MyBatis就可以根据全路径和方法名,将其和代理对象绑定起来。通过动态代理技术,让这个接口运行起来起来,而后采用命令模式。最后使用SqlSession接口的方法使得它能够执行对应的SQL。只是有了这层封装,就可以采用接口编程这样的编程更为简单明了。

SqlSession下的四大对象

  • Executor代表执行器,由它调度StatementHandler、ParameterHandler、ResultSetHandler
    等来执行对应的SQL。其中StatementHandler是最重要的。
  • StatementHandler的作用是使用数据库的Statement执行操作,是四大对象的核心,起到
    承上启下的作用。
  • ParameterHandler用来处理SQL参数的。
  • ResultSetHandler是进行数据集(ResultSet)的封装返回处理的。
Executor–执行器

SqlSession其实是门面,真正干活的是执行器,它是真正执行Java和数据库交互的对象。
MyBatis是怎么创建Executor的,这段代码在Configuration类中:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? this.defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Object 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);
        }

        if (this.cacheEnabled) {
            executor = new CachingExecutor((Executor)executor);
        }

        Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
        return executor;
    }
  • SIMPLE–简易执行器,默认
  • REUSE–一种能够执行重用预处理语句的执行器
  • BATCH–执行器重用语句和批量更新,批量专用的执行器

MyBatis将根据配置类型去确定需要创建哪一种Executor,它的缓存则用CachingExecutor进行包装Executor。

以SimpleExecutor的query方法作为例子进行讲解,代码如下:

public class SimpleExecutor extends BaseExecutor {
    public SimpleExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
    }

    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;

        int var6;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var6 = handler.update(stmt);
        } finally {
            this.closeStatement(stmt);
        }

        return var6;
    }

    public  List 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;
    }

    protected  Cursor doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, (ResultHandler)null, boundSql);
        Statement stmt = this.prepareStatement(handler, ms.getStatementLog());
        stmt.closeOnCompletion();
        return handler.queryCursor(stmt);
    }

    public List doFlushStatements(boolean isRollback) {
        return Collections.emptyList();
    }

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Connection connection = this.getConnection(statementLog);
        Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
    }
}

MyBatis根据Configuration来构建StatementHandler,然后使用prepareStatement
方法,对SQL编译和参数进行初始化。实现过程:它调用StatementHandler的prepare(),然后通过StatementHandler的parameterize()来设置参数,最后使用StatementHandler的query方法,把ResultHandler传递进去,使用它组织结果返回给调用者来完成一次查询,这样焦点又转移到StatementHandler对象上。

StatementHandler–数据库会话器

数据库会话器专门处理数据库会话的,MyBatis生成StatementHandler的代码如下:

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }

很显然创建的真实对象是一个RoutingStatementHandler对象,它实现了接口StatementHandler。
和Executor一样,用代理对象做一层层封装。
RoutingStatementHandler不是真实的服务对象,它是通过适配器模式来找到对应的StatementHandler
来执行。在MyBatis中,Executor样,RoutingStatementHandler分为三种:SimpleStatementHandler、
PreparedStatementHandler和CallableStatementHandler。它所对应的是
JDBC的Statement、PreparedStatement(预编译处理)和CallableStatement(存储过程处理)。

在初始化RoutingStatementHandler对象时,会根据上下文环境决定创建哪个具体的StatementHandler
对象实例,代码如下:

private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        switch(ms.getStatementType()) {
        case STATEMENT:
            this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case PREPARED:
            this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case CALLABLE:
            this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }

    }

根据配置来适配对应的StatementHandler对象。以最常用的PreparedStatementHandler
为例,看看MyBatis是怎么执行查询的。Executor执行查询时会执行StatementHandler
的prepare、parameterize和query方法,其中PreparedStatementHandler的prepare
方法的代码如下:

public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        ErrorContext.instance().sql(this.boundSql.getSql());
        Statement statement = null;

        try {
            statement = this.instantiateStatement(connection);
            this.setStatementTimeout(statement, transactionTimeout);
            this.setFetchSize(statement);
            return statement;
        } catch (SQLException var5) {
            this.closeStatement(statement);
            throw var5;
        } catch (Exception var6) {
            this.closeStatement(statement);
            throw new ExecutorException("Error preparing statement.  Cause: " + var6, var6);
        }
    }

instantiateStatement()方法是对SQL进行了预编译,然后做一些基础配置,比如超时、获取的最大行数等的设置。Executor中会调用它的parameterize()方法去设置参数,如代码:

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

这个时候它是调用ParameterHandler去完成。下节再来讨论它是如何实现的,这里先看查询的方法–执行SQL返回的结果,代码如下:

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

在执行前参数和SQL都被prepare()方法预编译,参数在parameterize()方法中已经进行
了设置,所以只要执行SQL然后返回结果就可以了。执行之后我们看到了ResultSetHandler对结果的封装和返回。

ParameterHandler–参数处理器

MyBatis是通过ParameterHandler对预编译语句进行参数设置的,它的作用是完成对预编译参数的设置,接口定义如下:

public interface ParameterHandler {
    //返回参数对象
    Object getParameterObject();
    //设置预编译SQL语句的参数
    void setParameters(PreparedStatement var1) throws SQLException;
}

具体实现就不贴代码了,大致的思路是:从parameterObject对象中取到参数,然后使用typeHandler转换参数,如果有设置,那么它会根据签名注册的typeHandler对参数进行处理。而typeHandler也是在MyBatis初始化时,注册在Configuration里面的,需要时就可以直接拿来用了,MyBatis就是这样完成参数设置的。

ResultSetHandler–结果处理器

ResultSetHandler是组装结果集返回的,ResultSetHandler的接口定义如下:

public interface ResultSetHandler {
    //包装结果集
     List handleResultSets(Statement var1) throws SQLException;

     Cursor handleCursorResultSets(Statement var1) throws SQLException;

    void handleOutputParameters(CallableStatement var1) throws SQLException;
}

SqlSession运行总结

SqlSession是通过执行器Executor调度StatementHandler来运行的。而StatementHandler经过3步:

  • prepared预编译SQL
  • parameterize设置参数
  • query/update执行SQL

其实,parameterize是调用parameterHandler的方法设置的,而参数是根据类型处理器
typeHandler处理的。query/update方法通过ResultSetHandler进行处理结果的封装,
如果是update语句,就返回整数,否则就通过typeHandler处理结果类型,然后用ObjectFactory提供的规则组装对象,返回给调用者。

祝进步

你可能感兴趣的:(MyBatis)