? ? ? ? Mybatis版本:3.5.10-SNAPSHOT。
????????MyBatis-Spring版本:2.1.0-SNAPSHOT。
1 简介
????????MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置,并将原始类型、接口和Java POJO映射到数据库的记录中。
1.1 MyBatis与Hibernate? ? ? ??
????????Hibernate几乎不需要写SQL,甚至不用懂SQL的语法。只需要定义好POJO到数据库表的映射关系,即可通过Hibernate提供的方法来完成持久层操作;而MyBatis是需要自己写SQL的,工作量稍大,但是可以对SQL进行调优,更灵活。通过Mybatis的代码生成器,可以自动帮我们生成Java实体类、mapper接口和XML文件,一定程度上可以简化开发。
????????在国内的环境中,MyBatis相比于Hibernate更流行些。而在国外,Hibernate用得多一些。在我看来,两者都有各自的适用场景。因为Hibernate我用得不多(只在当年毕设时有用过),所以就不妄加评论了。
1.2 MyBatis与MyBatis-Plus
????????MyBatis-Plus是在MyBatis的基础上进行二次开发的框架,除了保留MyBatis的原有功能之外,还提供了类似Hibernate那样直接通过Java代码即可操作数据库的能力。但是MyBatis-Plus使用的是自己定义的语法来操作数据库,所以需要增加学习成本。它更适合那些规模小、SQL简单(最好是单表的增删改查)、需要快速进行上线的项目使用(以下一段内容讲述了我对于MyBatis-Plus这个框架的一些看法。如果不感兴趣,请直接跳到第2小节进行查看)。
? ? ? ? 我在之前的公司中有用过MyBatis-Plus,但从我个人的角度而言,我并不是特别能看好MyBatis-Plus能达到MyBatis的地位(正如其官网所言:愿景是成为MyBatis最好的搭档),甚至说能取代MyBatis(取名为“Plus”的野心),它只会满足一部分人的需求。MyBatis-Plus留给我的印象是:如果是复杂的查询语句的话,它的语法将会写得很复杂,不利于排查问题和调优。所以这也就限制住了MyBatis-Plus只适合那些简单业务的场景,而MyBatis的代码生成器又从一定程度上蚕食了这个场景下的市场。
????????也许有人会说了,MyBatis-Plus和MyBatis是可以兼容的,简单的业务场景可以用MyBatis-Plus,复杂的业务场景还是用MyBatis。这里我想说的是:别看官网说的只做增强不做改变,在你没看过MyBatis-Plus的源码之前,你真的放心敢去用吗?是真的不做改变还是只是表面使用上的不做改变?这些都需要查看源码后才能得知,增加了使用上的成本(如果你不在乎框架成不成熟,出不出错,只要用得爽就行,那当我没说)。还有一点,如果哪天MyBatis的源码发生了大的架构上的变化,MyBatis-Plus何时才能进行跟进?像这样依托于其他框架之上发展出来的二开框架,注定会受到原有框架的限制。
? ? ? ? 在这里我不想过多地去评价MyBatis-Plus的语法封装,也许它现在并不完美,但是毕竟使用量还是很多的,所以要相信迭代的力量,这些都可以进行完善。但我在这里想要说的是:我不认同MyBatis-Plus这个框架存在的意义,确切的说是依托于MyBatis从而存在的意义。
? ? ? ? 现在国内的开发环境不知道从什么时候开始传出一种风气,不管开发出什么框架,都要往大而全的方向写,或者说是自己搞个生态,重复造轮子(可能是国内竞争压力大,导致越来越卷)。而MyBatis那种大道至简的源码设计风格,是非常能切中我的点的。它其实只干了一件事,就是专注于Java接口与SQL之间的映射,其他的功能也都是为了这个核心功能去服务的。所以MyBatis非常适合那些想学习源码的初学者,作为他们的第一个进行学习的源码框架。正如MyBatis源码中的pom文件中的描述所言:简单是MyBatis最大的优势。
<description>
The MyBatis SQL mapper framework makes it easier to use a relational database with object-oriented
applications. MyBatis couples objects with stored procedures or SQL statements using a XML descriptor or
annotations. Simplicity is the biggest advantage of the MyBatis data mapper over object relational mapping
tools.
</description>
????????在了解了MyBatis的设计理念之后再来看MyBatis-Plus,是否感觉MyBatis-Plus的设计有点儿违背初心、背道而驰了呢(也别老怪别人拿你和MyBatis比较,谁叫你取了这样一个名字呢)?对于源码层面上来说,改写或者增强了MyBatis原有的一些功能,在我看来并不是在做简化。MyBatis-Plus所谓的“简化”只是针对于使用者的角度来说的,而且这种简化也是有条件限制的(只适合简单业务)。同时MyBatis-Plus又内置了许多其他的功能,像自动分页、乐观锁、逻辑删除等等。看得出来,MyBatis-Plus的作者是想要去做一个大而全的框架的(这点本身没毛病)。诚然我知道MyBatis也有自定义插件的功能,但是如果将这些功能内置,并写进官网首页中作为框架亮点的话,就完全是另一种导向了。所以我认为MyBatis-Plus的设计理念和MyBatis的完全不同,尤其是在看过MyBatis的源码之后,更能体会到这一点。这个时候再叫“MyBatis-Plus”就有点不合适了(如果“Plus”代表的是更加臃肿,而不是更强,那我倒是能理解了)。
????????想用MyBatis的人都是为了能自己写SQL、自己掌握主动权的;不想用MyBatis的、图省心的人可以去用Spring Data JPA或者其他,两者之间都有着很清晰的用户群区分。而MyBatis-Plus相当于架在两者中间,明明是个全自动ORM框架(或者说是全/半自动?手自一体?),却是在半自动ORM框架上改写出来的,给人感觉不伦不类的。在我看来,如果MyBatis-Plus能不依托于MyBatis而独立开发并发展,它的前景应该会更好一些(但是这样也就蹭不到MyBatis的热度了,所以说成也MyBatis,败也MyBatis-_-)。
????????扯得有点远了,下面开始MyBatis源码的学习。
2 配置
? ? ? ? 以下的代码是以XML的方式为例来进行配置的,首先是构建SqlSessionFactory:
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
//将配置文件中的配置内容加载进Configuration对象中
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
????????下面就来分析一下它的build方法的实现:
/**
* SqlSessionFactoryBuilder:
*/
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//解析XML
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//走到这里,已经将配置文件中的内容解析成了Configuration对象
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.
}
}
}
/**
* XMLConfigBuilder:
* 第11行代码处:
*/
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
//使用DOM和SAX的方式来构建XPathParser对象,将XML文件的内容进行读取并放进Configuration中
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//Configuration的构造器中会默认注册一些别名映射,例如JDBC、POOLED等等,这样就可以直接使用别名
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
/**
* 第13行代码处:
*/
public Configuration parse() {
//已经解析过了就抛出异常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
//设置解析标志位
parsed = true;
//解析mybatis-config.xml的<configuration>节点下的内容
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
/**
* 第56行代码处:
*/
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
//解析properties节点内容并设置进Configuration对象中
propertiesElement(root.evalNode("properties"));
//解析settings节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
//加载VFS虚拟文件系统
loadCustomVfs(settings);
//指定日志实现
loadCustomLogImpl(settings);
/*
解析typeAliases别名节点
TypeAliasRegistry是Configuration对象中的属性,其构造器中会默认注册一些基本类型及对象类型的别名映射,这样我们就可以直接使用这些基本类型的别名了
*/
typeAliasesElement(root.evalNode("typeAliases"));
/*
添加插件
插件最终会存入到Configuration的InterceptorChain中,通过责任链的方式调用
*/
pluginElement(root.evalNode("plugins"));
//加载对象工厂,用于反射实例化对象(不常用)
objectFactoryElement(root.evalNode("objectFactory"));
//加载对象包装工厂(不常用)
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//加载反射工厂,用于属性和getter/setter获取(不常用)
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//设置一些settings和默认值到Configuration对象中
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//设置环境
environmentsElement(root.evalNode("environments"));
//设置数据库厂商id
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
/*
解析typeHandler类型处理器(javaType和jdbcType之间的转换)
跟解析别名一样,TypeHandlerRegistry也会加载一些默认的转换,也可以自定义
*/
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mapper接口
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
/**
* 第94行代码处:
*/
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
//获取transactionManager
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//获取dataSource
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);
//设置进Configuration对象中的Environment中
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}
/**
* 第103行代码处:
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
//获取mappers节点下的内容
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
/*
<1>通过package批量的方式
<package name="com.demo.mapper"/>
*/
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
/*
<2>通过resource、classpath下读取的方式
<mapper resource="org/apache/ibatis/demo/xml/UserMapper.xml"/>
*/
String resource = child.getStringAttribute("resource");
/*
<3>通过url、本地或网络资源的方式
<mapper url="D:/mapper/UserMapper.xml"/>
*/
String url = child.getStringAttribute("url");
/*
<4>通过class、mapper接口的方式
<mapper class="com.demo.mapper.UserMapper"/>
*/
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
//resource解析方式
ErrorContext.instance().resource(resource);
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//真正解析mapper的地方
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
//url解析方式
ErrorContext.instance().resource(url);
try (InputStream inputStream = Resources.getUrlAsStream(url)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
//真正解析mapper的地方
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
//class解析方式
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.");
}
}
}
}
}
/**
* XMLMapperBuilder:
* 第170行和第178行代码处:
*/
public void parse() {
//如果没有解析过的话
if (!configuration.isResourceLoaded(resource)) {
//解析mapper
configurationElement(parser.evalNode("/mapper"));
//已加载的内容放进Configuration的loadedResources中
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
/**
* 第200行代码处:
*/
private void configurationElement(XNode context) {
try {
//解析namespace属性
String namespace = context.getStringAttribute("namespace");
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"));
//解析增删改查节点
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);
}
}
/**
* XMLMapperBuilder:
* 第233行代码处:
*/
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
/**
* 第245行和第247代码处:
*/
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:
* 第257行代码处:
*/
public void parseStatementNode() {
//拿到增删改查节点的id属性
String id = context.getStringAttribute("id");
//拿到增删改查节点的数据库厂商id
String databaseId = context.getStringAttribute("databaseId");
//如果和当前数据源的数据库厂商id不匹配,则直接返回
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//获取节点名称(select|insert|update|delete)
String nodeName = context.getNode().getNodeName();
//通过节点名称获取相应的枚举
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//判断是否是查询语句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//获取flushCache属性(为空的话查sql不会刷新缓存,增删改sql会)
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//获取useCache属性(为空的话查sql会使用到缓存,增删改sql不会)
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
//是否需要分组
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
//解析include片段
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
//解析parameterType
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
//获取自定义SQL脚本语言驱动
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
//解析selectKey节点(自增id)
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;
}
/*
通过XMLLanguageDriver来解析SQL脚本,具体分为:
DynamicSqlSource:需要通过参数才能确定的SQL语句;
RawSqlSource:不需要通过参数就能确定的SQL语句。
*/
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//STATEMENT, PREPARED, CALLABLE,默认为PREPARED
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
//解析parameterMap属性,不写的话会通过TypeHandler类型处理器来映射
String parameterMap = context.getStringAttribute("parameterMap");
//解析resultType
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
//解析resultMap
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
//解析keyProperty,仅适用于增改语句
String keyProperty = context.getStringAttribute("keyProperty");
//解析keyColumn,仅适用于增改语句
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//将解析完的select|insert|update|delete语句添加进MappedStatement对象中,并最终添加进Configuration对象中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
/**
* XMLMapperBuilder:
* 第203行代码处:
*/
private void bindMapperForNamespace() {
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
configuration.addLoadedResource("namespace:" + namespace);
//这里重点看下addMapper方法
configuration.addMapper(boundType);
}
}
}
/**
* Configuration:
* 第376行代码处:
*/
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
/**
* MapperRegistry:
*/
public <T> void addMapper(Class<T> type) {
//判断mapper是否是接口
if (type.isInterface()) {
//如果已经添加进缓存了,就抛出异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
/*
创建一个MapperProxyFactory,放进knownMappers缓存中
等到后面调用sqlSession.getMapper方法时,会调用MapperProxyFactory的newInstance方法来JDK动态代理出来一个实现类(这里也就是使用到了代理模式)
*/
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//进行解析
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
/**
* MapperAnnotationBuilder:
* 第411行代码处:
*/
public void parse() {
String resource = type.toString();
//如果没有解析过、放进缓存中的话
if (!configuration.isResourceLoaded(resource)) {
//根据mapper接口名获取XML文件并解析(之前已经解析过了,这里会直接跳过)
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
//解析注解的方式(例如@Select注解,这里不进行分析)
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
/**
* SqlSessionFactoryBuilder:
* 第13行代码处:
*/
public SqlSessionFactory build(Configuration config) {
//将Configuration放进DefaultSqlSessionFactory中,后面就能够拿着Configuration中的配置执行了
return new DefaultSqlSessionFactory(config);
}
2.1 二级缓存
? ? ? ? 在上面第225行代码处的解析二级缓存的逻辑需要特别说明一下,这里使用到了装饰者模式。MyBatis的一级缓存指的是SqlSession级别的缓存,而二级缓存指的是应用级别的缓存。一级缓存后面会讲,这里先讲一下二级缓存。二级缓存有多种实现,他们通过相互装饰,最终变成一个大的缓存集合,放进Configuration的caches属性中,如下图所示:
????????而使用的时候是通过责任链的方式来分别调用每种缓存的能力,后面会看到。现在先来看下二级缓存的实现细节:?
/**
* XMLMapperBuilder:
*/
private void cacheElement(XNode context) {
if (context != null) {
//获取缓存的一些配置
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//重点看下这个方法
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
/**
* MapperBuilderAssistant:
* 第17行代码处:
*/
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
//二级缓存默认是PerpetualCache(HashMap实现)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
//这里使用到了装饰者模式,装饰了LruCache(LinkedHashMap实现,最近最少使用算法)
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
//最终将缓存存进Configuration对象中
configuration.addCache(cache);
currentCache = cache;
return cache;
}
/**
* CacheBuilder:
* 第42行代码处:
*/
public Cache build() {
//设置默认缓存实现类为PerpetualCache
setDefaultImplementations();
//PerpetualCache实例化
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
//获取上面第36行代码处的LruCache并实例化出来,这里有多个的话会一层一层包装
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
//设置其他的装饰器
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
/**
* 第55行代码处:
*/
private void setDefaultImplementations() {
if (implementation == null) {
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
//设置默认缓存实现类为PerpetualCache
decorators.add(LruCache.class);
}
}
}
/**
* 第63行代码处:
*/
private Cache newCacheDecoratorInstance(Class<? extends Cache> cacheClass, Cache base) {
//拿到Cache的构造器
Constructor<? extends Cache> cacheConstructor = getCacheDecoratorConstructor(cacheClass);
try {
//调用构造器进行包装
return cacheConstructor.newInstance(base);
} catch (Exception e) {
throw new CacheException("Could not instantiate cache decorator (" + cacheClass + "). Cause: " + e, e);
}
}
/**
* 第67行代码处:
*/
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
//装饰者模式,将PerpetualCache装饰为ScheduledCache(一小时会清空一次缓存)
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
//装饰者模式,将ScheduledCache装饰为SerializedCache(序列化和反序列化存储)
cache = new SerializedCache(cache);
}
//装饰者模式,将SerializedCache装饰为LoggingCache(打印缓存命中的日志信息)
cache = new LoggingCache(cache);
//装饰者模式,将LoggingCache装饰为SynchronizedCache(方法上加上synchronized关键字,防止并发)
cache = new SynchronizedCache(cache);
if (blocking) {
//装饰者模式,将SynchronizedCache装饰为BlockingCache(内部维护了一个同步锁,获取不到锁资源的时候会被阻塞,这样可以用来防止请求被穿透,避免将请求打到数据库上)
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
????????那么具体是如何进行装饰的呢?我们可以查看下LruCache的构造器实现:
public class LruCache implements Cache {
private final Cache delegate;
//...
public LruCache(Cache delegate) {
this.delegate = delegate;
//...
}
//...
}
????????可以看到,LruCache内部含有一个Cache类型的delegate属性,它就是需要被包装的Cache。每一个Cache内部都有这么一个delegate属性,所以通过不断调用构造器,就能将Cache链串联起来。
2.2 SqlNode
????????在上面第2小节的第327行代码处,是解析SQL脚本的地方。这里也有一些设计亮点可以单独拿出来进行讲解(这里不会涉及到具体的解析过程,具体的解析使用到了OGNL表达式,感兴趣的同学可以自行查看)。这里会将SQL脚本解析成一个个的SqlNode,使用到了组合设计模式。如下图所示:
????????
????????可以看到,一个SQL被解析成了一个SqlNode树,而在后面的执行SQL阶段就会调用每一个SqlNode的apply方法来进行拼接。下面来具体看下MyBatis是如何解析成一个个的SqlNode的:
/**
* XMLLanguageDriver:
*/
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
/**
* XMLScriptBuilder:
* 第6行代码处:
*/
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
//重点看下这个方法
initNodeHandlerMap();
}
/**
* 第19行代码处:
* 这里会初始化一些节点处理器,后面会看到使用的地方
*/
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
/**
* 第7行代码处:
*/
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;
}
/**
* 第43行代码处:
*/
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));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
//如果当前节点是一个文本节点的话
String data = child.getStringBody("");
//如果是文本节点的话,封装成TextSqlNode
TextSqlNode textSqlNode = new TextSqlNode(data);
//通过查看SQLNode中是否含有“${}”来判断是否是动态的SQL
if (textSqlNode.isDynamic()) {
//如果是动态SQL的话,最终会封装成TextSqlNode
contents.add(textSqlNode);
isDynamic = true;
} else {
//不是动态SQL,会被封装成StaticTextSqlNode
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
//如果当前节点是一个元素节点的话
String nodeName = child.getNode().getNodeName();
//获取节点处理器,节点处理器会在上面的第26行代码处的initNodeHandlerMap方法中被初始化
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
/*
分别调用不同节点处理器的处理方法来进行解析,最终返回对应的SqlNode
注意:这里会使用到递归的方式来进行解析,递归调用本方法去解析子节点
*/
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
3 操作
3.1 openSession方法
????????通过SqlSessionFactory的openSession方法可以获取到SqlSession:
SqlSession sqlSession = sqlSessionFactory.openSession();
? ? ? ? SqlSession不是真正做事的类,真正做事的是Executor。所以这里也可以说是用到了门面设计模式,SqlSession提供了统一的抽象入口,也就是门面。它内部会通过调用Executor,来完成实际的逻辑操作。下面首先来看一下SqlSession的创建:
/**
* DefaultSqlSessionFactory:
*/
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//获取环境变量
final Environment environment = configuration.getEnvironment();
//获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//创建事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建SQL执行器对象
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();
}
}
/**
* Configuration:
* 第19行代码处:
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//defaultExecutorType也是ExecutorType.SIMPLE,这里写了两行重复代码,猜测是代码遗留问题
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//根据执行器的类型来进行创建(SIMPLE, REUSE, BATCH)
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) {
executor = new CachingExecutor(executor);
}
/*
之前在分析配置中的XMLConfigBuilder#parseConfiguration方法中,pluginElement方法是用来添加插件的,该方法会往interceptorChain中添加一个Interceptor
而这里就是在配置这些插件
*/
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
3.1.1 插件
? ? ? ? MyBatis支持插件机制,我们可以自己实现插件,像分页、读写分离等功能都可以通过插件来实现。之前我们在第2小节的pluginElement方法中已经将插件插入到interceptorChain中了,而在上面的第3.1小节的第55行代码处,会执行pluginAll方法来配置插件。下面来看下其实现:
/**
* InterceptorChain:
*/
public Object pluginAll(Object target) {
/*
可以看到这里就是遍历调用每一个拦截器的plugin方法
plugin方法的默认实现是会生成一个代理类,所以如果有多个插件的话,这里执行的效果就是代理类又被代理了,这么一直嵌套代理下去
*/
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
/**
* Interceptor:
* 第10行代码处:
*/
default Object plugin(Object target) {
//默认实现,自定义的插件可以覆写
return Plugin.wrap(target, this);
}
/**
* Plugin:
*/
public static Object wrap(Object target, Interceptor interceptor) {
//获取@Intercepts中的@Signature中的type
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//如果当前代理的类和@Intercepts中的@Signature中的type类型可以配对上
if (interfaces.length > 0) {
//则可以进行动态代理,返回代理类。等到具体调用executor的方法时会调用到插件的invoke方法
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
/*
如果当前方法是插件注解上的方法的话,调用插件的intercept方法
intercept方法内部调用invocation.proceed()方法即可调用到目标方法
*/
return interceptor.intercept(new Invocation(target, method, args));
}
//否则,直接调用目标方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
? ? ? ? 对于上面第10行代码处的嵌套代理的解释,下面以Executor为例,进行演示。首先需要创建三个插件:
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyPlugin1 implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("执行方法前111。。。");
Object proceed = invocation.proceed();
System.out.println("执行方法后111。。。");
return proceed;
}
}
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyPlugin2 implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("执行方法前222。。。");
Object proceed = invocation.proceed();
System.out.println("执行方法后222。。。");
return proceed;
}
}
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MyPlugin3 implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("执行方法前333。。。");
Object proceed = invocation.proceed();
System.out.println("执行方法后333。。。");
return proceed;
}
}
? ? ? ? 可以看到,就是在执行目标方法的前后分别打印了日志。然后需要将插件进行配置:
<plugins>
<plugin interceptor="org.apache.ibatis.demo.plugin.MyPlugin2"/>
<plugin interceptor="org.apache.ibatis.demo.plugin.MyPlugin1"/>
<plugin interceptor="org.apache.ibatis.demo.plugin.MyPlugin3"/>
</plugins>
? ? ? ? 注意:这里的顺序是MyPlugin2->MyPlugin1->MyPlugin3,即封装的顺序。最后我们运行下程序,查看下执行结果:
执行方法前333。。。
执行方法前111。。。
执行方法前222。。。
执行方法后222。。。
执行方法后111。。。
执行方法后333。。。
[UserDO(id=1, name=name1, sex=true, phone=phone1, address=address1), UserDO(id=2, name=name2, sex=true, phone=phone2, address=address2), UserDO(id=3, name=name3, sex=true, phone=phone3, address=address3), UserDO(id=4, name=name4, sex=true, phone=phone4, address=address4), UserDO(id=5, name=name5, sex=true, phone=phone5, address=address5), UserDO(id=6, name=name6, sex=true, phone=phone6, address=address6), UserDO(id=7, name=name7, sex=true, phone=phone7, address=address7), UserDO(id=8, name=name8, sex=true, phone=phone8, address=address8), UserDO(id=9, name=name9, sex=true, phone=phone9, address=address9), UserDO(id=10, name=name10, sex=true, phone=phone10, address=address10), UserDO(id=11, name=name11, sex=true, phone=phone11, address=address11), UserDO(id=12, name=name12, sex=true, phone=phone12, address=address12), UserDO(id=13, name=name13, sex=true, phone=phone13, address=address13)]
? ? ? ? 其实看到这个执行的顺序就能联想到栈了。下面的图展示了具体的执行原理。
? ? ? ? 这里需要说明的一点是:实际目标方法的打印顺序和图中所画有些区别,这是因为我这里的目标方法是条select查询语句,而我手动打印查询结果是在程序的最后执行的,也就是在Executor执行之后打印的(SqlSession执行之后,还记得吗?SqlSession是Executor的门面),这个时候插件都已经运行完了,所以目标方法会放在最后才打印:
List<UserDO> list = session.selectList("org.apache.ibatis.demo.dao.UserDAO.list", UserDO.class);
System.out.println(list);
? ? ? ? 还有一点需要说明:上面的例子是针对同一type的多个插件的执行顺序的研究。而不同type的执行顺序是在代码里写死的,后面会看到具体写死的地方:
执行方法前Executor。。。
执行方法前StatementHandler。。。
执行方法后StatementHandler。。。
执行方法前ParameterHandler。。。
执行方法后ParameterHandler。。。
执行方法前ResultSetHandler。。。
执行方法后ResultSetHandler。。。
执行方法后Executor。。。
3.2?selectOne方法
????????数据操作以selectOne方法为例:
UserDO userDO = session.selectOne("org.apache.ibatis.demo.dao.UserDAO.getById", 1L);
????????查看其实现:
/**
* DefaultSqlSession:
*/
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
//可以看到selectOne方法是通过selectList方法来实现的,只不过是只截取第一条数据而已
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;
}
}
/**
* 第8行代码处:
*/
@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);
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
//从Configuration中获取mappedStatements,也就是之前第2.2小节解析的SqlNode
MappedStatement ms = configuration.getMappedStatement(statement);
//可以看到是通过执行器来执行的sql
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
/**
* 第36行代码处:
*/
private Object wrapCollection(final Object object) {
return ParamNameResolver.wrapToMapIfCollection(object, null);
}
/**
* ParamNameResolver:
* 如果参数是collection/list/array的话,就包装成map形式返回(这样就可以使用“array[0]”的方式来动态传参)
*/
public static Object wrapToMapIfCollection(Object object, String actualParamName) {
if (object instanceof Collection) {
ParamMap<Object> map = new ParamMap<>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
} else if (object != null && object.getClass().isArray()) {
ParamMap<Object> map = new ParamMap<>();
map.put("array", object);
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
}
return object;
}
/**
* CachingExecutor:
* 第36行代码处:
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//解析sql
BoundSql boundSql = ms.getBoundSql(parameterObject);
//生成缓存key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
/**
* MappedStatement:
* 第80行代码处:
*/
public BoundSql getBoundSql(Object parameterObject) {
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;
}
/**
* DynamicSqlSource:
* 第91行代码处:
*/
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
//之前在第2.2小节讲了sqlNode,而这里就是通过责任链和递归的方式来执行每一个sqlNode的apply方法,最终会拼接成需要执行的sql
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
//替换动态参数,将“#{}”替换成“?”,将参数封装为parameterMappings
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
/**
* CachingExecutor:
* 第83行代码处:
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//是否开启二级缓存
Cache cache = ms.getCache();
if (cache != null) {
//是否需要刷新缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
//之前的第2.1小节说过,二级缓存使用了装饰者模式,一个包装一个。所以这里就是在一个一个调用它们的getObject方法(装饰者+责任链)
@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);
}
/**
* TransactionalCacheManager:
* 第145行代码处:
*/
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
private TransactionalCache getTransactionalCache(Cache cache) {
/*
这里的作用是将Cache最外面再包一层TransactionalCache
TransactionalCache内部会暂存所有的二级缓存,其内部的putObject方法并不会直接去调用二级缓存的putObject方法,
而是等到调用commit方法时才会去调用二级缓存的putObject方法;当调用rollback方法时会调用二级缓存的removeObject方法
*/
return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
}
/**
* BaseExecutor:
* 第148行代码处:
*/
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//如果queryStack为0,并且需要删除一级缓存的话,就删除它
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
/*
从一级缓存中查找
这里的localCache会在commit或rollback方法中被清空,也就体现了一级缓存是SqlSession级别的缓存
*/
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;
}
/**
* 第204行代码处:
*/
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;
}
/**
* SimpleExecutor:
* 第231行代码处:
*/
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//拿到Connection和Statement
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
/**
* Configuration:
* 第252行代码处:
*/
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//StatementHandler的插件会在此处执行
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
/**
* RoutingStatementHandler:
* 第266行代码处:
*/
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:
//默认情况下会走这里,创建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());
}
/**
* PreparedStatementHandler:
* 第284行代码处:
*/
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
/**
* BaseStatementHandler:
*/
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;
//创建一个ParameterHandler(ParameterHandler的插件会在此处执行)
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
//创建一个ResultSetHandler(ResultSetHandler的插件会在此处执行)
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
/*
注:在上面小节的最后提到了不同type的插件的执行顺序,从这里也就能看到了。会按照Executor->StatementHandler->ParameterHandler->ResultSetHandler
的执行顺序执行(Executor的插件会在sqlSessionFactory.openSession方法中提前执行)
另外,通过全局搜索MyBatis的源码发现,插件的执行时机只在上面的四个对象中有介入。这也就意味着只能对上面的四个对象做插件上的增强,其它类型的插件即使写了,也不会生效
*/
}
/**
* SimpleExecutor:
* 第254行代码处:
*/
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取连接
Connection connection = getConnection(statementLog);
//获取statement
stmt = handler.prepare(connection, transaction.getTimeout());
//处理参数
handler.parameterize(stmt);
return stmt;
}
/**
* RoutingStatementHandler:
* 第255行代码处:
*/
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}
/**
* PreparedStatementHandler:
*/
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//这里就会调用底层的JDBC去查询数据库了
ps.execute();
//处理结果集(内部会使用到动态代理来创建目标对象,TypeHandler来赋值对象)
return resultSetHandler.handleResultSets(ps);
}
3.3 getMapper方法
????????通过SqlSession的getMapper方法可以直接获取到mapper接口,从而可以调用到相关业务方法。这种方式比直接调用SqlSession的相关方法要更好一些,避免了硬编码:
UserDAO mapper = session.getMapper(UserDAO.class);
UserDO userDO = mapper.getById(1L);
????????getMapper方法的实现就很简单了,下面来看下其实现:
/**
* DefaultSqlSession:
*/
@Override
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:
*/
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
/*
获取缓存中的值
还记得之前在第2小节中调用的addMapper方法吗?在其中会往knownMappers里添加MapperProxyFactory。
而这里就是从其中拿出mapper接口
*/
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);
}
}
/**
* MapperProxyFactory:
* 第32行代码处:
*/
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//从这里就可以看到,MyBatis就是通过JDK动态代理生成的Mapper实现类
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
? ? ? ? getMapper方法其实只做了一件事,就是动态代理出来mapper接口的实现类,类型为MapperProxy。之后就可以直接拿着代理类调用业务方法就行了。因为代理类是MapperProxy,所以调用业务方法时会调用到其invoke方法:
/**
* MapperProxy:
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
/**
* PlainMethodInvoker:
* 第10行代码处:
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
/**
* MapperMethod:
*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//下面就是在根据不同的SQL语句进行不同的处理
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:
//拿SELECT语句来举例,通过下面的逻辑可以看到,这里最终就是在调用sqlSession的相关方法进行查询
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;
}
? ? ? ? 上面的第61行代码处可以看到(SELECT语句的单条查询),业务方法的执行底层仍然是在调用sqlSession的相关方法。
4 与Spring集成
????????在我们使用MyBatis时,大部分情况下都不是单独使用的,都是需要和Spring进行集成后再使用。那么接下来就来看下MyBatis是如何与Spring进行集成的(注:MyBatis与Spring集成的代码是写在单独的mybatis-spring项目中的,不在原有的mybatis项目中)。
4.1 SqlSessionFactoryBean
????????之前在单独使用MyBatis的时候,我们是通过SqlSessionFactoryBuilder的build方法生成的SqlSessionFactory,而在和Spring集成后,是通过SqlSessionFactoryBean来生成的:
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setMapperLocations(new ClassPathResource("org/mybatis/spring/demo/xml/UserMapper.xml"));
return factoryBean.getObject();
}
? ? ? ? 可以看到,这里将数据源和mapper文件传给SqlSessionFactoryBean了。SqlSessionFactoryBean本身是个FactoryBean,熟悉Spring源码的同学应该知道,FactoryBean的getObject方法的返回值会被做成bean放进IoC容器中(我之前有写过对Spring源码的解析《较真儿学源码系列-Spring IoC核心流程源码分析》,感兴趣的话可以查看)。那么下面就来看下其getObject方法的实现:
/**
* SqlSessionFactoryBean:
*/
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
/**
* 第7行代码处:
*/
@Override
public void afterPropertiesSet() throws Exception {
//...
if (xmlConfigBuilder != null) {
try {
//之前第2小节已经分析过了,通过XMLConfigBuilder的parse方法来解析配置文件的内容,放到Configuration里
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
/*
这里会把SpringManagedTransactionFactory赋值给transactionFactory,所以最终用到的Transaction会是Spring事务管理器创建的Transaction;
最终用到的connection会是事务管理器创建好的连接
*/
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
//之前也说过,创建XMLMapperBuilder并调用其parse方法,会对mapper文件进行解析
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
//调用build方法,将Configuration放进SqlSessionFactoryBuilder中
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
????????至此就明白了SqlSessionFactoryBean的作用就是将MyBatis的配置信息放到Configuration对象中。
4.2 Spring如何去管理Mapper接口
4.2.1?MapperFactoryBean
?????????如果想把一个类交给Spring容器来管理的话,我们只需要在这个类上加上@Component或@Service或@Controller注解即可。但是在MyBatis中我们却不能在这样做,因为mapper是接口。如果是接口或者抽象类的话,是不能被Spring所实例化的:
/**
* ClassPathScanningCandidateComponentProvider:
*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
/**
* ClassMetadata:
* 第6行代码处:
*/
default boolean isConcrete() {
return !(isInterface() || isAbstract());
}
? ? ? ? 这个时候就需要MapperFactoryBean登场了,它会对mapper接口进行包装:
@Bean
public MapperFactoryBean userMapper() throws Exception {
MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(UserMapper.class);
mapperFactoryBean.setSqlSessionFactory(sqlSessionFactory());
return mapperFactoryBean;
}
????????同理,因为是个FactoryBean,所以查看其getObject方法实现:
/**
* MapperFactoryBean:
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* SqlSessionTemplate:
*/
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
????????可以看到,最终就是通过调用sqlSession的getMapper方法来创建出代理类,之前在第3.3小节已经讲过了。
4.2.2?并发控制
? ? ? ? 这里需要对SqlSessionTemplate单独做一些分析。我们之前对MyBatis源码进行分析的时候,如果细心的话可以发现DefaultSqlSession乃至整个MyBatis源码中都很少有加锁的控制。那么可以认为DefaultSqlSession是线程不安全的(这点其实没毛病,MyBatis的宗旨就是简单轻量化)。那么现在与Spring集成后,就不得不考虑这点了。SqlSessionTemplate是mybatis-spring项目单独封装的类,下面来看下它的构造器:
/**
* SqlSessionTemplate:
*/
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType,
new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
//这里是在动态代理,生成代理类。重点关注下最后一个参数,之后在调用sqlSessionProxy的方法时会调用到SqlSessionInterceptor的invoke方法
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class}, new SqlSessionInterceptor());
}
/**
* SqlSessionInterceptor:
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//重点看下是如何获取到sqlSession的
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
//目标方法的调用
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
//...
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
/**
* SqlSessionUtils:
* 第33行代码处:
*/
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
/*
获取同一个线程之前有没有创建过sqlSession
Spring事务相关的获取资源的方法,这里不详细去看了,底层就是在使用ThreadLocal
*/
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//通过sqlSessionHolder获取sqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
//如果之前存在sqlSession的话,就直接返回
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
//之前不存在sqlSession,说明当前线程是第一次获取,此时通过openSession方法获取到sqlSession
session = sessionFactory.openSession(executorType);
//并放进ThreadLocal中(如果事务同步激活的话)
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
? ? ? ? 可以看到,Mybatis集成Spring后,是通过ThreadLocal来保证的并发安全。这样的话,每个线程都有自己单独的sqlSession。
? ? ? ? 需要注意的一点是:如果事务没有开启的话,ThreadLocal中是不会放入值的。这样造成的结果就是每次请求(注意这里的措辞,不是同一个线程)都是new出来的新的sqlSession。这样虽然不会有并发问题,但是会使缓存失效。还记得MyBatis的一级缓存是sqlSession级别的吗?所以这也就是为什么没有开启事务,MyBatis一级缓存失效的原因。因为每次请求都会创建一个新的sqlSession,不管在不在同一个线程内。之前sqlSession的缓存都作废了。
? ? ? ? 至于为什么会这么做,为什么事务不开启的时候不往ThreadLocal里存值。猜测是为了Spring事务的概念。如果一个方法里面有多条查询SQL的语句的话,那么可以认为这几条查询语句是相互独立的,而如果在方法上加上@Transactional注解的话,则代表这里面的所有查询SQL是一个整体,所以需要共用同一个sqlSession、共用一级缓存(?我之前有写过对Spring事务的源码解析《较真儿学源码系列-Spring事务管理核心流程源码分析》,感兴趣的话可以查看)。
4.2.3?@MapperScan
? ? ? ? 前面讲解了通过MapperFactoryBean包装的方式来创建mapper接口的bean,但是我们不可能每次创建出一个mapper接口,就手动用MapperFactoryBean包装一下,再注入到Spring容器中。这个时候我们就会用到@MapperScan注解,以此来提供统一的注册mapper接口的方式。查看其实现:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
//...
}
? ? ? ? @MapperScan注解上会引用到MapperScannerRegistrar类,因其实现了ImportBeanDefinitionRegistrar接口,所以我们只需要看其registerBeanDefinitions方法的实现就行了:
/**
* MapperScannerRegistrar:
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//获取@MapperScan注解上的配置信息
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
/**
* 第10行代码处:
*/
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
//...
// for spring-native
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
//这里是将MapperScannerConfigurer注册为一个bean定义
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
? ? ? ? 可以看到,最后是注册了MapperScannerConfigurer。MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,那么接下来就来看下其postProcessBeanDefinitionRegistry接口的实现:
/**
* MapperScannerConfigurer:
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//创建扫描器,可以对mapper进行通用的扫描
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
//...
scanner.registerFilters();
//进行扫描
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
/**
* ClassPathMapperScanner:
* 第13行代码处:
*/
public void registerFilters() {
boolean acceptAllInterfaces = true;
//...
if (acceptAllInterfaces) {
// default include filter that accepts all classes
/*
这里的作用是添加一个includeFilter,使其可以跳过ClassPathScanningCandidateComponentProvider的isCandidateComponent(MetadataReader)方法
的校验,之后能跳到ClassPathMapperScanner改写的isCandidateComponent(AnnotatedBeanDefinition)方法里
*/
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}
/**
* ClassPathBeanDefinitionScanner:
* 第15行代码处:
*/
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
/**
* ClassPathMapperScanner:
* 第51行代码处:
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//调用原有的扫描逻辑(在其中会调用到isCandidateComponent方法,也就是会跳到ClassPathMapperScanner改写的isCandidateComponent(AnnotatedBeanDefinition)方法里)
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
//这里是ClassPathMapperScanner自己改写的内容
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
/**
* 第68行代码处:
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
//这里就可以看到:ClassPathMapperScanner改写了Spring默认的实现,使mapper接口可以被注册到Spring容器中
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
/**
* 第75行代码处:
*/
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
//遍历所有的mapper
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
boolean scopedProxy = false;
//...
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
//给MapperFactoryBean的构造器传参,参数为mapper接口的Class对象
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
try {
// for spring-native
definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
} catch (ClassNotFoundException ignore) {
// ignore
}
//bean定义的类型需要从原始mapper的类型改写为MapperFactoryBean(本行代码和上面给构造器传参的代码的顺序不能换)
definition.setBeanClass(this.mapperFactoryBeanClass);
//...
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
if (scopedProxy) {
continue;
}
//...
}
}
? ? ? ? 上面最终会把类型为MapperFactoryBean的bean定义注册进去,之后的操作就是调用MapperFactoryBean的getObject方法了,之前在第4.2.1小节已经讲过了。
原创不易,未得准许,请勿转载,翻版必究
|