第四篇 Mybatis源码阅读:Mybatis执行流程
一、Mybatis的执行流程
Mybatis做一个ORM框架,最主要的目的就是完成数据的持久化操作,实现Java对象和数据的映射,所以这个过程中,必然会涉及到连接数据库、执行SQL、结果封装成Java对象这些操作,Mybatis的执行流程也会伴随这些操作进行,首先,通过下面这张图了解Mybatis执行的大致流程,后面从源码级别去跟进Mybatis的执行。
Mybatis从加载配置文件开始初始化全局配置组件Configuration,配置文件包括全局配置XML和Mapper.XML,全局配置XML如下,其主要内容包括了数据源、日志、缓存等配置,全局配置XML和全局配置组件Configuration是对应的。之后,通过SqlSessionFactory创建会话SqlSession,SqlSession本身不会执行SQL,委托给Executor执行器处理,Executor完成数据库操作。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="org/mybatis/example/config.properties"> <property name="username" value="dev_user"/> <property name="password" value="F2Fa3!33TYyg"/> </properties> <settings> <setting name="lazyLoadingEnabled" value="true"/> </settings> <plugins> <plugin interceptor=""></plugin> </plugins> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.117.128:3306/pattern?characterEncoding=UTF8"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper class="com.mybatis.demo.mapper.BlogMapper"/> </mappers> </configuration>
二、源码阅读
Mybatis的源码非常简洁,并且命名也通俗易懂,很适合作为源码阅读的第一课,首先Mybatis会使用SqlSessionFactoryBuilder的build方法去创建SqlSessionFactory,创建SqlSessionFactory的过程中,需要解析XML,数据源配置等,所以使用了构建者模式(构建者模式一般用于复杂对象的创建),构建代码如下。
// SqlSessionFactoryBuilder类
/* 构建SqlSessionFactory **/ 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. } } }
这里首先使用XMLConfigBuilder加载XML文件,并解析XML成XPathParser对象,之后调用parse()方法,并交给parseConfiguration()方法处理,在parseConfiguration方法中会获取</configuration>标签的所有内容,并逐步注释各个配置到Configuration对象中。这块儿的解析顺序是取决和XML配置的顺序,这个由http://mybatis.org/dtd/mybatis-3-config.dtd约束。
// XMLConfigBuilder类
//初始化configuration配置 private void parseConfiguration(XNode root) { try { // XML文件按下面顺序解析,所以XML配置也会按照此顺序 // 解析外部属性配置<>,如<properties><property name="username" value="dev_user"/></properties> // 这些属性会被放入到configuration的Variables中管理,Variables是一个Hashtable propertiesElement(root.evalNode("properties")); // 解析setting调整配置,如<settings><setting name="lazyLoadingEnabled" value="true"/></settings> Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); //日志配置 loadCustomLogImpl(settings); //解析类型别名,如<typeAliases><typeAlias alias="Author" type="domain.blog.Author"/></typeAliases> typeAliasesElement(root.evalNode("typeAliases")); //解析插件 <plugins><plugins> pluginElement(root.evalNode("plugins")); //对象工厂标签<objectFactory/> objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 //环境配置<environment/>,包括数据源和事务管理 environmentsElement(root.evalNode("environments")); //数据库厂商标识<databaseIdProvider/>, 用于适配多数据库 databaseIdProviderElement(root.evalNode("databaseIdProvider")); //解析类型处理器 typeHandlerElement(root.evalNode("typeHandlers")); //解析Mapper mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
这里需要说一下mapperElement方法,在这个方法中,遍历每一个Mapper.xml,将它们解析放入Configuration的MapperRegistry之中。在这里有两个大分支分别处理<package/>和<mapper/>,其中<mapper/>又有三个小分支,他们的含义如下:
1、<package name="org.mybatis.builder"/> 将包内的映射器接口实现全部注册为映射器
2、<mapper resource="org/mybatis/builder/AuthorMapper.xml"/> 使用相对于类路径的资源引用
3、<mapper url="file:///var/mappers/AuthorMapper.xml"/> 使用完全限定资源定位符(URL)
4、<mapper class="org.mybatis.builder.AuthorMapper"/> 使用映射器接口实现类的完全限定类名
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { // <package name="org.mybatis.builder"/ if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); //直接添加包名,通过遍历包下面的接口添加到MapperRegistry中 configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); //三者只能存其一 if (resource != null && url == null && mapperClass == null) { // <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> ErrorContext.instance().resource(resource); try (InputStream inputStream = Resources.getResourceAsStream(resource)) { XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //解析xml的namespace的值得到接口添加到MapperRegistry中 mapperParser.parse(); } } else if (resource == null && url != null && mapperClass == null) { // <mapper url="file:///var/mappers/AuthorMapper.xml"/> 使用完全限定资源定位符(URL) ErrorContext.instance().resource(url); try (InputStream inputStream = Resources.getUrlAsStream(url)) { XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); //解析xml的namespace的值得到接口添加到MapperRegistry中 mapperParser.parse(); } } else if (resource == null && url == null && mapperClass != null) { // <mapper class="com.mybatis.demo.mapper.BlogMapper"/> Class<?> mapperInterface = Resources.classForName(mapperClass); // 直接添加接口 configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
在mapper分支中,url,resource和class只能存在其一,否则抛出异常BuilderException,通过跟进addMapper方法,可以发现,这些Mapper信息最终都存在MapperRegistry的knownMappers中,MapperRegistry又归属configuration对象持有,所有最后的结果都是注册mapper信息到configuration,到下面是MapperRegistry的源码,在MapperRegistry中一个HashMap对象knownMappers存储MapperProxyFactory,MapperProxyFactory根据mapper接口信息创建代理类,创建逻辑在getMapper方法中,代理类中会持有一个SqlSession对象。
public class MapperRegistry { private final Configuration config; private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); public MapperRegistry(Configuration config) { this.config = config; } //获取代理对象 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 { //实例化,注入sqlSession对象 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } public <T> boolean hasMapper(Class<T> type) { return knownMappers.containsKey(type); } // 添加Mapper信息 public <T> void addMapper(Class<T> type) { if (type.isInterface()) { //判断是否重复,重复抛出异常 if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { knownMappers.put(type, new MapperProxyFactory<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } ... }
到这里Mybatis的初始化阶段就结束了,之后就是构建SqlSession执行SQL了。sqlSessionFactory首先会调用openSession()方法,再交给openSessionFromDataSource方法处理。方法有三个参数,分别是执行器Executor的类型,事务隔离级别TransactionIsolationLevel,事务默认提交。Executor的类型有三种,分别SIMPLE, REUSE, BATCH,事务隔离级别一般是有四种,但是TransactionIsolationLevel类中多了一种不支持事务的NONE,openSessionFromDataSource最后创建了一个DefaultSqlSession返回,源码如下:
// DefaultSqlSessionFactory类
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); //DefaultSqlSession持有一个Executor对象,所以先创建一个执行器 final Executor executor = configuration.newExecutor(tx, execType); //创建一个默认的SqlSession 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(); } }
//执行器枚举类ExecutorType和事务隔离枚举类TransactionIsolationLevel
public enum ExecutorType { SIMPLE, //简单执行器 REUSE, //可重用执行器 BATCH //批处理执行器 }
public enum TransactionIsolationLevel { NONE(Connection.TRANSACTION_NONE), //不支持 READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED), // 读提交 READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED), // 读未提交 REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ), // 可重复读 SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE), //可串行化 /** * A non-standard isolation level for Microsoft SQL Server. * Defined in the SQL Server JDBC driver {@link com.microsoft.sqlserver.jdbc.ISQLServerConnection} * * @since 3.5.6 */ SQL_SERVER_SNAPSHOT(0x1000); private final int level; TransactionIsolationLevel(int level) { this.level = level; } public int getLevel() { return level; } }
SqlSession创建完成之后,就是获取代理类了,就是SqlSession的getMapper方法,这个方法最终进入到MapperRegistry的getMapper方法,mapperProxyFactory会newInstance代理类返回,在这个代理类中会持有一个sqlSession对象,代理类表面是执行Mapper接口的方法,实际上是调用持有的sqlSession对象,sqlSession对象对象中持有一个Executor执行器,Executor是一个接口,UML类图如下
这里使用了装饰器模式,在执行器的单独篇章中讲,这里以一个select查询为例,sqlSession对象首先会使用缓存执行器CachingExecutor执行器的query方法,在这个方法中,首先会判断二级缓存是否开启,开启,则处理缓存逻辑,反之,进入到BaseExecutor的query方法处理,在这个方法里,会处理一级缓存,如果一级缓存没有命中,就会进入调用doQuery方法,在baseExecutor中doQuery是一个抽象方法,交由子类实现,这种类似于模板方法模式设计,BaseExecutor有三个实现类,这里默认使用SimpleExecutor,进入SimpleExecutor的doQuery方法,这里逻辑就跟jdbc非常相似了,首先会创建prepareStatement,执行SQL后,将结果交于ResultHandler处理返回。
// CachingExecutor类
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { //二级缓存处理逻辑 flushCacheIfRequired(ms); // 清空二级缓存 if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") //获取二级缓存 List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { //缓存为null,委托给BaseExecutor执行查询 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } //未开启二级缓存逻辑 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
// baseExecutor类
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } //如果查询堆栈是0(就是之前没有执行过)且Mapper的flushCacheRequired是true if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; //一级缓存中获取 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //缓存为空,从数据库获取 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }
// SimpleExecutor类
@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); } }
下面用一张图总结整个流程: