记录一下尝试阅读Mybatis源码的过程,这篇笔记是我一边读,一遍记录下来的,虽然内容也很少,对Mybatis总体的架构体系也没有摸的很清楚,起码也能把这个过程整理下来,这也是我比较喜欢的一种学习方式吧java
单独Mybatis框架搭建的环境,没有和其余框架整合程序员
入口点的源码以下:sql
@Test public void test01() { try { this.resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml"); // 2. 建立SqlSessionFactory工厂 this.factory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 3. 建立sqlSession // todo 怎么理解这个sqlSession? 首先它是线程级别的,线程不安全, 其次它里面封装了大量的CRUD的方法 this.sqlSession = factory.openSession(); IUserDao mapper = this.sqlSession.getMapper(IUserDao.class); List<User> all = mapper.findAll(); for (User user : all) { System.out.println(user); } // 事务性的操做,自动提交 this.sqlSession.commit(); // 6, 释放资源 this.sqlSession.close(); this.resourceAsStream.close(); } catch (IOException e) { e.printStackTrace(); } }
首先跟进这个,看看如何构建SqlSessionFactory
对象数据库
this.factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
这个SqlSessionFactoryBuilder
类的存在很简单,取名也叫他构建器,Mybatis的官网是这样解释它的,这个类能够被实例化(由于它有且仅有一个默认的无参构造),使用它的目的就是用来建立多个SqlSessionFactory实例,最好不要让他一直存在,进而保证全部用来解析xml的资源能够被释放缓存
因此跳过对这个构建器的关注,转而看的build()
方法安全
首先会来到这个方法,直接能够看到存在一个XML配置解析器,这其实并不意外,毕竟如今是MyBatis是孤军一人,就算咱们使用的是注解开发模式,不也得存在主配置文件不是?session
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 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. } } }
接着看看parser.parse()
,它里面会解析主配置文件中的信息,解析哪些信息呢? 源码以下: 很清楚的看到,涵盖mybatis配置文件中的全部的标签mybatis
private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); ... typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
第一个问题: 解析的配置信息存在哪里呢? 其实存放在一个叫Configuration
的封装类中, 这个上面的解析器是XMLConfigBuilder
这个封装类的保存者是它的父类BaseBuilder
架构
继续跟进build()
方法,源码以下:app
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
小结: 至此也就完成了DefaultSqlSessionFactory()的构建,回想下,这个构建器真的太无私了,牺牲了本身,不只仅建立了默认的SqlSessionFactory,还将配置文件的信息给了他
建立完成SqlSession工厂的建立, 咱们继续跟进this.sqlSession = factory.openSession();
, 从工厂中获取一个SqlSession
跟进几个空壳方法,咱们很快就能来到下面的方法:
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); 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(); } }
什么是SqlSession呢? 注释是这么说的,他是MyBatis在java中主要干活的接口,经过这个接口,你能够执行命令(它里面定义了大量的 诸如selectList
相似的方法),获取mapper,合并事务
The primary Java interface for working with MyBatis. Through this interface you can execute commands, get mappers and manage transactions.
经过上面的我贴出来的函数,你们能够看到,经过事务工厂实例化了一个事物Transaction
,那么问题来了,这个Transaction
又是什么呢? 注释是这么解释的: Transaction 包装了一个数据库的链接,处理这个链接的生命周期,包含: 它的建立,准备 提交/回滚 和 关闭
紧接着建立执行器:configuration.newExecutor(tx, execType)
,默认建立的是CachingExecutor
它维护了一个SimpleExecutor
, 这个执行器的特色是 在每次执行完成后都会关闭 statement 对象
关于mybatis的执行器,其实挺多事的,打算专门写一篇笔记
继续看new DefaultSqlSession()
咱们得知,这个sqlSession的默认实现类是DefaultSqlSession
第三个参数autocommit为false, 这也是为何咱们若是不手动提交事务时,虽然测试会经过,可是事务不会被持久化的缘由
小结: 当前函数是 openSession()
, 若是说它是打开一个session,那跟没说是同样的,经过源码咱们也看到了,这一步实际上是Mybatis将 数据库链接,事务,执行器进行了一下封装而后返回给程序员
咱们交给mybatis的mapper是一个接口,看看Mybatis是如何实例化咱们的结果,返回给咱们一个代理对象的
跟进源码:IUserDao mapper = this.sqlSession.getMapper(IUserDao.class);
,通过一个空方法,咱们进入Configuration
类中的函数以下:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
从mapperRegistry
中获取mapper,他是Configuration
属性以下: 能够看到这个mapperRegistry
甚至包含了Configuration
,甚至还多了个 knownMappers
那么问题来了,这个knownMappers
是干啥呢? 我直接说,这个map就是在上面解析xml配置文件时,存放程序员在<mappers>
标签下配置的<maper>
protected final MapperRegistry mapperRegistry = new MapperRegistry(this); // 详情: public class MapperRegistry { private final Configuration config; private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
继续跟进这个getMapper()
以下: 而且咱们在配置文件中是这样配置的
<mappers> <mapper class="com.changwu.dao.IUserDao"/> </mappers>
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
因此不难想象,咱们确定能获取到结果,经过上面的代码咱们能看到,获取到的对象被强转成了MapperProxyFactory
类型,它的主要成员以下: 说白了,这个 map代理工厂是个辅助对象,它是对程序员提供的mapper结果的描述,同时内置使用jdk动态代理的逻辑为mapper建立代理对象
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>(); ... protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
说到了为mapper建立动态代理,就不得不去看看是哪一个类充当了动态代理的须要的InvoketionHandler -- 这个类是mybatis中的MapperProxy, 没错它实现了InvocationHandler接口,重写了invoke()
逻辑,源码以下:
@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 if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
小结: 至此当前模块的获取mapper对象已经完结了,咱们明明白白的看到了MyBatis为咱们的mapper使用jdk的动态代理建立出来代理对象, 这也是为何咱们免去了本身写实现类的粗活
上一个模块咱们知道了Mybatis为咱们建立出来了mapper接口的代理对象,那当咱们获取到这个代理对象以后执行它的mapper.findAll();
实际上触发的是代理对象的invoke()
方法
因此说,接着看上面的MapperProxy
的invoke()
方法:
@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 if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
上面的方法咱们关注两个地方,第一个地方就是final MapperMethod mapperMethod = cachedMapperMethod(method);
,见名知意: 缓存MapperMethod
第一个问题: 这个MapperMethod
是什么? 它实际上是Mybatis为sql命令+方法全限定名设计的封装类
*/ public class MapperMethod { private final SqlCommand command; private final MethodSignature method;
说白了,就是想为MapperProxy保存一份map格式的信息,key=方法全名 value=MapperMethod(command,method),存放在MapperProxy的属性methodCache中
comand -> "name=com.changwu.dao.IUserDao,fingAll"
method -> "result= public abstract java.util.List.com.changwu.dao.IUserDao.findAll()"
为何要给这个MapperProxy保存这里呢? 很简单,它是mapper的代理对象啊,程序员使用的就是他,在这里留一份method的副本,再用的话多方便?
完成缓存后,继续看它如何执行方法:,跟进mapperMethod.execute(sqlSession, args)
由于我使用的是@Select("select * from user")
,因此必定进入下面的result = executeForMany(sqlSession, args);
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } .. case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); ... }
因此咱们继续关注这个 executeForMany(sqlSession, args);
方法,看他的第一个参数是sqlSession
,也就是咱们的DefaultSqlSession
,他里面存在两大重要对象: 1是configuration 配置对象, 2是Executor 执行器对象
继续跟进:
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.selectList(command.getName(), param, rowBounds); } else { result = sqlSession.selectList(command.getName(), param); }
咱们在继续跟进这个selectList
方法以前,先看看这个command.getName()是啥? 其实咱们上面的代码追踪中有记录: 就是name=com.changwu.dao.IUserDao,fingAll
继续跟进去到下面的方法:
这个方法就比较有趣了,首先来讲,下面的入参都是什么
statement = "com.changwu.dao.IUserDao.findAll"
parameter=null
第二个问题:MappedStatement
是什么? 它是一个对象,维护了不少不少的配置信息,可是咱们关心它里面的两条信息,这其实能够理解成一种方法与sql之间的映射,以下图
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
前面阅读时,咱们知道Mybatis为咱们建立的默认的执行器 Executor是CachingExecutor
,以下图
继续跟进,主要作了下面三件事, 获取到绑定的sql,而后调用SimpleExecutor
缓存key,而后继续执行query()
方法
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
接着调用SimpleExecutor
的query()
方法,而后咱们关注SimpleExecutor
的doQuery()
方法,源码以下
@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); } }
咱们注意到.在SimpleExcutor中建立的了一个 XXXStatementHandler这样一个处理器, 因此咱们的只管感受就是,sql真正执行者其实并非Executor,而是Executor会为每一条sql的执行从新new 一个 StatementHandler ,由这个handler去具体的执行sql
关于这个StatementHandler究竟是是何方神圣? 暂时了解它是Mybatis定义的一个规范接口,定义了以下功能便可
public interface StatementHandler { // sql预编译, 构建statement对象 Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; // 对prepare方法构建的预编译的sql进行参数的设置 void parameterize(Statement statement) throws SQLException; // 批量处理器 void batch(Statement statement) throws SQLException; // create update delete int update(Statement statement) throws SQLException; // select <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException; <E> Cursor<E> queryCursor(Statement statement) throws SQLException; // 获取sql的封装对象 BoundSql getBoundSql(); // 获取参数处理对象 ParameterHandler getParameterHandler(); }
了解了这个StatementHandler是什么,下一个问题就是当前咱们建立的默认的statement是谁呢? routingStatementHandler
以下图
立刻马就发生了一件悄无声息的大事!!!根据现有的sql等信息,构建 PreparedStatement,咱们关注这个prepareStatement(handler, ms.getStatementLog());
方法,经过调试咱们得知,prepareStatement()
是RoutingStatementHandler
的抽象方法,被PreparedStatementHandler
重写了,因此咱们去看它如何重写的,以下:
@Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null; try { statement = instantiateStatement(connection);
咱们关注这个instantiateStatement()
方法, 而且进入它的connection.prepareStatement(sql);
方法,以下图:
纯洁的微笑... 见到了原生JDK, jdbc的亲人...
建立完事这个 preparedStatement,下一步总该执行了吧...绕这么多圈...
咱们回到上面代码中的return handler.query(stmt, resultHandler);
准备执行,此时第一个参数就是咱们的刚建立出来的PreparedStatement, 回想一下,上面建立的这个默认的statement中的表明是PreparedStatementHandler
,因此,咱们进入到这个StatementHandler的实现类RountingStatementHandler
中,看他的query()
方法
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { return delegate.query(statement, resultHandler); }
调用RountingStatementHandler
中维护的表明的StatementHandler也就是PreparedStatementHandler
的query()
方法,顺势跟进去,最终会经过反射执行jdbc操做,如图, 我圈出来的对象就是咱们上面建立出来的preparedStatement
跟进conmit()
方法,分红两步
清空缓存是在CachingExecutor
中调用了SimpleExecutor
简单执行器的方法commit(required)
@Override public void commit(boolean required) throws SQLException { delegate.commit(required); tcm.commit(); }
接在SimpleExecutor
的父类BaseExecutor
中完成
@Override public void commit(boolean required) throws SQLException { if (closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } clearLocalCache(); flushStatements(); if (required) { transaction.commit(); } }
提交事务的操做在tcm.commit();
中完成
本文到这里也就行将结束了,若是您以为挺好玩的,欢迎点赞支持,有错误的话,也欢迎批评指出