Mybatis版本:3.5.10-SNAPSHOT。
MyBatis是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置,并将原始类型、接口和Java POJO映射到数据库的记录中。
现在国内的开发环境不知道从什么时候开始传出一种风气,不管开发出什么框架,都要往大而全的方向写,或者说是自己搞个生态,重复造轮子(可能是国内竞争压力大,导致越来越卷)。而MyBatis那种大道至简的源码设计风格,是非常能切中我的点的。它其实只干了一件事,就是专注于Java接口与SQL之间的映射,其他的功能也都是为了这个核心功能去服务的。所以MyBatis非常适合那些想学习源码的初学者,作为他们的第一个进行学习的源码框架。正如MyBatis源码中的pom文件中的描述所言:简单是MyBatis最大的优势。
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.
Hibernate几乎不需要写SQL,甚至不用懂SQL的语法。只需要定义好POJO到数据库表的映射关系,即可通过Hibernate提供的方法来完成持久层操作;而MyBatis是需要自己写SQL的,工作量稍大,但是可以对SQL进行调优,更灵活。通过Mybatis的代码生成器,可以自动帮我们生成Java实体类、mapper接口和XML文件,一定程度上可以简化开发。
在国内的环境中,MyBatis相比于Hibernate更流行些。而在国外,Hibernate用得多一些。在我看来,两者都有各自的适用场景。因为Hibernate我用得不多(只在当年毕设时有用过),所以就不妄加评论了。
MyBatis-Plus是在MyBatis的基础上进行二次开发的框架,除了保留MyBatis的原有功能之外,还提供了类似Hibernate那样直接通过Java代码即可操作数据库的能力。但是MyBatis-Plus使用的是自己定义的语法来操作数据库,所以需要增加学习成本。它更适合那些规模小、SQL简单(最好是单表的增删改查)、需要快速进行上线的项目使用。
我在之前的公司中有用过MyBatis-Plus,但从我个人的角度而言,我并不是特别能看好MyBatis-Plus能达到MyBatis的地位(正如其官网所言:愿景是成为MyBatis最好的搭档),甚至说能取代MyBatis(取名为“Plus”的野心),它只会满足一部分人的需求。MyBatis-Plus留给我的印象是:如果是复杂的查询语句的话,它的语法将会写得很复杂,不利于排查问题和调优。所以这也就限制住了MyBatis-Plus只适合那些简单业务的场景,而MyBatis的代码生成器又从一定程度上蚕食了这个场景下的市场。
在这里我不想过多地去评价MyBatis-Plus这个框架,我没有达到那个高度,也不想去引战。每个人的开发经验、想法都不同,所以对于MyBatis-Plus的理解也就不同。在我看来,MyBatis-Plus是有它存在的意义的,毕竟连MyBatis官方也实现了类似的插件:mybatis-dynamic-sql。
以下的代码是以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的节点下的内容
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批量的方式
*/
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
/*
<2>通过resource、classpath下读取的方式
*/
String resource = child.getStringAttribute("resource");
/*
<3>通过url、本地或网络资源的方式
*/
String url = child.getStringAttribute("url");
/*
<4>通过class、mapper接口的方式
*/
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 list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
/**
* 第245行和第247代码处:
*/
private void buildStatementFromContext(List 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: and 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 void addMapper(Class type) {
mapperRegistry.addMapper(type);
}
/**
* MapperRegistry:
*/
public void addMapper(Class 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);
}
在上面第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) {
//设置默认缓存实现类为PerpetualCache
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
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小节的第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 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);
}
通过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;
}
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, Set> 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 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;
}
}
可以看到,就是在执行目标方法的前后分别打印了日志。然后需要将插件进行配置:
注意:这里的顺序是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 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。。。
数据操作以selectOne方法为例:
UserDO userDO = session.selectOne("org.apache.ibatis.demo.dao.UserDAO.getById", 1L);
查看其实现:
/**
* DefaultSqlSession:
*/
@Override
public T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
//可以看到selectOne方法是通过selectList方法来实现的,只不过是只截取第一条数据而已
List 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 List selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
private List 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
通过SqlSession的getMapper方法可以直接获取到mapper接口,从而可以调用到相关业务方法。这种方式比直接调用SqlSession的相关方法要更好一些,避免了硬编码:
UserDAO mapper = session.getMapper(UserDAO.class);
UserDO userDO = mapper.getById(1L);
getMapper方法的实现就很简单了,下面来看下其实现:
/**
* DefaultSqlSession:
*/
@Override
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
/**
* Configuration:
*/
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
/**
* MapperRegistry:
*/
@SuppressWarnings("unchecked")
public T getMapper(Class type, SqlSession sqlSession) {
/*
获取缓存中的值
还记得之前在第2小节中调用的addMapper方法吗?在其中会往knownMappers里添加MapperProxyFactory。
而这里就是从其中拿出mapper接口
*/
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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 mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy 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的相关方法。
原创不易,未得准许,请勿转载,翻版必究