Mybatis源码探究

Posted by ZhouJ000 on March 2, 2019

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录

MyBatis并不是一个完整的ORM框架,MyBatis只完成的是Relation到Object的映射,也就是其所说的data mapper framework,旨在更简单、更方便地完成数据库操作功能

核心类

mybatis-1

Configuration

Configuration是MyBatis中相当重要的一个类,可以提供许多配置,主要包括了mybatis-configuration.xml基础配置文件和mapper.xml映射器配置文件。在在spring提供的org.mybatis.spring.SqlSessionFactoryBean中,可以配置configLocation提供配置路径,基础配置文件中可以提供properties全局参数、setting设置、typeAliases别名、typeHandlers类型处理器、Mapper映射器。mapper.xml可以在org.mybatis.spring.mapper.MapperScannerConfigurer中定义扫包路径,也可以通过mybatis-configuration.xml中的Mapper映射器定义。其中mybatis-configuration.xml是在XMLConfigBuilder类中完成解析的;而mapper.xml在XMLMapperBuilder中解析完成,其中把对Statement的解析(即mapper.xml中SELECT、INSERT、UPDATE、DELETE定义部分)委托给XMLStatementBuilder来完成

Configuration与DefaultSqlSessionFactory是1:1的关联关系,这也就意味着在一个DefaultSqlSessionFactory衍生出来的所有SqlSession作用域里,Configuration对象是全局唯一的:

public Configuration(Environment environment) {
    this();
    this.environment = environment;
}

public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
}

SqlSessionFactory

每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为中心的。SqlSessionFactoryBuilder可以从XML配置文件或一个预先定制的Configuration的实例中构建出SqlSessionFactory的实例

看一下SqlSessionFactoryBuilder源码:

// 这里使用了Reader,或另一种使用InputStream;environment指定环境;properties指定配置
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
		// 封装配置文件相关信息: XPathParser + environment + properties
		XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
		// 解析配置信息,封装为Configuration,创建DefaultSqlSessionFactory
		return build(parser.parse());
    } catch (Exception e) {
		throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
		ErrorContext.instance().reset();
		try {
			reader.close();
		} catch (IOException e) {
			// Intentionally ignore. Prefer previous error.
		}
	}
}

public SqlSessionFactory build(Configuration config) {
	return new DefaultSqlSessionFactory(config);
}

可知详细的解析工作为XMLConfigBuilder的parse()方法:

public Configuration parse() {
	if (parsed) {
		throw new BuilderException("Each XMLConfigBuilder can only be used once.");
	}
	parsed = true;
	parseConfiguration(parser.evalNode("/configuration"));
	return configuration;
}

private void parseConfiguration(XNode root) {
	try {
		//issue #117 read properties first
		// 解析properties元素
		propertiesElement(root.evalNode("properties"));
		Properties settings = settingsAsProperties(root.evalNode("settings"));
		loadCustomVfs(settings);
		loadCustomLogImpl(settings);
		typeAliasesElement(root.evalNode("typeAliases"));
		pluginElement(root.evalNode("plugins"));
		objectFactoryElement(root.evalNode("objectFactory"));
		objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
		reflectorFactoryElement(root.evalNode("reflectorFactory"));
		settingsElement(settings);
		// read it after objectFactory and objectWrapperFactory issue #631
		// 解析environments元素
		environmentsElement(root.evalNode("environments"));
		databaseIdProviderElement(root.evalNode("databaseIdProvider"));
		typeHandlerElement(root.evalNode("typeHandlers"));
		mapperElement(root.evalNode("mappers"));
	} catch (Exception e) {
		throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
	}
}

像平时项目里配置的org.mybatis.spring.SqlSessionFactoryBean,内部也是通过SqlSessionFactoryBuilder的这种方式创建的,只不过Configuration的创建不同

SqlSession

SqlSession完全包含了面向数据库执行SQL命令所需的所有方法,可以通过SqlSession实例来直接执行已映射的SQL语句。可以通过SqlSessionFactory获得SqlSession的实例 mybatis-sqlsession

查看DefaultSqlSessionFactory中最基本的openSession()方法获取SqlSession:

public SqlSession openSession() {
	return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
	Transaction tx = null;
	try {
		// 获取Environment配置信息,包含了数据源和事务的配置
		final Environment environment = configuration.getEnvironment();
		final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
		tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
		// 内部实际上是通过excutor执行SQL的,Executor是对于Statement的封装
		final Executor executor = configuration.newExecutor(tx, execType);
		// 创建DefaultSqlSession对象
		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();
	}
}

MapperProxy

当我们在项目里配置好mapper和dao后,调用到自己dao里的方法就去执行相应的SQL了,这其实是对应的MapperProxy在代理 mybatis-mapperproxy

获取MapperProxy:

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

// Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

// MapperRegistry
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 {
		// ----> 最后还是通过MapperProxyFactory去创建实例
		return mapperProxyFactory.newInstance(sqlSession);
	} catch (Exception e) {
		throw new BindingException("Error getting mapper instance. Cause: " + e, e);
	}
}

// MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
	final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
	return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
	// 动态代理我们创建的dao接口
	return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

Executor

mybatis-excutor

那么既然是MapperProxy在代理我们的dao,那么是怎么执行到到SQL语句的?那么先从MapperProxy的Invoke方法来看看:

// MapperProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	try {
		// Object 或 Default方法
		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);
	}
	// 从methodCache获取,如果不存在创建一个new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()
	final MapperMethod mapperMethod = cachedMapperMethod(method);
	return mapperMethod.execute(sqlSession, args);
}

// MapperMethod  ----> 还是调用SqlSession的方法
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 UPDATE: {
			Object param = method.convertArgsToSqlCommandParam(args);
			result = rowCountResult(sqlSession.update(command.getName(), param));
			break;
		}
		case DELETE: {
			Object param = method.convertArgsToSqlCommandParam(args);
			result = rowCountResult(sqlSession.delete(command.getName(), param));
			break;
		}
		case SELECT:
			if (method.returnsVoid() && method.hasResultHandler()) {
			  executeWithResultHandler(sqlSession, args);
			  result = null;
			} else if (method.returnsMany()) {
			  result = executeForMany(sqlSession, args);
			} else if (method.returnsMap()) {
			  result = executeForMap(sqlSession, args);
			} else if (method.returnsCursor()) {
			  result = executeForCursor(sqlSession, args);
			} else {
			  Object param = method.convertArgsToSqlCommandParam(args);
			  result = sqlSession.selectOne(command.getName(), param);
			  if (method.returnsOptional()
				  && (result == null || !method.getReturnType().equals(result.getClass()))) {
				result = Optional.ofNullable(result);
			}
		}
        break;
		case FLUSH:
			result = sqlSession.flushStatements();
			break;
		default:
			throw new BindingException("Unknown execution method for: " + command.getName());
	}
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
}

// DefaultSqlSession
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
		// 获取
		MappedStatement ms = configuration.getMappedStatement(statement);
		// 实际还是Executor的方法,CURD最后到Executor就是调用它的update和query方法
		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();
    }
}

// Configuration					id, true
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
	if (validateIncompleteStatements) {
		buildAllStatements();
	}
	return mappedStatements.get(id);
}

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

然后看看Executor后续的步骤:

// BaseExecutor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 得到绑定sql
	BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建缓存key
	CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

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
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
		// 清理本地缓存
		clearLocalCache();
    }
    List<E> list;
    try {
		// queryStack递增,递归调用时上面不会再清理缓存
		queryStack++;
		// 先尝试从localCache去获取
		list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
		if (list != null) {
			// 如果查到localCache缓存,处理localOutputParameterCache
			handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
		} else {
			// 还是到DB去查询
			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
			// 如果是statement,清本地缓存
			clearLocalCache();
		}
    }
    return list;
}

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
		// ----> 实际调用子类方法
		list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
		localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
		localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
		throw new ExecutorException("Executor was closed.");
    }
    // 清理本地缓存
    clearLocalCache();
    // ----> 实际调用子类方法
    return doUpdate(ms, parameter);
}

BaseExecutor有3个实现doxxx的子类:SimpleExecutor、BatchExecutor、ReuseExecutor,那么用哪个呢?在DefaultSqlSessionFactory的openSession方法中可以指定ExecutorType,否则使用defaultExecutorType:

// Configuration
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE

// SimpleExecutor
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
		StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
		// 使用prepareStatement
		stmt = prepareStatement(handler, ms.getStatementLog());
		// 调用query方法,或update方法
		return handler.query(stmt, resultHandler);
    } finally {
		closeStatement(stmt);
    }
}

// RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch (ms.getStatementType()) {
		case STATEMENT:
			delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
			break;
		case PREPARED:
			delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
			break;
		case CALLABLE:
			delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
			break;
		default:
			throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
}

那么statementType是什么?通过参数往上找,发现是在XMLConfigBuilder的parseConfiguration方法里的mapperElement(root.evalNode("mappers"))开始,通过解析mapper的配置得到的,默认使用StatementType.PREPARED:

// XMLMapperBuilder
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
		configurationElement(parser.evalNode("/mapper"));
		configuration.addLoadedResource(resource);
		bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingCacheRefs();
    // -> ....
    parsePendingStatements();
}

private void configurationElement(XNode context) {
    try {
		String namespace = context.getStringAttribute("namespace");
		if (namespace == null || namespace.equals("")) {
			throw new BuilderException("Mapper's namespace cannot be empty");
		}
		builderAssistant.setCurrentNamespace(namespace);
		cacheRefElement(context.evalNode("cache-ref"));
		cacheElement(context.evalNode("cache"));
		parameterMapElement(context.evalNodes("/mapper/parameterMap"));
		resultMapElements(context.evalNodes("/mapper/resultMap"));
		sqlElement(context.evalNodes("/mapper/sql"));
		buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
		throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
}

StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

那么看下PreparedStatementHandler是怎么执行的:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // jdbc的PreparedStatement执行
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
}

总结mybatis-2

常见问题

#与$的区别

#{}: 解析为一个JDBC预编译语句(prepared statement)的参数标记符,一个#{}被解析为一个参数占位符
${}: 仅仅为一个纯碎的String替换,在动态SQL解析阶段将会进行变量替换

从前面可以看出,在Executor执行doxxx时,需要创建StatementHandler,里面会通过RoutingStatementHandler来路由选择SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler,分别对应了JDBC中的Statement、PreparedStatement和CallableStatement。看到PreparedStatement里面有个parameterize方法对statement参数化设置:

public void parameterize(Statement statement) throws SQLException {
	parameterHandler.setParameters((PreparedStatement) statement);
}

那么是不是用${}就是用了Statement,还是都使用默认PreparedStatement,在设置参数时和#{}区分的呢?之前知道创建SqlSessionFactory时候会解析配置信息,在XMLConfigBuilder总解析方法parseConfiguration里,会在mapperElement这步骤创建XMLMapperBuilder来解析mapper映射解析,

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
		for (XNode child : parent.getChildren()) {
			if ("package".equals(child.getName())) {
				String mapperPackage = child.getStringAttribute("name");
				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) {
					ErrorContext.instance().resource(resource);
					InputStream inputStream = Resources.getResourceAsStream(resource);
					// ----> XMLMapperBuilder
					XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
					mapperParser.parse();
				} else if (resource == null && url != null && mapperClass == null) {
					ErrorContext.instance().resource(url);
					InputStream inputStream = Resources.getUrlAsStream(url);
					// ----> XMLMapperBuilder
					XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
					mapperParser.parse();
				} else if (resource == null && url == null && mapperClass != null) {
					Class<?> mapperInterface = Resources.classForName(mapperClass);
					// ----> MapperAnnotationBuilder
					configuration.addMapper(mapperInterface);
				} else {
					throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
				}
			}
		}
    }
}

在XMLMapperBuilder中根据节点建立statement

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
		final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
		try {
			statementParser.parseStatementNode();
		} catch (IncompleteElementException e) {
			configuration.addIncompleteStatement(statementParser);
		}
    }
}

// XMLStatementBuilder
public void parseStatementNode() {
    // ...
    String lang = context.getStringAttribute("lang");
    // 默认是XMLLanguageDriver.class
    LanguageDriver langDriver = getLanguageDriver(lang);
    // ...
    
	SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

最后那个builderAssistant.addMappedStatement是不是很熟悉,前面讲的通过statementType判断就是这里设置的。那么sql解析在哪里呢?其实是在SqlSource中进行的,那么来看langDriver.createSqlSource方法:

// XMLLanguageDriver
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
}

// XMLScriptBuilder
public SqlSource parseScriptNode() {
    // 判断
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
		sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
		sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}

protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
		XNode child = node.newXNode(children.item(i));
		// CDATA 或 文本
		if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
			String data = child.getStringBody("");
			TextSqlNode textSqlNode = new TextSqlNode(data);
			// ----> 判断
			if (textSqlNode.isDynamic()) {
				// 添加TextSqlNode
				contents.add(textSqlNode);
				isDynamic = true;
			} else {
				// 添加StaticTextSqlNode
				contents.add(new StaticTextSqlNode(data));
			}
		// 元素节点,用NodeHandler处理各个类型的子节点
		} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
			String nodeName = child.getNode().getNodeName();
			// 有TrimHandler、WhereHandler、SetHandler、IfHandler、ChooseHandler等
			NodeHandler handler = nodeHandlerMap.get(nodeName);
			if (handler == null) {
				throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
			}
			handler.handleNode(child, contents);
			isDynamic = true;
		}
    }
    return new MixedSqlNode(contents);
}

在TextSqlNode类中创建GenericTokenParser去解析文本:

public boolean isDynamic() {
    // DynamicCheckerTokenParser
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    // 创建GenericTokenParser去解析
    GenericTokenParser parser = createParser(checker);
    // 解析文本,会调用TokenHandler(checker)的handleToken将isDynamic设为true
    parser.parse(text);
    return checker.isDynamic();
}

private GenericTokenParser createParser(TokenHandler handler) {
	// 通过 ${ 和 } 截取判断
	return new GenericTokenParser("${", "}", handler);
}

那么如果有${}设为true,创建DynamicSqlSource:

public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
}

那么如果没有${}就会创建RawSqlSource:

public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
}

public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
	SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
	Class<?> clazz = parameterType == null ? Object.class : parameterType;
	// 解析
	sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}

// SqlSourceBuilder
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    // 解析#{},使用ParameterMappingTokenHandler
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    // ----> 解析
    String sql = parser.parse(originalSql);
    // 创建StaticSqlSource,所以RawSqlSource内部持有一个SqlSource,最后将?替换参数放入parameterMappings中
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

这边都用到了GenericTokenParser的parse方法:

public String parse(String text) {
    if (text == null || text.isEmpty()) {
		return "";
    }
    // search open token
    int start = text.indexOf(openToken);
    if (start == -1) {
		return text;
    }
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    while (start > -1) {
		if (start > 0 && src[start - 1] == '\\') {
			// this open token is escaped. remove the backslash and continue.
			builder.append(src, offset, start - offset - 1).append(openToken);
			offset = start + openToken.length();
		} else {
			// found open token. let's search close token.
			if (expression == null) {
				expression = new StringBuilder();
			} else {
				expression.setLength(0);
			}
			builder.append(src, offset, start - offset);
			offset = start + openToken.length();
			int end = text.indexOf(closeToken, offset);
			while (end > -1) {
				if (end > offset && src[end - 1] == '\\') {
					// this close token is escaped. remove the backslash and continue.
					expression.append(src, offset, end - offset - 1).append(closeToken);
					offset = end + closeToken.length();
					end = text.indexOf(closeToken, offset);
				} else {
					expression.append(src, offset, end - offset);
					offset = end + closeToken.length();
					break;
				}
			}
			if (end == -1) {
				// close token was not found.
				builder.append(src, start, src.length - start);
				offset = src.length;
			} else {
				// ----> 调用handler的handleToken方法
				builder.append(handler.handleToken(expression.toString()));
				offset = end + closeToken.length();
			}
		}
		start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
		builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
}

// 对于#{}是用的ParameterMappingTokenHandler
public String handleToken(String content) {
	parameterMappings.add(buildParameterMapping(content));
	return "?";
}

很显然,对于#{}的会使用?占位符,但是如果对于${}#{}都有的SQL来说,DynamicSqlSource不是啥都没做么。先不急,当拿到SqlSource后会创建MappedStatement.Builder,其中持有SqlSource,最后返回MappedStatement。这个类是不是很熟悉,正是在DefaultSqlSession调用select、update等方法时候需要获取的:

// BaseExecutor
BoundSql boundSql = ms.getBoundSql(parameter);

// MappedStatement
public BoundSql getBoundSql(Object parameterObject) {
    // 调用getBoundSql方法
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
		boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }
    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
		String rmId = pm.getResultMapId();
		if (rmId != null) {
			ResultMap rm = configuration.getResultMap(rmId);
			if (rm != null) {
				hasNestedResultMaps |= rm.hasNestedResultMaps();
			}
		}
    }
    return boundSql;
}

// 1. RawSqlSource
public BoundSql getBoundSql(Object parameterObject) {
    // 之前说过,内部持有的是StaticSqlSource
    return sqlSource.getBoundSql(parameterObject);
}
// StaticSqlSource
public BoundSql getBoundSql(Object parameterObject) {
	// 很简单,因为都处理过了
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}

// 2. DynamicSqlSource
public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // ----> 实际调用的是TextSqlNode的applay方法,将会替换${}
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    // ----> 解析#{},和之前的一样
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 全部加入metaParameters
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
}

// TextSqlNode
public boolean apply(DynamicContext context) {
    // GenericTokenParser("${", "}", handler),使用BindingTokenParser处理${}
    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
    // 调用GenericTokenParser.parse方法,之前讲过,这样就进入BindingTokenParser的handleToken方法
    context.appendSql(parser.parse(text));
    return true;
}

// TextSqlNode,里面应该就是对${}进行直接参数替换
public String handleToken(String content) {
	Object parameter = context.getBindings().get("_parameter");
	if (parameter == null) {
		context.getBindings().put("value", null);
	} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
		context.getBindings().put("value", parameter);
	}
	// 通过OGNL获取,返回直接append到SQL中
	Object value = OgnlCache.getValue(content, context.getBindings());
	String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"
	checkInjection(srtValue);
	return srtValue;
}

// SqlSourceBuilder,由于有可能同时有#{}的情况,因此还要处理,详细就和之前的一样了
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    // 对#{}的GenericTokenParser
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

这样就拿到了boundSql,其在创建cacheKey的时候会用到,还会一直传递到SimpleExecutor的比如doQuery方法,之前没有分析是怎么创建Statement的,现在来看一下:

// 获取Configuration
Configuration configuration = ms.getConfiguration();
// 先会创建StatementHandler,即PreparedStatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建
stmt = prepareStatement(handler, ms.getStatementLog());


private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
	Statement stmt;
	// JDBC步骤 获取Connection
	Connection connection = getConnection(statementLog);
	// 准备,根据Connection创建prepareStatement(因为是PreparedStatementHandler),设置超时时间
	stmt = handler.prepare(connection, transaction.getTimeout());
	// ----> 确认参数
	handler.parameterize(stmt);
	return stmt;
}

// PreparedStatementHandler
public void parameterize(Statement statement) throws SQLException {
	parameterHandler.setParameters((PreparedStatement) statement);
}

// DefaultParameterHandler
public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 获取参数
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
		for (int i = 0; i < parameterMappings.size(); i++) {
			ParameterMapping parameterMapping = parameterMappings.get(i);
			if (parameterMapping.getMode() != ParameterMode.OUT) {
				// 属性值
				Object value;
				// 属性名
				String propertyName = parameterMapping.getProperty();
				if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
					value = boundSql.getAdditionalParameter(propertyName);
				} else if (parameterObject == null) {
					value = null;
				} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
					value = parameterObject;
				} else {
					MetaObject metaObject = configuration.newMetaObject(parameterObject);
					value = metaObject.getValue(propertyName);
				}
				TypeHandler typeHandler = parameterMapping.getTypeHandler();
				JdbcType jdbcType = parameterMapping.getJdbcType();
				if (value == null && jdbcType == null) {
					jdbcType = configuration.getJdbcTypeForNull();
				}
				try {
					// 设入PreparedStatement中
					typeHandler.setParameter(ps, i + 1, value, jdbcType);
				} catch (TypeException | SQLException e) {
					throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
				}
			}
		}
    }
}

之后就如前面分析的,调用PreparedStatement的execute()方法

返回怎么绑定对象

之前看了这么请求数据库的,那么Mybatis完成Relation到Object的映射,是怎么操作的呢?通过PreparedStatementHandler的query方法可以看出,最后返回的是resultSetHandler.handleResultSets(ps),使用的是ResultSetHandler:

// DefaultResultSetHandler
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    final List<Object> multipleResults = new ArrayList<>();
    int resultSetCount = 0;
    // 获取第一个结果集,将传统JDBC的ResultSet包装成一个包含结果列元信息的ResultSetWrapper对象,通常只会有一个
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 从配置中读取对应的ResultMap,是将jdbc类型数据映射为DTO对象的重要类
	List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // 校验,当查询返回的ResultMap数量小于1时,说明没有找到对应的ResultType或者ResultMap可以将结果映射出来
    validateResultMapsCount(rsw, resultMapCount);
    // 一个Statement执行了多个sql语句,需要循环处理每个结果集
    while (rsw != null && resultMapCount > resultSetCount) {
		ResultMap resultMap = resultMaps.get(resultSetCount);
		// ----> 完成映射,将结果加到入multipleResults中
		handleResultSet(rsw, resultMap, multipleResults, null);
		rsw = getNextResultSet(stmt);
		cleanUpAfterHandlingResultSet();
		resultSetCount++;
    }
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
		while (rsw != null && resultSetCount < resultSets.length) {
			ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
			if (parentMapping != null) {
				String nestedResultMapId = parentMapping.getNestedResultMapId();
				ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
				handleResultSet(rsw, resultMap, null, parentMapping);
			}
			rsw = getNextResultSet(stmt);
			cleanUpAfterHandlingResultSet();
			resultSetCount++;
		}
    }
    return collapseSingleResultList(multipleResults);
}


private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
		if (parentMapping != null) {
			// 子映射
			handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
		} else {
			if (resultHandler == null) {
				DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
				// ----> 处理行信息
				handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
				multipleResults.add(defaultResultHandler.getResultList());
			} else {
				// 处理行信息
				handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
			}
		}
	} finally {
		// issue #228 (close resultsets)
		closeResultSet(rsw.getResultSet());
    }
}

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    // 存在结果嵌套,有子映射或内映射的情况
    if (resultMap.hasNestedResultMaps()) {
		ensureNoRowBounds();
		checkResultHandler();
		handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
		// ----> 非嵌套
		handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    // 跳过offset指定的记录条数
    skipRows(resultSet, rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
		// discriminator的处理,可以根据条件选择不同的映射
		ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
		// ----> 数据columnName与对象propertyName的映射匹配
		Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
		// 保存当前解析的对象,加入resultHandler.resultList中
		storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
}

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 使用反射创建对应的DTO对象
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
		// 拿到列信息
		final MetaObject metaObject = configuration.newMetaObject(rowValue);
		boolean foundValues = this.useConstructorMappings;
		if (shouldApplyAutomaticMappings(resultMap, false)) {
			// 自动映射,根据columnName和type属性名映射赋值
			foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
		}
		// ----> 调用setter方法设置信息,根据我们配置ResultMap的column和property映射赋值
		foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
		foundValues = lazyLoader.size() > 0 || foundValues;
		rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
}

private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
		String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
		if (propertyMapping.getNestedResultMapId() != null) {
			// the user added a column attribute to a nested result map, ignore it
			column = null;
		}
		if (propertyMapping.isCompositeResult()
			  || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
			  || propertyMapping.getResultSet() != null) {
			// ----> 数据转换
			Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
			// issue #541 make property optional
			final String property = propertyMapping.getProperty();
			if (property == null) {
				continue;
			} else if (value == DEFERRED) {
				foundValues = true;
				continue;
			}
			if (value != null) {
				foundValues = true;
			}
			if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
				// gcode issue #377, call setter on nulls (value is not 'found')
				// ----> 使用了setter方法设置值
				metaObject.setValue(property, value);
			}
		}
    }
    return foundValues;
}

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    if (propertyMapping.getNestedQueryId() != null) {
		return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if (propertyMapping.getResultSet() != null) {
		addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
		return DEFERRED;
    } else {
		// TypeHandler转换器
		final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
		// 数据库表的列名
		final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
		return typeHandler.getResult(rs, column);
    }
}

大体如上代码,为每个结果生成一个java对象,根据构造方法实例化,自动映射、根据ResultMap映射,里面会用到TypeHandler转换。这只是简单映射,还有一个复杂嵌套映射handleRowValuesForNestedResultMap方法,具体没看,以后补充吧

缓存

MyBatis提供了一级缓存和二级缓存的支持:
1、一级缓存: 基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,不同的SqlSession之间的缓存数据区域(HashMap)是互相不影响的。当Session flush或close之后,该Session中的所有Cache就将清空
2、二级缓存:与一级缓存其机制相同,默认也是采用PerpetualCache,HashMap存储,不同在于其存储作用域为Mapper(Namespace),多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。并且可自定义存储源,如Ehcache等
注意:对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了C/U/D操作后,默认该作用域下所有select中的缓存将被clear

缓存键CacheKey的格式为:cacheKey = ID + offset + limit + sql + parameterValues + environmentId

通常可能会使用默认的一级缓存;不会开启使用二级缓存,因为是Mapper(namespace)级别的,还容易掉坑。现在实际上都会使用Memcache、Redis进行缓存结果,所以项目上不需要。由于平时没怎么用过Mybatis的缓存,所以扩展看下:
mybatis一级缓存二级缓存
你真的会用Mybatis的缓存么,不知道原理的话,容易踩坑哦

Scope

SqlSessionFactory:可以认为是一个数据库连接池,一个应用连接几个数据库就有几个,但都该是单例,所以最佳作用域是应用作用域

SqlSession:相当于数据库的一个连接,是线程级别的。由于SqlSession是非线程安全的,不能被共享,所以最佳作用域是请求或方法作用域

Mapper:代表SqlSession中的一个数据库请求步骤,从SqlSession中获得,所以肯定小于等于SqlSession的作用域与生命周期,最佳作用域是方法作用域

namespace

命名空间,用于绑定我们写的dao接口,唯一性,使用完全包名+文件名绑定。而dao里的方法名绑定对应mapper里的sql id名。通过接口全限名+方法名可以唯一定位一个MappedStatement,因此dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略

扩展:
mybatis 3.x源码深度解析与最佳实践
Mybatis使用的设计模式