mybatis的执行流程

Mybatis的执行流程

没有看的很仔细,描述可能有误…

以下代码拷贝自mybatis-plus的源代码,和mybatis的应该大同小异。

1,MybatisSqlSessionFactoryBean

这个类实现了FactoryBean,在调用getObject()方法的时候会返回一个SqlSessionFactory的对象,默认实现是DefaultSqlSessionFactory。

//MybatisSqlSessionFactoryBean类    
public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            this.afterPropertiesSet();
        }

        return this.sqlSessionFactory;
    }
//afterPropertiesSet()方法会调用buildSqlSessionFactory()方法,生成SqlSessionFactory对象
//这里摘出部分代码
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    //创建配置文件MybatisConfiguration对象,其继承自mybatis的Configuration类
    //别名、自定义类型处理器的注册等等,并set到配置文件里面
    //1、这一步是事务处理,SpringManagedTransactionFactory会和spring的事务管理进行联动
    ((Configuration)targetConfiguration).setEnvironment(new Environment(MybatisSqlSessionFactoryBean.class.getSimpleName(), (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));
    //2、这一步会读取mapperLocations的xml文件路径,并格式化xml文件
    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), (Configuration)targetConfiguration, mapperLocation.toString(), ((Configuration)targetConfiguration).getSqlFragments());
    xmlMapperBuilder.parse();
    //创建SqlSessionFactory对象并返回
    SqlSessionFactory sqlSessionFactory = (new MybatisSqlSessionFactoryBuilder()).build((Configuration)targetConfiguration);
    return sqlSessionFactory;
}

1-1,事务实现

Spring的注解事务机制是先生成数据库连接,设置自动提交为false,然后把连接存到ThreadLocal对象里面,方法如果需要使用数据库连接,会从这个ThreadLocal对象里面取出,这样就实现了事务的控制

本篇不针对spring的事务部分代码做过多分析

//SpringManagedTransactionFactory类

//这一步会在获取SqlSession的时候调用
    public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        return new SpringManagedTransaction(dataSource);
    }
//SpringManagedTransaction
//获取数据库连接,这一步会在执行的时候调用,后续说明
public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            this.openConnection();
        }

        return this.connection;
    }

private void openConnection() throws SQLException {
    	//这一步会取出spring预先创建的数据库连接
    	//需要注意的是,这里的dataSource对象和事务事务管理器使用的dataSource是同一个对象,如果不是,应该会导致事务失效,简而言之,使用@Configuration,保证程序的dataSource对象都是同一个。
        this.connection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.connection.getAutoCommit();
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
        LOGGER.debug(() -> {
            return "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring";
        });
    }

1-2,格式化xml文件

XMLMapperBuilder会把xml文件转化为MappedStatement,然后加入configuration对象中,MappedStatement的完整key的格式是namespace+“.”+id,key好像分为长key和短key,长key必须保持唯一,MappedStatements的数据存储格式为map,实现对象是mybatis的Configuration类的内部类StrictMap,继承自HashMap对象,重写了部分方法,以实现长短key的获取。

xml文件的namespace之所以写成mapper接口的全路径,是为了能把接口类加入配置文件,后续能从配置类获取对应的代理对象,之后的代理对象能顺利找到对应的sql语句。

//XMLMapperBuilder
//看的不是很明白,所以不做过多说明,总之就是把xml的文件内容加入configuration对象
public void parse() {
        if (!this.configuration.isResourceLoaded(this.resource)) {
            //获取各个节点的数据,并格式化,加载进configuration对象,可以重点跟进这个方法
            this.configurationElement(this.parser.evalNode("/mapper"));
            //记录已加载过的资源
            this.configuration.addLoadedResource(this.resource);
            //绑定命名空间
            this.bindMapperForNamespace();
        }

        this.parsePendingResultMaps();
        this.parsePendingCacheRefs();
        this.parsePendingStatements();
    }
2,SqlSessionTemplate

自动配置类里面向spring环境注入的bean对象是SqlSessionTemplate,而非DefaultSqlSession,实测多线程下,如果不使用SqlSessionTemplate对象而是使用DefaultSqlSession,有时候会报错,没有找错误原因…

SqlSessionTemplate其实就是对SqlSessionFactory进行了一次代理,最终使用的对象还是DefaultSqlSession。

猜测是应该为了和spring的事务做关联。

//获取mapper的代理对象,这里不做@Mapper和@MapperScan的注解逻辑的分析
sqlSessionTemplate.getMapper(MapperTest2.class);

//SqlSessionTemplate,获取代理对象,这里是通过配置信息获取的
public <T> T getMapper(Class<T> type) {
        return this.getConfiguration().getMapper(type, this);
}

//Configuration,获取代理对象,这里贴的是mybatis的获取代理对象方法,mybatis-plus重写了这个方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
}
//MybatisConfiguration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mybatisMapperRegistry.getMapper(type, sqlSession);
}
//mapperRegistry对象和mybatisMapperRegistry对象在new MybatisConfiguration()的时候就已经初始化了
//MybatisMapperRegistry继承自MapperRegistry,逻辑很类似
//MapperRegistry类
//MapperRegistry的获取代理对象方法,最终由mapperProxyFactory生成接口的代理对象
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);
            }
        }
    }

//knownMappers在上述的xml解析等逻辑中会加入要处理的Mapper接口类
public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }
//生成代理对象,这一步mybatis-plus和mybatis的实现有些不同,逻辑都是类似的
//MybatisMapperProxyFactory生成代理对象
public T newInstance(SqlSession sqlSession) {
        MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
//MapperProxyFactory生成代理对象
public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
3,mapper执行方法

因为生成的mapper是代理对象,所以会进入代理类(MapperProxy或MybatisMapperProxy)中,执行代理方法

最终执行的是MapperMethod或MybatisMapperMethod的execute()方法

//MapperProxy类
//代理方法,按照上述逻辑,这里的sqlSession对象是sqlSessionTemplate对象,进入MapperMethod.execute()
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);
        }
    }

//走的应该是else分支,会创建一个MapperMethod对象去执行
//if分支什么时候走,不清楚....之前的版本应该不是这么设计的...
private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            return (MapperProxy.MapperMethodInvoker)MapUtil.computeIfAbsent(this.methodCache, method, (m) -> {
                if (m.isDefault()) {
                    try {
                        return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method));
                    } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {
                        throw new RuntimeException(var4);
                    }
                } else {
                    return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
                }
            });
        } catch (RuntimeException var4) {
            Throwable cause = var4.getCause();
            throw (Throwable)(cause == null ? var4 : cause);
        }
    }

MapperMethod类包含两个对象MapperMethod.SqlCommand和MapperMethod.MethodSignature

//MapperMethod的execute方法,代码太多,只贴出部分,以查询逻辑为例
public Object execute(SqlSession sqlSession, Object[] args) {
        //转换参数
        param = this.method.convertArgsToSqlCommandParam(args);
        //查询,这里的sqlSession是sqlSessionTemplate对象     
        result = sqlSession.selectOne(this.command.getName(), param);
    }
//paramNameResolver对象在new MapperMethod.MethodSignature的时候,也new 好了
//new paramNameResolver对象的时候,对@param注解进行了处理
//这一步也会对RowBounds参数进行处理,这个是mybatis提供的分页,但感觉不太好用,是从结果集中获取需要的数据
//反射好像不能拿到方法参数的名称,所以才用的注解...反正我是没拿到
//所以mapper接口方法的参数要么是一个实体类,要么是一个map对象,要么使用注解...
//spring的配置类@Bean的方法之所以能按参数名查找bean,是因为其解析了class文件,才拿到的参数名
//这个方法逻辑有点长,应该就是返回了一个实体类参数或一个ParamMap对象
public Object convertArgsToSqlCommandParam(Object[] args) {
            return this.paramNameResolver.getNamedParams(args);
 }
//MapperMethod.SqlCommand,这一步是为了找MappedStatement和其类型(增删改查)
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
            String methodName = method.getName();
            Class<?> declaringClass = method.getDeclaringClass();
    	//获取MappedStatement对象
            MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
		//省略部分代码...
}

//获取MappedStatement对象
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {
    		//这一步可以看出,key的组成形式是接口名+"."+方法名,和上述xml文件的格式化逻辑一致
    		//这样,就找到了接口和实际sql语句的关系
            String statementId = mapperInterface.getName() + "." + methodName;
            if (configuration.hasStatement(statementId)) {
                return configuration.getMappedStatement(statementId);
            } else if (mapperInterface.equals(declaringClass)) {
                return null;
            } else {
                Class[] var6 = mapperInterface.getInterfaces();
                int var7 = var6.length;

                for(int var8 = 0; var8 < var7; ++var8) {
                    Class<?> superInterface = var6[var8];
                    if (declaringClass.isAssignableFrom(superInterface)) {
                        MappedStatement ms = this.resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
                        if (ms != null) {
                            return ms;
                        }
                    }
                }

                return null;
            }
        }

4,进入查询逻辑

这部分的逻辑很多,省略的部分也很多…

//SqlSessionTemplate
//这里进入代理对象的执行逻辑,代理对象产生了一个DefaultSqlSession对象,用于执行sql
public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.selectOne(statement, parameter);
    }

//代码过长,这里只贴出DefaultSqlSessionFactory的openSession最终调用的方法逻辑
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            //从配置文件中获取事务工厂,上述第一步中,设置了SpringManagedTransactionFactory,这里会拿出来
            //然后创建一个事务对象,设置进执行器
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            //创建执行器,这里的执行器类型是SqlSessionTemplate传递的,但创建SqlSessionTemplate时候,使用的是一个参数的构造方法,其实执行器的类型还是默认类型ExecutorType.SIMPLE
            //这里如果有插件,比如分页插件PageHelper或者自定义的插件,都会在这一步进行组装,是按照顺序来的
            //所以说,插件的顺序可能会影响到插件的预期结果
            //如果启用了二级缓存,会进行二级缓存的包装,cacheEnabled默认是true,但是也需要在xml文件中开启,否则二级缓存是不生效的
            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;
    }
//DefaultSqlSession
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        List var6;
        try {
            //获取MappedStatement
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            //这一步如果打断点,如果有插件,就会先进入插件的逻辑而不是默认执行器的逻辑
            var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
        } catch (Exception var10) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);
        } finally {
            ErrorContext.instance().reset();
        }

        return var6;
    }
//CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //获取查询使用的sql,这一步会对if等标签进行处理,处理ognl表达式等
    //逻辑很长,可以作为重点看看
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //创建缓存键    
    CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
      
    Cache cache = ms.getCache();
    //如果xml文件没有配置缓存,直接走BaseExecutor    
    if (cache != null) {
            //是否需要刷新缓存
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, boundSql);
                //二级缓存的获取,二级缓存是在SqlSession关闭或提交的时候写入MappedStatement的
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    //真正的查询,进入BaseExecutor
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }
		//真正的查询,进入BaseExecutor
        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
//BaseExecutor
//查询方法,省略部分代码
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

            List list;
            try {
                ++this.queryStack;
                //一级缓存
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    //数据库查询,调用doQuery抽象方法
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }

            return list;
        }
    }
//SimpleExecutor,继承自BaseExecutor
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);
            //准备Statement
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            //查询并处理结果,mybatis自带的分页逻辑会在这个方法中处理,这部分的实现代码很长...
            //懒加载的处理逻辑好像也是在这里
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }
//准备Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    //获取连接
    Connection connection = this.getConnection(statementLog);
   //获取Statement
    Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
   //设置Statement使用的参数,如果需要的话     
    handler.parameterize(stmt);
    return stmt;
    }

//从事务中获取数据库连接
protected Connection getConnection(Log statementLog) throws SQLException {
    //这里对应了上文中的事务处理,SpringManagedTransaction的getConnection()方法    
    Connection connection = this.transaction.getConnection();
    //这一步会对数据库连接进行代理,一步步直到ResultSet,从而实现sql日志的打印
    //默认的打印是Slf4jImpl.class,ResultSetLogger代理类会判断isTraceEnabled(),实现查询结果的打印   
    return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值