MyBatis源码分析(各组件关系+底层原理)

MyBatis源码分析

  1. MyBatis流程图
    MyBatis源码分析(各组件关系+底层原理)_第1张图片

    下面将结合代码具体分析。

  2. MyBatis具体代码分析

  • SqlSessionFactoryBuilder根据XML文件流,或者Configuration类实例build出一个SqlSessionFactory。

  • SqlSessionFactory.openSession()相当于从连接池中获取了一个connection,创建Executor实例,创建事务实例。

    DefaultSqlSessionFactory.class
    
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
    
        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }
    
        return var8;
    }
    
  • 此时我们只是获得一条connection,session.getMapper(XxxMapper.class)时才进行创建代理实例的过程,后面会介绍。SqlSession.getMapper实际上托付给Configuration去做。

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

    Configuration交给自己的成员变量mapperRegistry去做。这个成员变量是Map再封装之后的,持有configuration实例和Map, MapperProxyFactory> knownMappers,正如xml文件中写的那样,每个mappee.xml中都有一个namespace,这个namespace就是Class,而后者是对这个接口进行代理的工厂MapperProxyFactory实例,其中封装了被代理接口和缓存。这个knownMappers应该是初始化configuration的时候就已经处理完毕的。

    MapperRegistry.class
    
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
    

    类似于Spring中的getBean方法,MyBatis中用getMapper的方式进行创建。下面代码可以看出,先根据class类型获取代理类工厂,去工厂中newInstance。注意这里是没有Spring中的单例多例的,只要你getMapper,框架就会给你newInstance一个全新的被代理实例。

    MapperRegistry.class
    
    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);
            }
        }
    }
    

    newInstance()中做了什么呢?

    MapperProxyFactory.class
    
    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);
    }
    

    其实我们知道,Proxy.newProxyInstance()需要三个参数,类加载器,被代理接口和InvocationHnadler,**什么?不知道?快去补习基础。**其中InvocationHandler掌管着invoke方法,正是这个方法中实现了对被代理实例的代码增强(或者叫做代理代码)。那我们就要着重看这个InvocationHandler里面到底有什么,特别是他的invoke方法。

    MapperProxy.class
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ...省略...
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
    

    invoke方法中,重点是调用了mapperMethod.execute()。这个mapperMethod就是:**被代理接口A,A中有方法a(),代理类实例((A)proxyA).a()中的这个a,就是method,而mapperMethod就是method被包装了一层。**换而言之,(session.getMapper(XxxMapper)).interfaceMethod()时,都在走mapperMethod.execute()这个方法。

  • 下面我们来看mapperMethod.execute这个方法。

    MapperMethod.class
    
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        ...省略...
        case SELECT:
        ...省略...
        param = this.method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(this.command.getName(), param);
        ...省略...
        }
        ...省略...
    }
    

    这个方法做了两件事,1.对参数用参数解析器转化为JDBCType的参数,这边不是重点。2.执行sqlSession.selectOne(),当然我删去了一些代码,为讲清楚,只讲selectOne()即可,其他都是大同小异的。又回到最初的起点,呆呆地望着镜子前。 sqlSession又见面了,发现了么?sqlSession先是把getMapper交给configuration做,然后自己还能执行类似selecOne,update之类的命令,这是因为sqlSession是暴露给用户的接口,如果用户要用传统方式,就可以直接调用selectOne之类的方法,比如Employee employee = session.selectOne("mybatisDemo.dao.EmployeeMapper.getEmpById",1);如果用户想用mapper.xml和mapper接口的方法,就getMapper获得代理实例然后调用接口方法即可。所以本质上,所有跟JDBC打交道的还是sqlsession的select、update等方法

    现在还都是表面功夫,直到sqlSession.selectOne才开始真正的辉煌旅程。小结一下,目前我们看到的MyBatis组件包括SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession。还未看到的有,Executor,ParameterHandler,StatementHandler,ResultSetHandler。这几个部件都会在之后出现。

  • 下面来分析session.selectOne()。selectOne内部调用的还是selectList,因此直接看SqlSession的实现类DefaultSqlSession中的方法。可以发现,Executor组件终于出现了,而这个组件才是真正执行query()方法的组件。SqlSession真的是领导,getMapper交给config做,select等脏活累活又交给Executor完成。Executor.query的入参有什么?被代理方法参数parameter,ms用于动态sql的。

    DefaultSqlSession.class
    
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        ...省略...
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        ...省略...
        return var5;
    }
    

    下面去看query方法,在Executor的一个抽象实现类,其实也就是模板类BaseExecutor中。

    BaseExecutor.class
    
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
        return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    

    BoundSql就是动态sql,key是将sql语句,入参组合起来作为缓存参数,即:如果sql语句相同且参数一样,那可以认为两个sql语句会返回同样的结果(缓存未失效的情况下)。query方法中进一步调用doQuery方法,这个方法在BaseExecutor中只给出抽象方法,交给子类去继承实现。这个子类就是SimpleExecutor。

    SimpleExecutor.class
    
    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;
    }
    

    这里出现了StatementHandler这个组件,先别急着点进newStatementHandler()方法,先看一下StatementHandler接口,发现这个接口有ParameterHandler getParameterHandler();方法和 List query(Statement var1, ResultHandler var2)。这时候,ParmeterHandlerResultHandler两大组件也出现了。所以这三个组件的关系是,StatementHandler中需要通过ParamterHandler处理参数,然后将结果通过ResultHandler处理成要求的JavaBean、Map、List后输出。

    小结一下:SqlSession将查询等任务交给Executor接口实现类完成,Executor内有StatementHandler,StatementHandler内有ParameterHandler和ResultHandler,分别进行参数处理和结果处理。

  • 还没讲newStatementHandler()这个方法呢,为什么要现在讲?

    Configuration.class
    
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
    }
    
    public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
    }
    
    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;
    }
    

    值得注意的是interceptorChain,拦截器链,这里的拦截器链通过pluginAll对几个Handler进行织入。织入的是什么代码呢?是你写的拦截器代码。回忆一下MyBatis写拦截器代码的时候要指定哪些呢?1.要指定针对Exector,ParameterHandler,StatementHandler,或者ResultHandler进行拦截2.要指定针对什么方法拦截。针对拦截器这一部分的原理,建议阅读

    https://www.jianshu.com/p/b82d0a95b2f3

    @Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
    public class ExamplePlugin implements Interceptor {
        public Object intercept(Invocation invocation) throws Throwable {
            return invocation.proceed();
        }
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
        public void setProperties(Properties properties) {
        }
    }
    

你可能感兴趣的:(Java学习)