三天撸完了MyBatis,各位随便问!!(冰河吐血整理,建议收藏)

三天撸完了MyBatis,各位随便问!!(冰河吐血整理,建议收藏)很多大厂在面试的时候喜欢问MyBatis底层的原理和源码实现。总之,MyBatis几乎成为了Java开发人员必须深入掌握的框架技术,冰河吐血整理,建议收藏!!

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

大家好,我是冰河~~

随着互联网的发展,越来越多的公司摒弃了Hibernate,而选择拥抱了MyBatis。而且,很多大厂在面试的时候喜欢问MyBatis底层的原理和源码实现。总之,MyBatis几乎成为了Java开发人员必须深入掌握的框架技术,今天,我们就一起来深入分析MyBatis源码。文章有点长,建议先收藏后慢慢研究。整体三万字左右,全程高能,小伙伴们可先收藏后慢慢研究。

小伙伴们如果觉得文章不错,点赞、收藏、评论,分享走一起呀,记得给冰河来个一键三连~~

好了,我们开始今天的正文。

三天撸完了MyBatis,各位随便问!!(冰河吐血整理,建议收藏)

MyBatis源码解析

大家应该都知道Mybatis源码也是对Jbdc的再一次封装,不管怎么进行包装,还是会有获取链接、preparedStatement、封装参数、执行这些步骤的。

配置解析过程

String resource = "mybatis-config.xml";
//1.读取resources下面的mybatis-config.xml文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//2.使用SqlSessionFactoryBuilder创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3.通过sqlSessionFactory创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();

Resources.getResourceAsStream(resource)读取文件

public static InputStream getResourceAsStream(String resource) throws IOException { 
   
	return getResourceAsStream(null, resource);
} 
//loader赋值为null
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException { 
   
	InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
	if (in == null) { 
   
		throw new IOException("Could not find resource " + resource);
	} 
	return in;
}
//classLoader为null
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) { 
   
	return getResourceAsStream(resource, getClassLoaders(classLoader));
} 
//classLoader类加载
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) { 
   
	for (ClassLoader cl : classLoader) { 
   
		if (null != cl) { 
   
			//加载指定路径文件流
			InputStream returnValue = cl.getResourceAsStream(resource);
			// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
			if (null == returnValue) { 
   
				returnValue = cl.getResourceAsStream("/" + resource);
			} 
			if (null != returnValue) { 
   
				return returnValue;
			}
		}
	} 
	return null;
}

总结:主要是通过ClassLoader.getResourceAsStream()方法获取指定的classpath路径下的Resource 。

通过SqlSessionFactoryBuilder创建SqlSessionFactory

//SqlSessionFactoryBuilder是一个建造者模式
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
public SqlSessionFactory build(InputStream inputStream) { 

return build(inputStream, null, null);
}
//XMLConfigBuilder也是建造者模式
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { 

try { 

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) { 

throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally { 

ErrorContext.instance().reset();
try { 

inputStream.close();
} catch (IOException e) { 

// Intentionally ignore. Prefer previous error.
}
}
}
//接下来进入XMLConfigBuilder构造函数
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { 

this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
//接下来进入this后,初始化Configuration
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { 

super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
//其中parser.parse()负责解析xml,build(configuration)创建SqlSessionFactory
return build(parser.parse());

parser.parse()解析xml

public Configuration parse() { 

//判断是否重复解析
if (parsed) { 

throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} 
parsed = true;
//读取配置文件一级节点configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) { 

try { 

//properties 标签,用来配置参数信息,比如最常见的数据库连接信息
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//实体别名两种方式:1.指定单个实体;2.指定包
typeAliasesElement(root.evalNode("typeAliases"));
//插件
pluginElement(root.evalNode("plugins"));
//用来创建对象(数据库数据映射成java对象时)
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//数据库环境
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//数据库类型和Java数据类型的转换
typeHandlerElement(root.evalNode("typeHandlers"));
//这个是对数据库增删改查的解析
mapperElement(root.evalNode("mappers"));
} catch (Exception e) { 

throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

总结:parseConfiguration完成的是解析configuration下的标签

private void mapperElement(XNode parent) throws Exception { 

if (parent != null) { 

for (XNode child : parent.getChildren()) { 

//解析<package name=""/>
if ("package".equals(child.getName())) { 

String mapperPackage = child.getStringAttribute("name");
//包路径存到mapperRegistry中
configuration.addMappers(mapperPackage);
} else { 

//解析<mapper url="" class="" resource=""></mapper>
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);
//读取Mapper.xml文件
InputStream inputStream = Resources.getResourceAsStream(resource);
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 mapperParser = new XMLMapperBuilder(inputStream,
configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} 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.");
}
}
}
}
}

总结: 通过解析configuration.xml文件,获取其中的Environment、Setting,重要的是将下的所有解析出来之后添加到
Configuration,Configuration类似于配置中心,所有的配置信息都在这里。

mapperParser.parse()对 Mapper 映射器的解析

public void parse() { 

if (!configuration.isResourceLoaded(resource)) { 

//解析所有的子标签
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//把namespace(接口类型)和工厂类绑定起来
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
} 
//这里面解析的是Mapper.xml的标签
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"));
//获得MappedStatement对象(增删改查标签)
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);
}
}
//获得MappedStatement对象(增删改查标签)
private void buildStatementFromContext(List<XNode> list) { 

if (configuration.getDatabaseId() != null) { 

buildStatementFromContext(list, configuration.getDatabaseId());
} 
buildStatementFromContext(list, null);
}
//获得MappedStatement对象(增删改查标签)
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { 

//循环增删改查标签
for (XNode context : list) { 

final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try { 

//解析insert/update/select/del中的标签
statementParser.parseStatementNode();
} catch (IncompleteElementException e) { 

configuration.addIncompleteStatement(statementParser);
}
}
}
public void parseStatementNode() { 

//在命名空间中唯一的标识符,可以被用来引用这条语句
String id = context.getStringAttribute("id");
//数据库厂商标识
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { 

return;
} 
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType =
SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//flushCache和useCache都和二级缓存有关
//将其设置为true后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true
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);
//外部resultMap的命名引用
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);
}
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) { 

if (unresolvedCacheRef) { 

throw new IncompleteElementException("Cache-ref not yet resolved");
} 
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
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(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap,
parameterType, id);
if (statementParameterMap != null) { 

statementBuilder.parameterMap(statementParameterMap);
} 
MappedStatement statement = statementBuilder.build();
//持有在configuration中
configuration.addMappedStatement(statement);
return statement;
}
public void addMappedStatement(MappedStatement ms){ 

//ms.getId = mapper.UserMapper.getUserById
//ms = MappedStatement等于每一个增删改查的标签的里的数据
mappedStatements.put(ms.getId(), ms);
}
//最终存放到mappedStatements中,mappedStatements存放的是一个个的增删改查
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection").conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());

解析bindMapperForNamespace()方法

把 namespace(接口类型)和工厂类绑定起来

private void bindMapperForNamespace() { 

//当前Mapper的命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) { 

Class<?> boundType = null;
try { 

//interface mapper.UserMapper这种
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) { 

} 
if (boundType != null) { 

if (!configuration.hasMapper(boundType)) { 

configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
public <T> void addMapper(Class<T> type) { 

mapperRegistry.addMapper(type);
} 
public <T> void addMapper(Class<T> type) { 

if (type.isInterface()) { 

if (hasMapper(type)) { 

throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
} 
boolean loadCompleted = false;
try { 

//接口类型(key)->工厂类
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally { 

if (!loadCompleted) { 

knownMappers.remove(type);
}
}
}
}

生成SqlSessionFactory对象

XMLMapperBuilder.parse()方法,是对 Mapper 映射器的解析里面有两个方法:

(1)configurationElement()解析所有的子标签,最终解析Mapper.xml中的insert/update/delete/select标签的id(全路径)组成key和整个标签和数据连接组成MappedStatement存放到Configuration中的 mappedStatements这个map里面。

(2)bindMapperForNamespace()是把接口类型(interface mapper.UserMapper)和工厂类存到放MapperRegistry中的knownMappers里面。

SqlSessionFactory的创建

public SqlSessionFactory build(Configuration config) { 

return new DefaultSqlSessionFactory(config);
}

直接把Configuration当做参数,直接new一个DefaultSqlSessionFactory。

SqlSession会话的创建过程

mybatis操作的时候跟数据库的每一次连接,都需要创建一个会话,我们用openSession()方法来创建。这个会话里面需要包含一个Executor用来执行 SQL。Executor又要指定事务类型和执行器的类型。

创建Transaction(两种方式)

属性 产生工厂类 产生事务
JDBC JbdcTransactionFactory JdbcTransaction
MANAGED ManagedTransactionFactory ManagedTransaction
  • 如果配置的是 JDBC,则会使用Connection 对象的 commit()、rollback()、close()管理事务。
  • 如果配置成MANAGED,会把事务交给容器来管理,比如 JBOSS,Weblogic。
SqlSession sqlSession = sqlSessionFactory.openSession();
public SqlSession openSession() { 

//configuration中有默认赋值protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

创建Executor

//ExecutorType是SIMPLE,一共有三种SIMPLE(SimpleExecutor)、REUSE(ReuseExecutor)、BATCH(BatchExecutor)
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { 

Transaction tx = null;
try { 

//xml中的development节点
final Environment environment = configuration.getEnvironment();
//type配置的是Jbdc所以生成的是JbdcTransactionFactory工厂类
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//Jdbc生成JbdcTransactionFactory生成JbdcTransaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建CachingExecutor执行器
final Executor executor = configuration.newExecutor(tx, execType);
//创建DefaultSqlSession属性包括 Configuration、Executor对象
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();
}
}

获得Mapper对象

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
public <T> T getMapper(Class<T> type) { 

return configuration.getMapper(type, this);
}

mapperRegistry.getMapper是从MapperRegistry的knownMappers里面取的,knownMappers里面存的是接口类型(interface mapper.UserMapper)和工厂类(MapperProxyFactory)。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 

return mapperRegistry.getMapper(type, sqlSession);
}

从knownMappers的Map里根据接口类型(interface mapper.UserMapper)取出对应的工厂类。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 

final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>)
knownMappers.get(type);
if (mapperProxyFactory == null) { 

throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} 
try { 

return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) { 

throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public T newInstance(SqlSession sqlSession) { 

final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

这里通过JDK动态代理返回代理对象MapperProxy(org.apache.ibatis.binding.MapperProxy@6b2ea799)

protected T newInstance(MapperProxy<T> mapperProxy) { 

//mapperInterface是interface mapper.UserMapper 
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new
Class[] { 
 mapperInterface }, mapperProxy);
}
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

执行SQL

User user = userMapper.getUserById(1);

调用invoke代理方法

由于所有的 Mapper 都是 MapperProxy 代理对象,所以任意的方法都是执行MapperProxy 的invoke()方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 

try { 

//判断是否需要去执行SQL还是直接执行方法
if (Object.class.equals(method.getDeclaringClass())) { 

return method.invoke(this, args);
//这里判断的是接口中的默认方法Default等
} else if (isDefaultMethod(method)) { 

return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) { 

throw ExceptionUtil.unwrapThrowable(t);
} 
//获取缓存,保存了方法签名和接口方法的关系
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

调用execute方法

这里使用的例子用的是查询所以走的是else分支语句。

public Object execute(SqlSession sqlSession, Object[] args) { 

Object result;
//根据命令类型走不行的操作command.getType()是select
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 { 

//将参数转换为SQL的参数
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;
}

调用selectOne其实是selectList

selectOne查询一个和查询多个其实是一样的。

public <T> T selectOne(String statement, Object parameter) { 

// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) { 

return list.get(0);
} else if (list.size() > 1) { 

throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else { 

return null;
}
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 

try { 

//从Configuration里的mappedStatements里根据key(id的全路径)获取MappedStatement 对象
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) { 

throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally { 

ErrorContext.instance().reset();
}
}

mappedStatements对象如图

在这里插入图片描述

MappedStatement对象如图

在这里插入图片描述

执行query方法

创建CacheKey

从 BoundSql 中获取SQL信息,创建 CacheKey。这个CacheKey就是缓存的Key。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { 

//创建缓存Key
BoundSql boundSql = ms.getBoundSql(parameterObject);
//key = -575461213:-771016147:mapper.UserMapper.getUserById:0:2147483647:select * from test_user where id = ?:1:development
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 

Cache cache = ms.getCache();
if (cache != null) { 

flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) { 

ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) { 

list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
} 
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

清空本地缓存

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.");
} 
//queryStack 用于记录查询栈,防止递归查询重复处理缓存
//flushCache=true 的时候,会先清理本地缓存(一级缓存)
if (queryStack == 0 && ms.isFlushCacheRequired()) { 

//清空本地缓存
clearLocalCache();
} 
List<E> list;
try { 

queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) { 

handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else { 

//如果没有缓存,会从数据库查询:queryFromDatabase()
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally { 

queryStack--;
} 
if (queryStack == 0) { 

for (DeferredLoad deferredLoad : deferredLoads) { 

deferredLoad.load();
} 
// issue #601
deferredLoads.clear();
//如果 LocalCacheScope == STATEMENT,会清理本地缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { 

// issue #482
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 { 

//执行Executor 的 doQuery(),默认是SimpleExecutor
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;
}

执行doQuery

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { 

Statement stmt = null;
try { 

Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally { 

closeStatement(stmt);
}
}

源码总结

总体上来说,MyBatis的源码还是比较简单的,只要大家踏下心来,花个两三天仔细研究下,基本上都能弄明白源码的主体脉络。

写在最后

如果你想进大厂,想升职加薪,或者对自己现有的工作比较迷茫,都可以私信我交流,希望我的一些经历能够帮助到大家~~

推荐阅读:

好了,今天就到这儿吧,小伙伴们点赞、收藏、评论,一键三连走起呀,我是冰河,我们下期见~~

三天撸完了MyBatis,各位随便问!!(冰河吐血整理,建议收藏)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/182085.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 关于 sso 博客大巴的神仙的一点思路

    关于 sso 博客大巴的神仙的一点思路
    聊天中的一些片段。。。可以记录参考下
     
    1每个应用独立维护自己的session
     
    2用户中心是一个特殊的应用,负责用户登录验证某个需要登录的应用发现没有登录状态就跳到用户中心用户中心判断有没在自己这里登录过,登录过就返回用户信息,没登陆过就先登录,然后记自己的session别的应用收到以后根据用户信息记自己的session之后只要自己的session还在,就不关用户中心的事了

  • html网页中加入音乐播放器,[HTML5]简单网页本地音乐播放器[通俗易懂]

    html网页中加入音乐播放器,[HTML5]简单网页本地音乐播放器[通俗易懂]既然HTML5提出与本地交互方便,就想写个HTML5的本地音乐播放器。一开始问题主要集中在怎么读取本地文件路径,我想肯定可以用JS实现去操作本地文件(因为node.js很容易实现读取本地文件,但是原生JS怎么写不太清楚),不过简单一点就用这样只能读取一个,我想做的是最好是把一个文件夹中的都取出来,然后参考http://sapphion.com/2011/11/html5-folder-upload…

  • datagrip永久激活(JetBrains全家桶)

    (datagrip永久激活)JetBrains旗下有多款编译器工具(如:IntelliJ、WebStorm、PyCharm等)在各编程领域几乎都占据了垄断地位。建立在开源IntelliJ平台之上,过去15年以来,JetBrains一直在不断发展和完善这个平台。这个平台可以针对您的开发工作流进行微调并且能够提供…

  • pycharm怎么关闭提示_嵌入自动补全

    pycharm怎么关闭提示_嵌入自动补全Pycharm是一款非常好用的Python的IDE。但就是因为它太好用了,所以对于Python新手来说,反而会导致脱离了代码提示功能,就完全不记得了函数名,大小写等,这对于新手来说是不太利于成长的。但是有一说一,Pycharm本身还是非常好用的,而且用习惯了,因此我一直想能够在学习的时候把Pycharm的代码提示功能关闭,工作的时候再打开,找了很久终于找到了T_T。当没有关闭代码提示的时候,…

  • 微信授权登录-微信公众号和PC端网站

    微信授权登录-微信公众号和PC端网站

    2021年10月25日
  • GG修改器修改钻石_gg是啥意思

    GG修改器修改钻石_gg是啥意思图就不上了,金钱、钻石和基因等修改,孤单亲测。工具:方法:一.打开GG修改器后选择六号特工二.内存范围:Ca1.钻石修改:进入角色—-选择草帽特工搜索:2500搜索类型:Q全选–修改–更多–增量填1切换下角色你会发现值变了返回搜索找到对应的值修改即可!改成-9999999992.金钱修改:进入关卡打死一个怪后搜索金钱的值搜索类型:Q直接修改即可!改成9999999993.基因修…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号