MyBatis运行原理
MyBatis运行原理
前言
- 第一次阅读源码,或许有那么一点点参考意见,仅作个人记录,后续会再改一改。
- Mybati 版本号: 3.5.13,配置文件是以XML的方式
- 请配合标题查看代码中有注释的部分,笔者只贴部分运行的代码,其他在运行中没有使用到的代码笔者删除,不是笔者不想贴,是太长了影响阅读。请各位读者多复制类名和方法名去寻找或者按照debug流程,还有一定要看markdown的大纲
配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<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://127.0.0.1:3306/test?allowMultiQueries=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 映射文件地址,在SpringBoot中是 mapper-locations: classpath:mapper/**/*.xml-->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/DeptMapper.xml"/>
</mappers>
</configuration>
运行dome
- debug断点打字 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream) 进入build方法
/**
* <P>运行原理配用代码</P>
*
* @author unknown
* @since 2023/10/10 01:06
* {@link MybatisSourceCodeExplanation} 文字解析
*/
public class OperatingPrincipleTest {
public static void main(String[] args) throws IOException {
// 读取Mybatis配置文件.获取输入流
InputStream resourceAsStream = getResourceAsStream("mybatis-config.xml");
/*
构建 SqlSessionFactorBean 类,在下面的 SqlSessionFactoryBuilder 标题中解释了如何构建 SqlSessionFactory 类
其实 SqlSessionFactory 类中值有一个类 Configuration , 在构建的过程中都是在为 Configuration 类进行赋值
*/
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获取SqlSession.设置自动提交数据
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 获取Mapper接口类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User byId = mapper.findById(2);
System.out.println("查询数据: " + byId);
// 关闭连接
sqlSession.close();
}
}
解析运行流程
SqlSessionFactoryBuilder
-
解析
mybatis-config.xml
配置文件,为Configuration
类设置默认值(也就是Mybatis中有些默认配置) -
解析
Mapper.xml
文件,添加到Configuration
类中的一个Map
集合-
以增删改查标签中的
ID
属性为key
,MappedStatement
为value
,MappedStatement
中包含了每一个增改删除标签。 -
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>
-
-
解析
Mapper.xml
文件中namespace
的值,添加到Configuration
类中的MapperRegistry
对象中-
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
-
MapperRegistry
对象包含了项目中所有namespace
的值
-
-
至于如何读取配置文件信息,在源码的 77行 打下断点
// 在SqlSessionFactoryBuilder类中,对 build 方法进行了重载,而 build 方法最终返回 SqlSessionFactory 对象
public class SqlSessionFactoryBuilder {
// reader: 读取配置文件流
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
// reader: 读取配置文件流
// environment: Mybatis配置文件中的environment标签,在mybatis-config.xml配置文件中可以看到是关于数据库信息的配置
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
// reader: 读取配置文件流
// properties:以 Properties 文件形式存在的配置文件
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
// reader: 读取配置文件流
// environment: Mybatis配置文件中的environment标签,在mybatis-config.xml配置文件中可以看到是关于数据库信息的配置
// properties:以 Properties 文件形式存在的配置文件
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
// inputStream: Mybatis配置文件流
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
// inputStream: Mybatis配置文件流
// environment: Mybatis配置文件中的environment标签,在mybatis-config.xml配置文件中可以看到是关于数据库信息的配置
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
// inputStream: Mybatis配置文件流
// environment: Mybatis配置文件中的environment标签,在mybatis-config.xml配置文件中可以看到是关于数据库信息的配置
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
// inputStream: Mybatis配置文件流
// environment: Mybatis配置文件中的environment标签,在mybatis-config.xml配置文件中可以看到是关于数据库信息的配置
// properties:以 Properties 文件形式存在的配置文件
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//>>>解析配置文件(具体看下个标题),调用最后一个build方法,返回 SqlSessionFactory 的默认实现类
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
// 返回 SqlSessionFactory 的默认实现类
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
parser.parse()
-
构建
XMLConfigBuilder
对象,在构造方法中调用parse
方法解析mybatis-config.xml
-
private XMLConfigBuilder(Class<? extends Configuration> configClass, XPathParser parser, String environment, Properties props) { super(newConfig(configClass)); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); // 在解析配置文件时,判断是否已解析 this.parsed = false; this.environment = environment; // 调用类中的parser this.parser = parser; }
-
-
读取
mybatis-config.xml
文件的configuration
标签parser.evalNode("/configuration")
: 返回XNode
对象,该对象包含mybatis-config.xml
文件的全部内容
-
读取
XNode
对象中的properties
标签- propertiesElement(root.evalNode("properties"));
-
读取
settings
标签,转为Properties
对象- Properties settings = settingsAsProperties(root.evalNode("settings"));
-
调用
settingsElement
方法,当配置文件内有些属性是没有配置的时候,设置默认属性值并保存在configuration
对象中 -
settingsElement(settings);
-
调用
mapperElement
方法,解析业务接口对应的Mapper
文件,判断引入方式, 获取文件地址- mapperElement(root.evalNode("mappers"));
-
通过文件地址解析,具体查看下个小标题
- mapperParser.parse();
public class XMLConfigBuilder extends BaseBuilder {
// 构建XMLConfig文件对象,在构造方法中调用 parser 方法解析
private XMLConfigBuilder(Class<? extends Configuration> configClass, XPathParser parser, String environment,
Properties props) {
super(newConfig(configClass));
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
// 在解析配置文件时,判断是否已解析
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
public Configuration parse() {
// 在 XMLConfigBuilder 方法赋值,也就是在构建XMLConfigBuilder对象时
// SqlSessionFactoryBuilder类中的XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// parser.evalNode("/configuration") : 返回XNode对象,该对象是"mybatis-config.xml"文件的全部内容
parseConfiguration(parser.evalNode("/configuration"));
// 返回 configuration 对象,此对象包含了所有的配置信息如下:
// "mybatis-config.xml"
// 我们业务接口对应的mapper.xml
// Statement保持的正删改查信息
// MapperRegistry中保存的namespace的值
return configuration;
}
// 读取"mybatis-config.xml"文件中的配置内容
private void parseConfiguration(XNode root) {
try {
// 先读取properties相关的属性
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
// 读取settings相关的属性
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):"设置默认的配置属性"
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析业务接口对应的Mapper.xml(例如UserMapper.xml)
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
// 当配置文件内有些属性是没有配置的时候,设置默认属性值并保存在configuration对象中
private void settingsElement(Properties props) {
// 看到这些代码的时,想必所有的配置文件都是这样的,有配置属性的加载配置属性,没有配置属性的设置默认值
configuration
.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(
AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(
stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
configuration.setArgNameBasedConstructorAutoMapping(
booleanValueOf(props.getProperty("argNameBasedConstructorAutoMapping"), false));
configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
configuration.setNullableOnForEach(booleanValueOf(props.getProperty("nullableOnForEach"), false));
}
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 获取子节点,遍历
for (XNode child : parent.getChildren()) {
// 判断"mybatis-config.xmml"中引入的方式,本文用的是 resource 标签
// <mappers>
// <mapper resource="mapper/UserMapper.xml"/>
// <mapper resource="mapper/DeptMapper.xml"/>
// </mappers>
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 以 resource 属性引入的方式
String resource = child.getStringAttribute("resource");
// 以 url 属性引入的方式
String url = child.getStringAttribute("url");
// 以 class 属性引入的方式
String mapperClass = child.getStringAttribute("class");
// 判断引入方式,如果以 resource 不等于 null 并且 url 等于 null 并且 class 等于 null
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
configuration.getSqlFragments());
// 调用 mapperParser.parse();
mapperParser.parse();
}
// 判断引入方式,如果以 resource 等于 null 并且 url 不等于 null 并且 class 等于 null
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try (InputStream inputStream = Resources.getUrlAsStream(url)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,
configuration.getSqlFragments());
// 调用 mapperParser.parse(); 具体逻辑查看下面标题 mapperParser.parse()
mapperParser.parse();
}
// 判断引入方式,如果以 resource 等于 null 并且 url 等于 null 并且 class 不等于 null
} else if (resource == null && url == null && mapperClass != null) {
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.");
}
}
}
}
}
}
mapperParser.parse()
- 解析业务接口Mapper对应的xml文件
public class XMLMapperBuilder extends BaseBuilder {
// 解析业务接口Mapper对应的xml文件,由于是for循序调用,每次解析一个xml文件
public void parse() {
// 判断资源是否已被加载,返回false,加上!取反
if (!configuration.isResourceLoaded(resource)) {
// 获取xml文件中的 Mapper 标签
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 看完标题上面configurationElement解析mapper到为buildParameterMapping的方法再来看此方法
// 添加本地资源和将namespace的值添加到 MapperRegistry 对象中
bindMapperForNamespace();
}
// 以下三个方法笔者debug时没有参数,看不到执行处理过程
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
// 获取 mapper 标签
String namespace = context.getStringAttribute("namespace");
// 判断是否为 null
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置当前命名空间
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
// 获取 parameterMap 标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 获取 resultMap 标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 获取 SQL 片段
sqlElement(context.evalNodes("/mapper/sql"));
// 解析 select|insert|update|delete 标签
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);
}
}
// 解析 select|insert|update|delete 标签代码实现
private void buildStatementFromContext(List<XNode> list) {
// 如果 databaseId 不等于 null,
// databaseId 是多数据源标识
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
// 继续调用 buildStatementFromContext方法
buildStatementFromContext(list, null);
}
// 解析 select|insert|update|delete 标签代码实现
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// 遍历所有 select|insert|update|delete 标签,每个标签都是一个单独的 context 对象
for (XNode context : list) {
// 构建解析 select|insert|update|delete 标签的 XMLStatementBuilder 对象
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context,
requiredDatabaseId);
try {
// 这才是真正的解析业务接口Mapper对应的xml文件代码 具体逻辑查看下级标题 parseStatementNode()
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
private void bindMapperForNamespace() {
// 获取 namespace 标签的值
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// ignore, bound type is not required
}
if (boundType != null && !configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
// 这行代码有趣,看注释说: spring可能不知道真实的资源名称,因此我们设置一个标志来防止从映射器接口再次加载该资源.
configuration.addLoadedResource("namespace:" + namespace);
// 将namespace的值添加到 MapperRegistry 对象中,和Statement对象包含所有的增删改查一样,MapperRegistry包含这所有的mapper.xml文件地址
// 在MapperRegistry对象中有两个属性:
// 1. private final Configuration config;
// 2. private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
configuration.addMapper(boundType);
}
}
}
}
parseStatementNode()
- 解析 mapper.xml 文件中的 select | insert | update | delete 标签(
public class XMLStatementBuilder extends BaseBuilder {
// 解析 select|insert|update|delete 标签代码实现
public void parseStatementNode() {
// 获取select|insert|update|delete标签内的属性ID ,以下只写属性,不在写select|insert|update|delete标签内
String id = context.getStringAttribute("id");
// 获取 databaseId 的属性值
String databaseId = context.getStringAttribute("databaseId");
// 判断当前数据库ID是否和标签内的databaseId一致
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 获取标签名称.如 select|insert|update|delete 标签
String nodeName = context.getNode().getNodeName();
// 转换成枚举对象字段属性
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 判断是否是 select 标签,以下几乎都是对 select|insert|update|delete 标签内的属性进行判断,就不写注释了
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
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");
if (resultTypeClass == null && resultMap == null) {
resultTypeClass = MapperAnnotationBuilder.getMethodReturnType(builderAssistant.getCurrentNamespace(), id);
}
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
boolean dirtySelect = context.getBooleanAttribute("affectData", Boolean.FALSE);
// 把 mapper.xml 中的每个标签和标签中的属性解析并且添加到 MappedStatement 中 查看下面标题 addMappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
}
}
buildParameterMapping
- 把mapper.xml标签中解析完的属性进行校验并且构建 MappedStatement 对象
- 换而言之,每个 MappedStatement 对象就是 select | insert | update | delete 标签的所有内容
public class MapperBuilderAssistant extends BaseBuilder {
// XMLStatementBuilder类中调用的 addMappedStatement 方法
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, boolean dirtySelect) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
// 命名规范校验
id = applyCurrentNamespace(id, false);
// 构建 MappedStatement 对象
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType)
.keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang)
.resultOrdered(resultOrdered).resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType)
.flushCacheRequired(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect);
// 用于获取与给定映射语句相关的参数映射信息
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
// configuration 中定义一个 mappedStatements 的 map 集合,将 Statement 添加到 map 集合中.
// 在这个for循环中,至此是不是可以推测项目类中所有的增删改查都会存在 mappedStatements 的 map 集合中?
configuration.addMappedStatement(statement);
return statement;
}
}
sqlSessionFactory.openSession(true)
-
断点打在 SqlSession sqlSession = sqlSessionFactory.openSession(true),进入openSession方法
-
获取数据库信息构建 TransactionFactory 类
-
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
-
-
通过数据库配置信息,事物隔离级别,是否自动提交构建 Transaction 类
-
Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
-
-
通过Transaction类和枚举对象 ExecutorType(默认SIMPLE) 构建 Executor类
-
final Executor executor = configuration.newExecutor(tx, execType);
-
使用 configuration && executor && autoCommit 构建 DefaultSqlSession 类并且返回 DefaultSqlSession
-
return new DefaultSqlSession(configuration, executor, autoCommit);
-
// 看源码和SqlSessionFactoryBuilder类一样,有多种构造方法
public class DefaultSqlSessionFactory implements SqlSessionFactory {
/*
configuration.getDefaultExecutorType(): 获取默认的ExecutorType-> SIMPLE,此枚举有三个字段,SIMPLE | REUSE | BATCH
SIMPLE: 在SIMPLE执行器下,每次执行SQL语句时都会创建一个新的数据库连接,执行完毕后立即关闭连接。这种执行器类型适用于简单的查询和非事务性操作,但可能在频繁执行SQL语句的情况下引入较大的开销。
REUSE: MyBatis会重复使用现有的数据库连接,而不是每次都创建新的连接。这可以显著降低连接创建和销毁的开销,适用于频繁执行多个SQL语句的情况,例如事务中的多个查询。
BATCH: BATCH 执行器类型通常适用于批量操作,MyBatis 会收集多个 SQL 语句,并在执行时一次性提交它们,但不适用于单个查询操作。
null: 事物隔离级别
autoCommit: 是否设置自动提交 false | true
*/
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
boolean autoCommit) {
Transaction tx = null;
try {
// 获取"mybatis-config.xml"中的数据库配置
final Environment environment = configuration.getEnvironment();
// 通过数据库配置获取 TransactionFactory 类
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 通过数据库配置信息,事物隔离级别,是否自动提交构建 Transaction 类
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
/*
通过Transaction类和枚举对象 ExecutorType(默认SIMPLE) 构建 Executor类,如果开启了二级缓存那么会创建一个缓存执行器,在执行增删改 查方法时会优先查询缓存,详细内容看 newExecutor 标题
*/
final Executor executor = configuration.newExecutor(tx, execType);
// 使用 configuration && executor&& autoCommit 构建 DefaultSqlSession 类,DefaultSqlSession 实现 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();
}
}
}
newExecutor
- 根据 ExecutorType 类型构建 Executor 类,如果开启二级缓存,则构建 CachingExecutor 对象
public class Configuration {
//创建 Executor 类
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 三元表达式,判断 ExecutorType 的类型
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor;
// 根据 ExecutorType 类型构建 Executor 类
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 是否开启二级缓存,如果开启则构建 CachingExecutor 对象
if (cacheEnabled) {
// 重点 查看下面标题 CachingExecutor
executor = new CachingExecutor(executor);
}
// 将 executor 添加到拦截链,使用所有拦截器包装 Executor 类,mybatis也有一个拦截器可以实现
return (Executor) interceptorChain.pluginAll(executor);
}
}
CachingExecutor
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
// 在 CachingExecutor 的构造方法中传入了 Executor类,只不过在 Executor 类中的 query 方法增加了缓存处理
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
@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);
}
}
sqlSession.getMapper(UserMapper.class)
- 调用 DefaultSqlSession 类中的 getMapper 方法
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
// 调用 Configuration.getMapper 方法
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
}
// 这里不是内部类,而是笔者懒,直接找到 Configuration 类中的 getMapper 方法粘贴到这里
// 调用 mapperRegistry.getMapper方法
public class Configuration {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 查看下面标题 mapperRegistry.getMapper
return mapperRegistry.getMapper(type, sqlSession);
}
}
mapperRegistry.getMapper
public class MapperRegistry {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取 MapperProxyFactory 类
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 返回一个MapperProxy代理类
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
public class MapperProxyFactory<T> {
public T newInstance(SqlSession sqlSession) {\
// 使用 sqlSession , mapperInterface , methodCache 构建 MapperProxy 类
// sqlSession: 包configuration,Executor,是否自动提交
// mapperInterface: 系统业务接口.如UserMap.java
// methodCache: 方法缓存
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// MapperProxy对象实现了 InvocationHandler 接口,此接口是JavaJDK动态代理一起使用
return newInstance(mapperProxy);
}
}
mapper.findById(2)
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
// 构造方法,看源码记得要看下构造方法
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
static {
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (NoSuchMethodException e) {
privateLookupIn = null;
}
privateLookupInMethod = privateLookupIn;
Constructor<Lookup> lookup = null;
if (privateLookupInMethod == null) {
// JDK 1.8
try {
lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
lookup.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
e);
} catch (Exception e) {
lookup = null;
}
}
lookupConstructor = lookup;
}
// proxy: 代理类
// method: 执行的方法
// args: SQL参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 判断method.getDeclaringClass()是不是Object类中的方法,如hashCode和equals和toString
// 因为method.getDeclaringClass() 是代理类 ,Object是所有类的父类!
if (Object.class.equals(method.getDeclaringClass())) {
// 如果是就直接执行Object方法
return method.invoke(this, args);
}
// 调用内部接口的invoke方法
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// 定义一个内部接口
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
// 内部接口实现类
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
// 内部类构造方法,虽然定义了一个新的内部类,但看构造方法还是使用的是 MapperMethod 对象
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
// 再次调用 MapperMethod 的 invoke 方法.具体流程查看 mapperMethod.execute 标题
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
}
mapperMethod.execute
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
// MapperMethod 的 invoke 方法
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);
// 如果方法返回Map集合
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
// 如果方法返回游标
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
// 以上结果都不满足
} else {
// 对参数SQL参数进行解析,具体查看 method.convertArgsToSqlCommandParam(args) 标题
Object param = method.convertArgsToSqlCommandParam(args);
// command.getName(): 获取增删改查标签的id值
// param: 包含参数的 map 集合
// sqlSession.selectOne: 调用 selectOne 方法,具体内容请查看 sqlSession.selectOne
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;
}
}
method.convertArgsToSqlCommandParam(args)
- 对 UserMapper.java 传进来的参数进行解析
public class ParamNameResolver {
// 定义参数名前缀
public static final String GENERIC_NAME_PREFIX = "param";
private final boolean useActualParamName;
private final SortedMap<Integer, String> names;
private boolean hasParamAnnotation;
// 参数名称解析器
public ParamNameResolver(Configuration config, Method method) {
// 获取参数使用的实际名称
this.useActualParamName = config.isUseActualParamName();
// 获取参数类型
final Class<?>[] paramTypes = method.getParameterTypes();
// 获取使用 @Parame 注解的参数,这是一个二维数组,第一个数组装的是注解的索引值,第二个装的是索引值对应的参数相关信息
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
// 创建一个 map ,用来存储后续解析完的参数
final SortedMap<Integer, String> map = new TreeMap<>();
// 获取数组长度
int paramCount = paramAnnotations.length;
// get names from @Param annotations (从@Param注释中获取名称)
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
// 变量二维数组
for (Annotation annotation : paramAnnotations[paramIndex]) {
// 如果注解中包含 Param
if (annotation instanceof Param) {
// 设置为ture
hasParamAnnotation = true;
// 将注解的值赋值给name, org.apache.ibatis.annotations.Param("id")
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (useActualParamName) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
// 将注解的索引和注解的值存入map
map.put(paramIndex, name);
}
// 存入成员变量中,方便其他方法使用
names = Collections.unmodifiableSortedMap(map);
}
/**
* <p>
* A single non-special parameter is returned without a name. Multiple parameters are named using the naming rule. In
* addition to the default names, this method also adds the generic names (param1, param2, ...).
* </p>
* 返回一个没有名称的非特殊参数。多个参数使用命名规则命名。除了默认名称之外,此方法还添加通用名称(param1、param2、...)
*/
// 参数解析 对照源码上的注释,加上类中定义的参数前缀,在使用Mybatis中可以按照参数的前后顺序也可以用param1、param2、..进行取值
public Object getNamedParams(Object[] args) {
// 获取参数数量
final int paramCount = names.size();
// 非空判断
if (args == null || paramCount == 0) {
return null;
}
// 如果参数是单个,直接返回
if (!hasParamAnnotation && paramCount == 1) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : null);
// 如果参数是多个,封装成一个map
} else {
// 定义参数封装集合
final Map<String, Object> param = new ParamMap<>();
int i = 0;
// 遍历参数集合,至于这个 names.entrySet() 是哪里来的数据,请debug此类中的 ParamNameResolver 方法
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
// 添加生成的参数名称.如 param1, param2, ...
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param (确保不覆盖以@Param命名的参数)
// 如果 param 集合中不包含 genericParamName 则添加到 param集合中
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
// 返回包含参数的 map 集合
return param;
}
}
}
sqlSession.selectOne
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
public DefaultSqlSession(Configuration configuration, Executor executor) {
this(configuration, executor, false);
}
@Override
public <T> T selectOne(String statement) {
return this.selectOne(statement, null);
}
// 调用 selectOne 方法后续
// statement: 增删改查标签的id值
// parameter: 解析完后的参数集合
@Override
public <T> T selectOne(String statement, Object parameter) {
// 其实在Mybatis中执行的selectOne访问,本质上还是调用了selectList方法,只不过在返回值上进行了处理\
// 继续往下查看 selectList 方法内部逻辑
List<T> list = this.selectList(statement, parameter);
// 如果结果是单个,就取第一个,笔者看到这行代码时愣了一下,这才回想起索引是从0开始的,size并不是~
if (list.size() == 1) {
return list.get(0);
}
// 如果结果是多个,直接抛出异常,经典报错!相信大家都不会陌生
if (list.size() > 1) {
throw new TooManyResultsException(
"Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
// selectList 方法
@Override
public <E> List<E> selectList(String statement) {
// 继续向下调用
return this.selectList(statement, null);
}
// 继续向下调用
@Override
public <E> List<E> selectList(String statement, Object parameter) {
// 继续向下调用
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
// 继续向下调用
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
// 继续向下调用
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
// selectList 处理过程
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 在标题 SqlSessionFactoryBuilder 的下文有提到过,
// MappedStatement是configuration中的一个集合,此集合存储着所有的增删改查标签的内容,
// 以属性ID的值为key,增删改查标签的所有内容为value
// 通过属性ID的key获取对应的语句标签内容
// 在getMappedStatement 方法中还有一个比较有意思的方法(buildAllStatements),看方法名是构建所有的Statement,
// 但是笔者在debug的时候没有数据,无法进入判断语句,待后续在了解
MappedStatement ms = configuration.getMappedStatement(statement);
// 判断是否是脏读,数据库的四种隔离级别,读未提交(脏读),读已提交,.可重复读和串行化
dirty |= ms.isDirtySelect();
// wrapCollection(parameter) 标题查看,重点
// executor.query 标题查看
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
wrapCollection(parameter)
- 在上面的 method.convertArgsToSqlCommandParam(args) 标题中是对多个参数的解析
- 而在 wrapCollection(parameter) 中是对集合类型的数据进行解析
public class ParamNameResolver {
public static final String GENERIC_NAME_PREFIX = "param";
private final boolean useActualParamName;
/**
* Wrap to a {@link ParamMap} if object is {@link Collection} or array.
*
* @param object
* a parameter object
* @param actualParamName
* an actual parameter name (If specify a name, set an object to {@link ParamMap} with specified name)
*
* @return a {@link ParamMap}
*
* @since 3.5.5
*/
// 对集合类型的参数进行解析
public static Object wrapToMapIfCollection(Object object, String actualParamName) {
// 如果是集合
if (object instanceof Collection) {
// 创建map集合,存储value
ParamMap<Object> map = new ParamMap<>();
// 以字符串"collection" 为key和value为Mapper.xml中的入参存储到map
map.put("collection", object);
// 如果List
if (object instanceof List) {
// 以字符串"list" 为key和value为Mapper.xml中的入参存储到map
map.put("list", object);
}
// 如果有使用 @param 注解给定参数名称,则添加到map中
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
// 返回该map
return map;
}
// 如果参数 != nll 并且是数组
if (object != null && object.getClass().isArray()) {
ParamMap<Object> map = new ParamMap<>();
// 以字符串"array" 为key和value为Mapper.xml中的入参存储到map
map.put("array", object);
// 如果有使用 @param 注解给定参数名称,则添加到map中
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
// 返回该map
return map;
}
return object;
}
}
executor.query
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler)
throws SQLException {
// 获取 BoundSql 代表SQL语句的详细信息:SQL语句,参数,以及参数在SQL语句中的映射关系
// setParameters: 在 PreparedStatement 类中的 setParameters 方法中完成参数对#{}的替换
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建缓存的key: 当前的hashcode,当前的检查码,增删改查的方法名,参数的hashcode,执行的SQL语句,传入的参数值和当前环境信息
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 调用下面的 query 方法(在前文有说过,CachingExecutor 的构造方法传入 Executor 对象,
// delegate.setExecutorWrapper(this)跟踪下去发现在 BaseExecutor 中 声明了 Executor 类就是 this)
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// query 方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
// 获取二级缓存
Cache cache = ms.getCache();
// 如果缓存 != null : 本次 debug 没有开启二级缓存
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) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 调用 BaseExecutor 类中的 query 方法 请查看标题: BaseExecutor.query
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
BaseExecutor.query
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
//
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
// ms.getResource(): UserMapper.xml的相对路径
// ms.getId(): insert | select | update | delete 标签中的ID属性
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 判断是否清除本地缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 如果 resultHandler == null 则用缓存的key去查缓存,否则 = null
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;
}
/**
* Apply a transaction timeout.
*
* @param statement
* a current statement
*
* @throws SQLException
* if a database access error occurs, this method is called on a closed <code>Statement</code>
*
* @since 3.4.0
*
* @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
*/
protected void applyTransactionTimeout(Statement statement) throws SQLException {
StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
}
// 处理一级缓存的输出参数
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter,
BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
final Object cachedParameter = localOutputParameterCache.getObject(key);
if (cachedParameter != null && parameter != null) {
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}
// 从数据库去查询
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 将key添加到缓存中. EXECUTION_PLACEHOLDER: 占位符,没有什么实际意义
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 前文有说到,使用Executor的类型是 SIMPLE ,so doQuery 会被 SimpleExecutor 调用,查看下面标题 SimpleExecutor.doQuery
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 删除key
localCache.removeObject(key);
}
// 将查询结果存入缓存
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
}
SimpleExecutor.doQuery
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
// 从数据库查询数据
// ms: 当前查询标签的详细信息
// parameter: sql参数
// rowBounds: Mybatis封装的逻辑分页,不好用而没什么人使用
// resultHandler: 暂时没有使用到,看命名就知道是处理返回结果的
// boundSql: SQL语句的详细信息:SQL语句,参数,以及参数在SQL语句中的映射关系
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
// 声明 Statement 对象,此对象也是原生JDBC的 Statement
Statement stmt = null;
try {
// 获取"mybatis-config.xml"配置信息
Configuration configuration = ms.getConfiguration();
// 重点 configuration.newStatementHandler构建 StatementHandler 对象,具体逻辑请查看 newStatementHandler 标题
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,
boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询语句
11
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
// 声明 Statement 对象
Statement stmt;
// 获取数据库链接
Connection connection = getConnection(statementLog);
// 解析 connection 类, 查看下面标题 prepare
stmt = handler.prepare(connection, transaction.getTimeout());
// 重点:参数预编译,查看下面标题 parameterize
handler.parameterize(stmt);
return stmt;
}
}
newStatementHandler
public class Configuration {
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 构建 StatementHandler 对象,实际返回的是 PreparedStatementHandler对象,具体逻辑查看下面标题 RoutingStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
rowBounds, resultHandler, boundSql);
// 将 statementHandler 添加到拦截链,使用所有拦截器包装 statementHandler 类,在Executor中有同样的操作
return (StatementHandler) interceptorChain.pluginAll(statementHandler);
}
}
RoutingStatementHandler
- 用于构建原生 JDBC 中的 Statement 对象
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
/*
- statementType 有三个属性,在默认值是 **PREPARED**,在java.sql包下,这三个属性都是接口形式。
- STATEMENT
- 在 Java 中 Statement 继承自 Wrapper ,提供了执行语句和获取结果的基本方法。
- 普通的不带参的查询SQL,支持批量更新,批量删除。
- Statement 每次执行sql语句,数据库都要执行sql语句的编译 。
- 最好用于仅执行一次查询并返回结果的情形,效率高于 PreparedStatement。
- PREPARED
- 在 Java 中 PreparedStatement 继承自 Statement,添加了处理 IN 参数的方法。
- 可变参数的SQL,编译一次,执行多次,效率高。
- 安全性好,有效防止Sql注入等问题。
- 支持批量更新,批量删除。
- PreparedStatement 是预编译的,使用 PreparedStatement 有几个好处:
- 在执行可变参数的一条SQL时,PreparedStatement 比 Statement 的效率高,因为DBMS预编译一条SQL当然会比多次编译一条SQL的效率要高。
- 安全性好,有效防止Sql注入等问题。
- 对于多次重复执行的语句,使用 PreparedStatement 效率会更高一点,并且在这种情况下也比较适合使用batch;
- 代码的可读性和可维护性。
- CALLABLE
- 在 Java 中 CallableStatement 继承自 PreparedStatement ,添加了处理 OUT 参数的方法。
- 继承自 PreparedStatement ,支持带参数的 SQL 操作;
- 支持调用存储过程,提供了对输出和输入/输出参数(INOUT)的支持;
*/
// 判断 select 标签中的 StatementType 属性
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
// 返回一个PreparedStatementHandler
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());
}
}
}
prepare
- 参数预编译前的准备工作中
- 在调用 stmt = handler.prepare(connection, transaction.getTimeout()) 方法时,handler对象是 RoutingStatementHandler,而RoutingStatementHandler 类的中定义了 private final StatementHandler delegate
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
// 构造方法
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
// 此处调用编译前的准备 -> stmt = handler.prepare(connection, transaction.getTimeout())
// 这里 delegate 类型则是 StatementHandler
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
// 同上,都是调用了 StatementHandler 的方法进行处理
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
- StatementHandler 是一个接口,它的另一个实现则是 BaseStatementHandler,该实现类中有对 prepare 方法的具体实现
BaseStatementHandler implements StatementHandler {
protected final Configuration configuration;
protected final ObjectFactory objectFactory;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final ResultSetHandler resultSetHandler;
protected final ParameterHandler parameterHandler;
protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final RowBounds rowBounds;
protected BoundSql boundSql;
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// 以下两个对象,创建的过程同等于 newStatementHandler 标题中的内容
// 构建 parameterHandler 对象,parameterHandler实现类中DefaultParameterHandler的setParameters方法是用于 参数 于 #{} 的替换,
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// 构建 resultSetHandler 对象,用于处理返回结果
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler,
resultHandler, boundSql);
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 使用 connection 初始化 Statement 类
statement = instantiateStatement(connection);
// 设置语句超时实际
setStatementTimeout(statement, transactionTimeout);
// FetchSize: 在解析内有设置此属性的值,标明一次性获取数据的条数,查看下面标题 parameterize
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
parameterize
- 使用 ParameterHandler 处理参数
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
@Override
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 typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
handler.query
-
query方法调用 RoutingStatementHandler 的 query 方法,和上述一样,而 delegate 的类型是 StatementHandler ,StatementHandler 是一个接口,具体实现在 PreparedStatementHandler 类中(光标选中 StatementHandler 。 ctrl + H 查看类关系图)
- RoutingStatementHandler
public class RoutingStatementHandler implements StatementHandler { private final StatementHandler delegate; @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { return delegate.query(statement, resultHandler); }
- 具体实现 query 方法
public class PreparedStatementHandler extends BaseStatementHandler {
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 查询结果处理 查看 handleResultSets(ps) 标题
return resultSetHandler.handleResultSets(ps);
}
}
handleResultSets(ps)
public class DefaultResultSetHandler implements ResultSetHandler {
@Override
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;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
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 ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
ResultSet rs = stmt.getResultSet();
while (rs == null) {
// move forward to get the first resultset in case the driver
// doesn't return the resultset as the first result (HSQLDB 2.1)
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else if (stmt.getUpdateCount() == -1) {
// no more results. Must be no resultset
break;
}
}
return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}
private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) {
if (rsw != null && resultMapCount < 1) {
throw new ExecutorException(
"A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId()
+ "'. 'resultType' or 'resultMap' must be specified when there is no corresponding method.");
}
}
private List<Object> collapseSingleResultList(List<Object> multipleResults) {
return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
}
}
尾声
- 学习源码可以帮你更好的使用该框架,最重要的是可以学习里面的编程思想(编程思想这个说法可能会有点抽象,说白了就是编程方式)
文字版总结
-
学习源码可以帮你更好的使用该框架,最重要的是可以学习里面的编程思想(编程思想这个说法可能会有点抽象,说白了就是编程方式)
-
构建
XMLConfigBuilder
对象,在构造方法中调用parser
方法解析mybatis-config.xml
文件- 解析
configuration
标签,获取所有配件信息,如果没有配置则设置其默认值(在SpringBoot框架,MybatisProperties类用于所有配置属性) - 解析
mybatis-config.xml
文件的mappers标签,获取标签下所有的Mapper.xml
配置路径,遍历配置路径,通过路径获取Mapper.xml
文件,获取Mapper.xml
文件下的所有标签(真的是解析了所有标签,但是笔者下面就记录两个重要的标签) - 解析
namespace
的值,添加到Configuration
类中的MapperRegistry
对象中,换而言之MapperRegistry
对象存储着项目所有的namespace
- 解析
select|insert|update|delete
标签时,每一个MappedStatement
对象就是一个select|insert|update|delete
标签(如果你的Mapper.xml
文件中只有三select
标签,Mybatis就会生成三个MappedStatemen
对象
- 解析
-
其实
在SqlSession
中包含configuration
对象,而configuration
对象中包含所有的配置信息 -
构建
SqlSession
对象- 通过
configuration.getEnvironment()
方法获取构建TransactionFactory
对象 - 通过数据库配置信息,事物隔离级别,是否自动提交构建
Transaction
类 - 通过
Transaction
类和枚举对象ExecutorType
(默认SIMPLE) 构建Executor
类 - 使用
configuration
&&executor
&&autoCommit
构建DefaultSqlSession
类并且返回DefaultSqlSession
(DefaultSqlSession
实现SqlSession
对象)
- 通过
-
获取
Mapper.xml
代理类对象- 调用
DefaultSqlSession
类中的getMapper
方法 - 在
DefaultSqlSession
类中,getMapper
方法的实现是configuration.getMapper
,在configuration.getMapper
中又调用``mapperRegistry.getMapper(在前面有写,
MapperRegistry对象存储着项目所有
namespace`的值) - 使用
sqlSession
、mapperInterface
、methodCache
构建MapperProxy
类
- 调用
-
执行
Mapper.xml
中的方法-
判读方法的
select|insert|update|delete
标签 -
判断方法是否有返回值
-
获取使用
@Parame
注解的参数,如有参数则设置key
,将SQL
参数为value
并存入map
集合 -
获取
BoundSql
:代表SQL
语句的详细信息:SQ
L语句、参数、以及参数在SQL
语句中的映射关系 -
创建缓存的
key
: 当前的hashcode
、当前的检查码、增删改查的方法名、参数的hashcode
、执行的SQL语句,传入的参数值和当前环境信息 -
获取二级缓存对象,在这里不多叙述,本次
debug
没有开启二级缓存 -
用缓存Key查询一级缓存,如果有则处理缓存中的数据作为返回结果,本次
debug
没有则再去从数据库中查询 -
使用当前查询标签的详细信息、
SQL
参数、Mybatis
封装的逻辑分页、resultHandlerboundSql
、boundSql
、构建StatementHandler
对象- 创建数据库连接对象
Connection
- 解析
Connection
,使用Connection
初始化Statement
类,设置语句超时实际。- FetchSize::标明一次性获取数据的条数
- 创建数据库连接对象
-
使用
TypeHandler
处理预编译参数,大胆猜测是SQL
参数#{}
的替换,笔者对这段源码没有debug -
使用
ResultSetHandler.handleResultSets
方法对查询结果的处理
-
面试版
- 解析
mybatis-config.xml
和Mapper.xml
文件。获取相关信息存入MapperRegistry
和MappedStatement
,创建configuration
对象 - 构建
SqlSession
中通过configuration
对象创建TransactionFactory
、Transaction
、Executor
、使用configuration
&&executor
&&autoCommit
构建DefaultSqlSession
类并且返回 - 使用
sqlSession
、mapperInterface
、methodCache
构建MapperProxy
类 - 执行方法时对方法标签、返回值的判断,创建
BoundSql
、创建缓存的key
,先查二级缓存如果二级缓存没有,再查一级如果一级缓存没有,再查数据库,使用TypeHandler
处理预编译参数,再用ResultSetHandler.handleResultSets
对查询结果的处理
疑惑
- 在 configuration 类中有一个 buildAllStatements 方法,此方法在笔者debug时,由于没有参数,进不去判断,看方法名和注释,感觉这个方法对理解源码肯定有帮助。
- 在 CachingExecutor 的 query 方法中,由于没有开启二级缓存,查看不到对缓存的处理