一、整体流程
Mybatis是一种ORM对象关系映射架构,实现Java Object和数据库字段映射。
如上图所示,Mybatis就是根据Java配置的数据源(driver、url、username、password)以及Mapper配置SQL(DQL查询、DML修改、DDL create)语句,基于JDBC底层的实现原理实现对数据库的操作。所以整体来说Mybatis工作大致分为三步:获取数据源、拿到需要执行的SQL、执行SQL语句并得到结果。我们以下面的程序入口来查看源码流程:
public class AppTest { @Test public void test() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); try { User user = session.selectOne("org.mybatis.example.UserMapper.selectUser", 1); System.out.println("user:{}"+user); } finally { session.close(); } } }
二、获取数据源
String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
如上代码所示,首先我们先来看看数据源的获取,查看如下代码,并进入到build方法:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { SqlSessionFactory var5; try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); var5 = this.build(parser.parse()); } catch (Exception var14) { throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException var13) { ; } } return var5; }
然后我们可以看到XMLConfigBuilder对XML进行parse:
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); this.localReflectorFactory = new DefaultReflectorFactory(); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; } public Configuration parse() { if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true; this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } } private void parseConfiguration(XNode root) { try { this.propertiesElement(root.evalNode("properties")); Properties settings = this.settingsAsProperties(root.evalNode("settings")); this.loadCustomVfs(settings); this.loadCustomLogImpl(settings); this.typeAliasesElement(root.evalNode("typeAliases")); this.pluginElement(root.evalNode("plugins")); this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectorFactoryElement(root.evalNode("reflectorFactory")); this.settingsElement(settings); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }
可以看到我们进行了typeAliases以及environments元素的加载,这实际就进行了数据源的XML解析以及获取数据源信息,如下配置所示:
<configuration> <properties resource="config.properties"/> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mybatis/UserMapper.xml"/> </mappers> </configuration>
接下来我们看看environmentsElement方法内部如何解析数据源:
private void environmentsElement(XNode context) throws Exception { if (context != null) { if (this.environment == null) { this.environment = context.getStringAttribute("default"); } Iterator var2 = context.getChildren().iterator(); while(var2.hasNext()) { XNode child = (XNode)var2.next(); String id = child.getStringAttribute("id"); if (this.isSpecifiedEnvironment(id)) { TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager")); DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource); this.configuration.setEnvironment(environmentBuilder.build()); } } } } private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); DataSourceFactory factory = (DataSourceFactory)this.resolveClass(type).getDeclaredConstructor().newInstance(); factory.setProperties(props); return factory; } else { throw new BuilderException("Environment declaration requires a DataSourceFactory."); } }
如图所示,可以知道一共有三种DataSourceFactory实现:
我们看下我们获取了配置之后,会在Configuration中通过setEnvironment存储环境信息,首先我们来看看Configuration类:
public class Configuration { protected Environment environment; } public final class Environment { private final String id; private final TransactionFactory transactionFactory; private final DataSource dataSource; }
因此可以知道数据源信息就存储在Configuration中的Environment属性中。如图所示:
因此整个流程代码就是:
>org.apache.ibatis.session.SqlSessionFactoryBuilder.build(java.io.InputStream)
>org.apache.ibatis.builder.xml.XMLConfigBuilder
>org.apache.ibatis.builder.xml.XMLConfigBuilder.parse
>org.apache.ibatis.builder.xml.XMLConfigBuilder.environmentsElement
>org.apache.ibatis.datasource.DataSourceFactory.getDataSource
>org.apache.ibatis.session.Configuration.setEnvironment###### set数据源
流程图如下:
三、SQL获取
在我们成功获取了所有数据源信息后,接下来就是获取SQL执行语句,在上面的配置加载过程中,有这么一句会进行Mapper的加载:
private void parseConfiguration(XNode root) { try { this.propertiesElement(root.evalNode("properties")); Properties settings = this.settingsAsProperties(root.evalNode("settings")); this.loadCustomVfs(settings); this.loadCustomLogImpl(settings); this.typeAliasesElement(root.evalNode("typeAliases")); this.pluginElement(root.evalNode("plugins")); this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectorFactoryElement(root.evalNode("reflectorFactory")); this.settingsElement(settings); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }
我们进入到方法内部进行查看,如下代码所示:
private void mapperElement(XNode parent) throws Exception { if (parent != null) { Iterator var2 = parent.getChildren().iterator(); while(true) { while(var2.hasNext()) { XNode child = (XNode)var2.next(); String resource; if ("package".equals(child.getName())) { resource = child.getStringAttribute("name"); this.configuration.addMappers(resource); } else { resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); XMLMapperBuilder mapperParser; InputStream inputStream; if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); inputStream = Resources.getResourceAsStream(resource); mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); inputStream = Resources.getUrlAsStream(url); mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments()); mapperParser.parse(); } else { if (resource != null || url != null || mapperClass == null) { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } Class<?> mapperInterface = Resources.classForName(mapperClass); this.configuration.addMapper(mapperInterface); } } } return; } } }
可以看到mapper的解析有四种方式(resource、url、class、package),其实对应配置方式就是:
<mappers> <mapper resource="mybatis/UserMapper.xml"/> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper class="com.generator.mapper.UserMapper"></mapper> <package name="com.generator.mapper"></package> </mappers>
我们以XML为例在方法内部查看时如何进行解析的,进入到XMLMapperBuilder中的parse流程:
public void parse() { if (!this.configuration.isResourceLoaded(this.resource)) { this.configurationElement(this.parser.evalNode("/mapper")); this.configuration.addLoadedResource(this.resource); this.bindMapperForNamespace(); } this.parsePendingResultMaps(); this.parsePendingCacheRefs(); this.parsePendingStatements(); }private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace != null && !namespace.equals("")) { this.builderAssistant.setCurrentNamespace(namespace); this.cacheRefElement(context.evalNode("cache-ref")); this.cacheElement(context.evalNode("cache")); this.parameterMapElement(context.evalNodes("/mapper/parameterMap")); this.resultMapElements(context.evalNodes("/mapper/resultMap")); this.sqlElement(context.evalNodes("/mapper/sql")); this.buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } else { throw new BuilderException("Mapper's namespace cannot be empty"); } } catch (Exception var3) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3); } } private void buildStatementFromContext(List<XNode> list) { if (this.configuration.getDatabaseId() != null) { this.buildStatementFromContext(list, this.configuration.getDatabaseId()); } this.buildStatementFromContext(list, (String)null); } private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { Iterator var3 = list.iterator(); while(var3.hasNext()) { XNode context = (XNode)var3.next(); XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException var7) { this.configuration.addIncompleteStatement(statementParser); } } }
然后我们看到了statementParse的parseStatementNode方法,进入到方法,可以看到:
public void parseStatementNode() { String id = this.context.getStringAttribute("id"); String databaseId = this.context.getStringAttribute("databaseId"); if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { String nodeName = this.context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = this.context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false); XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant); includeParser.applyIncludes(this.context.getNode()); String parameterType = this.context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = this.resolveClass(parameterType); String lang = this.context.getStringAttribute("lang"); LanguageDriver langDriver = this.getLanguageDriver(lang); this.processSelectKeyNodes(id, parameterTypeClass, langDriver); String keyStatementId = id + "!selectKey"; keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true); Object keyGenerator; if (this.configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = this.configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass); StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString())); Integer fetchSize = this.context.getIntAttribute("fetchSize"); Integer timeout = this.context.getIntAttribute("timeout"); String parameterMap = this.context.getStringAttribute("parameterMap"); String resultType = this.context.getStringAttribute("resultType"); Class<?> resultTypeClass = this.resolveClass(resultType); String resultMap = this.context.getStringAttribute("resultMap"); String resultSetType = this.context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { resultSetTypeEnum = this.configuration.getDefaultResultSetType(); } String keyProperty = this.context.getStringAttribute("keyProperty"); String keyColumn = this.context.getStringAttribute("keyColumn"); String resultSets = this.context.getStringAttribute("resultSets"); this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } } public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (this.unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } else { id = this.applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache); ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); this.configuration.addMappedStatement(statement); return statement; } }
因此我们发现SQL语句也被加载到Configuration中了,如图所示:
因此我们可以知道流程是这样子的:
>org.apache.ibatis.session.SqlSessionFactoryBuilder.build(java.io.InputStream) >org.apache.ibatis.builder.xml.XMLConfigBuilder >org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement >org.apache.ibatis.builder.xml.XMLMapperBuilder >org.apache.ibatis.builder.xml.XMLMapperBuilder.parse >org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>) >org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode >org.apache.ibatis.session.Configuration.addMappedStatement ######set 这个sql
流程图如下所示:
四、SQL执行
接下来我们看看怎么用存储的数据源信息以及SQL语句进行执行获取结果:
SqlSession session = sqlSessionFactory.openSession(); try { User user = session.selectOne("org.mybatis.example.UserMapper.selectUser", 1); System.out.println("user:{}"+user); } finally { session.close(); }
我们先看看openSession,我们以SqlSessionFactory的默认实现DefaultSqlSessionFactory为例,代码如下:
public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; public SqlSession openSession() { return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false); } 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; } }
可以看见我们初始化了一个SQLSession,接下来我们看selectOne如何调用SQL:
public <T> T selectOne(String statement) { return this.selectOne(statement, (Object)null); } public <T> T selectOne(String statement, Object parameter) { List<T> list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } } public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { List var5; try { MappedStatement ms = this.configuration.getMappedStatement(statement); var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception var9) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9); } finally { ErrorContext.instance().reset(); } return var5; }
我们可以看到会获取MappedStatement,然后调用executor进行执行,我们以SimpleExecutor为例,代码如下:
public class SimpleExecutor extends BaseExecutor { public SimpleExecutor(Configuration configuration, Transaction transaction) { super(configuration, transaction); }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; } }
我们接下来随便查看一个PreparedStatement的实现类PreparedStatementWrapper,代码如下:
public class PreparedStatementHandler extends BaseStatementHandler { public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement)statement; ps.execute(); return this.resultSetHandler.handleResultSets(ps); } }
继续看结果处理:
public class DefaultResultSetHandler implements ResultSetHandler { public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId()); List<Object> multipleResults = new ArrayList(); int resultSetCount = 0; ResultSetWrapper rsw = this.getFirstResultSet(stmt); List<ResultMap> resultMaps = this.mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); this.validateResultMapsCount(rsw, resultMapCount); while(rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount); this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null); rsw = this.getNextResultSet(stmt); this.cleanUpAfterHandlingResultSet(); ++resultSetCount; } String[] resultSets = this.mappedStatement.getResultSets(); if (resultSets != null) { while(rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId); this.handleResultSet(rsw, resultMap, (List)null, parentMapping); } rsw = this.getNextResultSet(stmt); this.cleanUpAfterHandlingResultSet(); ++resultSetCount; } } return this.collapseSingleResultList(multipleResults); } }
接下来看ResultSetWrapper:
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException { this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); this.resultSet = rs; ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); for(int i = 1; i <= columnCount; ++i) { this.columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i)); this.jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i))); this.classNames.add(metaData.getColumnClassName(i)); } }
因此可以看出为什么Mybatis是一个ORM模型架构,因为实现了数据库列和Java对象属性的一一映射,如下图所示:
综上所述,SQL执行代码流程如下:
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession() >org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource >org.apache.ibatis.session.Configuration.newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType) >org.apache.ibatis.session.defaults.DefaultSqlSession.DefaultSqlSession(org.apache.ibatis.session.Configuration, org.apache.ibatis.executor.Executor, boolean) >org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(java.lang.String, java.lang.Object) >org.apache.ibatis.executor.SimpleExecutor.doQuery >org.apache.ibatis.executor.statement.PreparedStatementHandler.query >org.apache.ibatis.executor.resultset.ResultSetWrapper.ResultSetWrapper
因此SQL执行的时序图如下图所示:
而且根据以上代码,可以看出Mybatis是基于JDBC实现的,也是使用Connection、Statement、PrepareStatement、ResultSet进行SQL运行。
五、Mybatis执行流程总结
根据代码走读,可以看出Mybatis执行流程如下所示:
- Mybatis根据配置项将数据源DataSource和Mapper加载到Configuration的environment和statement中;
- SqlSessionFactoryBuilder根据Configuration配置的数据源信息build一个SQLSessionFactory;
- SqlSessionFactory工厂通过OpenSession创建一个SqlSession;
- SqlSession调用自身的Executor根据COnfiguration中的statement配置创建一个StatementHandler进行SQL执行;
- StatementHandler执行的结果通过ResultSetWrapper实现数据库列和Java类属性列一一映射,得到Java结果。