荐 Mybatis执行流程三大阶段(源码解析)
这里写自定义目录标题
- 一、流程概述
- 二、加载配置的三个核心类
- 三、Configuration对象
- 四、流程解析
- 4.1 初始化阶段
- parse()解析Mapper.xml
- 1.解析mapper节点
- 2.将mapper文件添加到Configuration中
- 3.注册Mapper接口
- 初始化涉及到的设计模式:
- 4.2 代理封装阶段(binding)
- 代理封装涉及到的设计模式
- 4.3 数据读写阶段
- Executor
- StatementHandler
- 4.3.1.获取`Configuration`对象
- 4.3.2.创建`StatementHandler`对象
- 4.3.3.创建Statement
- 4.4 结果集处理
- 数据读写阶段涉及的设计模式
一、流程概述
Mybatis的执行流程,首先从入门例子开始:
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 1.读取mybatis配置文件创SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
}
@Test
public void quickStart() throws IOException {
// 2.获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取对应mapper实例
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//4.调用接口方法
List<User> users = mapper.selectAll();
for (User user : users) {
System.out.println(user);
}
}
可以看到,整个流程可以大致分为三个阶段:
-
初始化
读取XML配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化的工作;
-
代理封装
封装iBatis模型,使用Mapper接口开发的初始化工作,获取Mapper接口的动态代理对象。
-
数据访问
通过SqlSession完成SQL解析,参数的映射、sql执行、结果解析过程。
二、加载配置的三个核心类
加载配置时,MyBatis是通过建造者模式来实现的(但是只是实现屏蔽了复杂对象的创造过程,并没有建造者熟悉的味道–流式建造过程,更像是工厂模式),三个核心类和相关类图如下:
其中:
BaseBuilder:是所有解析器的抽象父类,内部维护了配置文件Configuration的唯一实例,提供了通用的方法;
//初始化过程的核心对象,包含xml配置文件的所有信息,唯一单例
protected final Configuration configuration;
//记录TypeAlias别名信息
protected final TypeAliasRegistry typeAliasRegistry;
//记录TypeHandler别名信息
protected final TypeHandlerRegistry typeHandlerRegistry;
XMLConfigBuilder: 主要负责解析 mybatis-config.xml;
XMLMapperBuilder: 主要负责解析映射配置 Mapper.xml 文件;
XMLStatementBuilder: 主要负责解析映射配置文件中的 SQL 节点;
三、Configuration对象
Configuration包含了配置文件中的所有信息,是全局单例的,应用级别生命周期,几个重要属性如下:
public class Configuration {
/*配置全局性的cache开关,默认为true**/
protected boolean cacheEnabled = true;
/* 设置但JDBC类型为空时,某些驱动程序 要指定值**/
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
/* 执行类型,有simple、resue及batch**/
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
/*指定 MyBatis 应如何自动映射列到字段或属性*/
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
//反射工厂
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
//创建POJO对象工厂
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
/*延迟加载的全局开关*/
protected boolean lazyLoadingEnabled = false;
/*指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具*/
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
/*TypeHandler注册中心*/
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
/*TypeAlias注册中心*/
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
/*mapper接口的动态代理注册中心*/
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
/*mapper文件中增删改查操作的注册中心*/
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
/*mapper文件中配置cache节点的 二级缓存*/
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
/*mapper文件中配置的所有resultMap对象 key为命名空间+ID*/
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
/*mapper文件中配置KeyGenerator的insert和update节点,key为命名空间+ID*/
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
/*加载到的所有*mapper.xml文件*/
protected final Set<String> loadedResources = new HashSet<>();
/*mapper文件中配置的sql元素,key为命名空间+ID*/
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
}
其中涉及到几个配置相关的类:
MappedStatemen
t(存储 mapper.xml 文件中的 select、insert、update 和 delete 节点和相关属性);
ResultMap
:用于解析 mapper.xml 文件中的 resultMap 节点,使用 ResultMapping
封装了我们在resultMap标签中配置的所有属性信息:
public class ResultMapping {
private Configuration configuration;//引用的configuration对象
private String property;
private String column;
private Class<?> javaType;
private JdbcType jdbcType;
private TypeHandler<?> typeHandler;//对应节点的typeHandler属性
private String nestedResultMapId;////对应节点的resultMap属性,嵌套结果时使用
private String nestedQueryId;////对应节点的select属性,嵌套查询时使用
private Set<String> notNullColumns;//对应节点的notNullColumn属性
private String columnPrefix;
private List<ResultFlag> flags;//标志,id 或者 constructor
private List<ResultMapping> composites;
private String resultSet;//对应节点的resultSet属性
private String foreignColumn;//对应节点的foreignColumn属性
private boolean lazy;//对应节点的fetchType属性,是否延迟加载
}
同样的ParameterMap
也使用了ParameterMapping
维护参数相关信息。
SqlSource:用于创建 BoundSql
。mapper.xml 文件中的 sql 语句会被解析成 BoundSql 对象,经过解析 BoundSql 包含的语句最终仅仅包含?占位符,可以直接提交给数据库执行
四、流程解析
4.1 初始化阶段
初始化流程开始的地方是从new SqlSessionFactoryBuilder().build(reader);
开始的:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//读取配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());//解析配置文件得到configuration对象,并返回SqlSessionFactory
} 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.
}
}
}
第一个就是XMLConfiBuilder,我们来看下其构造中做了啥:
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
可以看到,其调用父类BaseBuilder的构造,并传入new的一个Configuration对象,这也是唯一一处new Configuration的位置,其他所有地方都是使用该Configuration对象的引用,保证单例。
接着具体看看parser.parse()
中是如何解析配置文件的:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
这边首先通过parser.evalNode("/configuration")
拿到配置文件的根元素,然后调用parseConfiguration
解析:
private void parseConfiguration(XNode root) {
try {
//解析<properties>节点
propertiesElement(root.evalNode("properties"));
//解析<settings>节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//解析<typeAliases>节点
typeAliasesElement(root.evalNode("typeAliases"));
//解析<plugins>节点
pluginElement(root.evalNode("plugins"));
//解析<objectFactory>节点
objectFactoryElement(root.evalNode("objectFactory"));
//解析<objectWrapperFactory>节点
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析<reflectorFactory>节点
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);//将settings填充到configuration
// read it after objectFactory and objectWrapperFactory issue #631
//解析<environments>节点
environmentsElement(root.evalNode("environments"));
//解析<databaseIdProvider>节点
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析<typeHandlers>节点
typeHandlerElement(root.evalNode("typeHandlers"));
//解析<mappers>节点
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
该方法中就解析了配置文件中所有标签。
我们来重点看解析mappers节点的部分:
在配置文件中我们通常这样定义:
<!-- 映射文件,mapper的配置文件 -->
<mappers>
<!--直接映射到相应的mapper文件 -->
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/OrderMapper.xml" />
</mappers>
解析:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {//处理mapper子节点
if ("package".equals(child.getName())) {//package子节点
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {//获取<mapper>节点的resource、url或mClass属性这三个属性互斥
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {//如果resource不为空
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);//加载mapper文件
//实例化XMLMapperBuilder解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {//如果url不为空
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);//加载mapper文件
//实例化XMLMapperBuilder解析mapper映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {//如果class不为空
Class<?> mapperInterface = Resources.classForName(mapperClass);//加载class对象
configuration.addMapper(mapperInterface);//向代理中心注册mapper
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
我们在mappers标签中配置mapper可以有两种方式,一个是配置package让其扫描指定包下的mapper,或者使用mapper标签,通过resource或url或class来指定。
我们重点看一下使用mapper标签配置的方式:
-
获取resource、url和class属性的值;
-
优先使用resource来解析,创建
XMLMapperBuilder
,调用parse()
解析该xxxMapper.xml
文件;public void parse() { //判断是否已经加载该配置文件 if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper"));//处理mapper节点 configuration.addLoadedResource(resource);//将mapper文件添加到configuration.loadedResources中 bindMapperForNamespace();//注册mapper接口 } //处理解析失败的ResultMap节点 parsePendingResultMaps(); //处理解析失败的CacheRef节点 parsePendingCacheRefs(); //处理解析失败的Sql语句节点 parsePendingStatements(); }
parse()解析Mapper.xml
1.解析mapper节点
先看下mapper节点一般有啥东西:
<mapper namespace="com.wml.mapper.UserMapper">
<resultMap id="resultMap" type="User">
<id column="id" property="id"/>
<result column="username" property="userName"/>
<result column="password" property="password"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
</resultMap>
<sql id="fields">
id, username, password, name, age
</sql>
<select id="userList" resultMap="resultMap">
SELECT <include refid="fields"/> FROM tb_user
</select>
<cache/>
</mapper>
private void configurationElement(XNode context) {
try {
//1.获取mapper节点的namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//2.设置到builderAssistant的namespace属性
builderAssistant.setCurrentNamespace(namespace);
//3.解析cache-ref节点,可以引用其他命名空间下的缓存
cacheRefElement(context.evalNode("cache-ref"));
//★★★★★★★★4.解析cache节点
cacheElement(context.evalNode("cache"));
//5.解析parameterMap节点(已废弃)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//6.★★★★★★★★★★解析resultMap节点(基于数据结果去理解)
resultMapElements(context.evalNodes("/mapper/resultMap"));
//6.★★★★★★★★★★★★解析sql节点
sqlElement(context.evalNodes("/mapper/sql"));
//7.★★★★★★★解析select、insert、update、delete节点 ----------
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
各种节点的解析中,我们重点看打五角星的。
1.1解析缓存节点
private void cacheElement(XNode context) throws Exception {
if (context != null) {
//获取cache节点的type属性,默认为PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
//找到type对应的cache接口的实现
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
//读取eviction属性,缓存的淘汰策略,默认LRU
String eviction = context.getStringAttribute("eviction", "LRU");
//根据eviction属性,找到装饰器
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
//读取flushInterval属性:缓存的刷新周期
Long flushInterval = context.getLongAttribute("flushInterval");
//读取size属性:缓存的容量大小
Integer size = context.getIntAttribute("size");
//读取readOnly属性:缓存是否只读
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
//读取blocking属性:缓存是否阻塞
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//通过builderAssistant创建缓存对象,并添加至configuration
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
读取cache中的各个属性后,调用builderAssistant
的useNewCache
创建缓存对象,并添加至configuration
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
//建造模式,创建一个cache对象,将cache中的信息注入
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class)) // 缓存实现类默认为PerpetualCache
.addDecorator(valueOrDefault(evictionClass, LruCache.class)) //缓存策略默认为LRU
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
//最后将缓存添加至configuration对象中,注意二级缓存以命名空间为单位进行划分
configuration.addCache(cache);
currentCache = cache;
return cache;
}
传入Cache信息后调用build()完成Cache的构造:
public Cache build() {
//设置缓存的主实现类为PerpetualCache
setDefaultImplementations();
//通过反射实例化PerpetualCache对象
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);//根据cache节点下的<property>信息,初始化cache
if (PerpetualCache.class.equals(cache.getClass())) {//如果cache是PerpetualCache的实现,则为其添加标准的装饰器
for (Class<? extends Cache> decorator : decorators) {//为cache对象添加装饰器,这里主要处理缓存清空策略的装饰器
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
//通过一些属性为cache对象添加装饰器
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
//如果cache不是PerpetualCache的实现,则为其添加日志的能力
cache = new LoggingCache(cache);
}
return cache;
}
会调用setStandardDecorators
为缓存添加装饰器:
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);//size
}
if (clearInterval != null) { //定时清空
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {//读写属性
cache = new SerializedCache(cache);
}
cache = new LoggingCache(cache);//默认加上日志功能
cache = new SynchronizedCache(cache); //默认加上同步能力
if (blocking) {
//根据配置加上阻塞能力
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
1.2 解析resultMap
接着看解析resultMap:
private void resultMapElements(List<XNode> list) throws Exception {
//遍历所有的resultmap节点
for (XNode resultMapNode : list) {
try {
//解析具体某一个resultMap节点
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
实际就是解析sql查询的字段与pojo属性之间的转化规则,解析前先分析下resultMap的数据结构:
ResultMap
public class ResultMap {
private Configuration configuration;//configuration对象
private String id;//resultMap的id属性
private Class<?> type;//resultMap的type属性
private List<ResultMapping> resultMappings;//除discriminator节点之外的映射关系
private List<ResultMapping> idResultMappings;//记录ID或者<constructor>中idArg的映射关系
private List<ResultMapping> constructorResultMappings;////记录<constructor>标志的映射关系
private List<ResultMapping> propertyResultMappings;//记录非<constructor>标志的映射关系
private Set<String> mappedColumns;//记录所有有映射关系的columns字段
private Set<String> mappedProperties;//记录所有有映射关系的property字段
private Discriminator discriminator;//鉴别器,对应discriminator节点
private boolean hasNestedResultMaps;//是否有嵌套结果映射
private boolean hasNestedQueries;////是否有嵌套查询
private Boolean autoMapping;//是否开启了自动映射
以上存储的是和整个resultMap
标签的所有信息,而resultMap中的子标签,如constructor
、id
、result
等都使用ResultMapping
类封装:
ResultMapping
public class ResultMapping {
private Configuration configuration;//引用的configuration对象
// ====================
private String property;//节点的property属性
private String column;//节点的column属性
private Class<?> javaType;//节点的javaType属性
private JdbcType jdbcType;//节点的jdbcType属性
private TypeHandler<?> typeHandler;//节点的typeHandler属性
//================以上都是子标签可配置的属性信息==============
private String nestedResultMapId;//节点的resultMap属性,嵌套结果时使用
private String nestedQueryId;//节点的select属性,嵌套查询时使用
private Set<String> notNullColumns;//对应节点的notNullColumn属性
private String columnPrefix;//对应节点的columnPrefix属性
private List<ResultFlag> flags;//标志,id 或者 constructor
private List<ResultMapping> composites;
private String resultSet;//对应节点的resultSet属性
private String foreignColumn;//对应节点的foreignColumn属性
private boolean lazy;//对应节点的fetchType属性,是否延迟加载
}
以上两个类都是基于建造者模式构造。
接着看下具体解析过程:
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//获取resultMap节点的id属性
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//获取resultMap节点的type属性
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//获取resultMapp节点的extends属性,描述继承关系
String extend = resultMapNode.getStringAttribute("extends");
//获取resultMap节点的autoMapping属性,是否开启自动映射
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//从别名注册中心获取entity的class对象
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
//记录子节点中的映射结果集合
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
//从xml文件中获取当前resultmap中的所有子节点,并开始遍历
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {//处理<constructor>节点
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {//处理<discriminator>节点
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {//处理<id> <result> <association> <collection>节点
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);//如果是id节点,向flags中添加元素
}
//创建ResultMapping对象并加入resultMappings集合中
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//实例化resultMap解析器
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//通过resultMap析器实例化resultMap并将其注册到configuration对象
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
大流程:
- 读取resultMap的属性:id、type、extends、autoMapping;
- 获取resultMap所有子标签,遍历解析:constructor、discriminator或是id、 result、association、collection,通过创建ResultMapping对象添加到对应集合中;
- 最后创建ResultMap解析器,实例化resultMap后添加到Configuration对象中。
创建ResultMapping对象:
//根据resultmap中的子节点信息,创建resultMapping对象
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
//使用建造者模式创建resultMapping对象
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
首先解析出该标签中的所有属性信息,接着通过builderAssistant.buildResultMapping
,内部使用ResultMapping
的Builder
构造一个ResultMapping
对象返回;最后将该对象添加到大流程的resultMappings
中。
1.3解析sql标签
这部分比较简单,如下:
list为所有的sql标签,遍历后,拿到id属性,然后将id和该节点的映射关系保存到Map结构的sqlFragments
中。
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
}
}
1.4 解析select、update、delete等标签
同样,先看下维护该标签的数据结构:
MappedStatement
所有的信息都封装到了MappedStatement
类中:
public final class MappedStatement {
private String resource;//节点的完整的id属性,包括命名空间
private Configuration configuration;
private String id;//节点的id属性
private Integer fetchSize;//节点的fetchSize属性,查询数据的条数
private Integer timeout;//节点的timeout属性,超时时间
private StatementType statementType;//节点的statementType属性,默认值:StatementType.PREPARED;
private ResultSetType resultSetType;//节点的resultSetType属性,jdbc知识
private SqlSource sqlSource;//节点中具体的sql语句信息
private Cache cache;//对应的二级缓存
private ParameterMap parameterMap;//已废弃
private List<ResultMap> resultMaps;//节点的resultMaps属性
private boolean flushCacheRequired;//节点的flushCache属性是否刷新缓存
private boolean useCache;//节点的useCache属性是否使用二级缓存
private boolean resultOrdered;
private SqlCommandType sqlCommandType;//sql语句的类型(枚举类):INSERT, UPDATE, DELETE, SELECT,UNKNOWN
private KeyGenerator keyGenerator;//节点keyGenerator属性
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;//是否有嵌套resultMap
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;//多结果集使用
}
该类维护了sql语句标签所有的信息,这里里面有几个比较重要的:SqlSource
。
SqlSource
维护了具体的Sql语句信息:
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
内部使用BoundSql封装:
public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
}
-
sql:
将#{}、${}替换为 ? 后的sql语句
-
parameterMappings
参数集合,有几个#{}或${}就有几个元素,#{}的位置和该参数集合对应位置映射,如
select *from user where id=#{id} and username=#{username}
,对应集合的第0个元素就是id参数值,第1个元素就是username参数值。
接着看具体解析流程:
//解析所有的sql语句节点并维护到configuration对象
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//此处创建XMLStatementBuilder 用于解析sql语句节点
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析sql语句节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
该部分遍历所有的sql语句节点,然后根据当前节点构造一个XMLStatementBuilder
对象,用于解析该节点,具体解析如下:
public void parseStatementNode() {
//获取sql节点的id
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
/*获取sql节点的各种属性*/
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//根据sql节点的名称获取sql类型:INSERT, UPDATE, DELETE, SELECT
String nodeName = context.getNode().getNodeName();
//SqlCommandType为sql类型的枚举类
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
//在解析sql语句之前先解析<include>节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
//在解析sql语句之前,先解析<selectKey>节点,随后在xml节点中删除
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
//实例化sqlSource,使用sqlSource封装sql语句,这部分较复杂,后面单独讲解
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");//获取resultSets属性
String keyProperty = context.getStringAttribute("keyProperty");//获取主键信息keyProperty
String keyColumn = context.getStringAttribute("keyColumn");///获取主键信息keyColumn
//根据<selectKey>获取对应的SelectKeyGenerator的id
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
//获取keyGenerator对象,如果是insert类型的sql语句,会使用KeyGenerator接口获取数据库生产的id;
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//通过builderAssistant实例化MappedStatement,并注册至configuration对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
总体来说还是很简单的,获取对应的属性信息,然后最终调用帮手builderAssistant.addMappedStatement
内部通过建造者模式构造一个MappedStatement
,最终会添加到Configuration
对象的protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
中。
其中,key为命名空间加当前sql标签的id。
解析sql语句
XMLLanguageDriver#createSqlSource
@Override
//解析xml文件中的sql语句并封装成SqlSource
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
解析Sql语句时通过XMLScriptBuilder
建造器来进行解析的:
//解析sql脚本,返回SqlSource
public SqlSource parseScriptNode() {
//1.解析动态sql
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);//动态sql的解析
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);//非动态sql的解析
}
//实际返回的都是StaticSqlSource,可以直接让数据库执行的sql语句,包含?占位符
return sqlSource;
}
该部分首先进行的是动态sql的判断:
动态sql判断
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) {//如果是文本类型或者是CDATA类型,则创建TextSqlNode或StaticTextSqlNode
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // 如果节点内元素时ELEMENT类型的而不是文本类型
String nodeName = child.getNode().getNodeName();
//如果该节点有对应的NodeHandler则为动态sql,否则为静态sql
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//处理动态sql
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
这里面有个nodeHandlerMap
,该map在构造的时候会调用initNodeHandlerMap
:
private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
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());
}
因此,对于和动态sql相关的几个标签,都初始化了对应的Hnadler处理器,即如果能拿到对应的handler,说明该sql是一个动态sql。
接着会调用handler.handleNode(child, contents);
处理该动态sql的标签,针对不同标签对应不同的处理类,如上,每个处理类会将sql信息构造成对应的SqlNode节点,添加到contents中:
如ifHnandler
:
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String test = nodeToHandle.getStringAttribute("test");
//将if中test信息封装到IfSqlNode中
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
这些IfSqlNode的父接口为SqlNode
public interface SqlNode {
boolean apply(DynamicContext context);
}
其实现类如下:
该部分用到了组合模式
所有标签节点都实现了apply方法,在DynamicSqlSource
的getBoundSql
方法中,如下:
就会调用rootSqlNode.apply(context);
方法,从根节点开始,递归的向下调用所有实现SqlNode接口的叶子节点的apply方法。
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
递归的终节点是TextSqlNode
,直接将对应的内容append到sql中:
@Override
public boolean apply(DynamicContext context) {
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
context.appendSql(parser.parse(text));
return true;
}
而其他的节点需要先判断再递归(或传递)向下调用apply方法。
回到前面,动态sql的解析parseDynamicTags
结束后,会通过contents
构造一个MixedSqlNode
返回,contents
就是所有SqlNode
的集合。
再回到parseScriptNode
方法,会根据是否为动态sql,来通过返回的MixedSqlNode
创建DynamicSqlSource
或RawSqlSource
返回。
这样就拿到了最终的SqlSource
对象。
OK,到这里解析配置文件中的mapper标签和Mapper.xml的部分就基本讲解完毕了。
接下来继续回到流程解析一开始的parse
方法,
public void parse() {
//判断是否已经加载该配置文件
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));//处理mapper节点
configuration.addLoadedResource(resource);//将mapper文件添加到configuration.loadedResources中
bindMapperForNamespace();//注册mapper接口
}
.............
}
2.将mapper文件添加到Configuration中
configuration.addLoadedResource(resource)
会将当前的Mapper.xml文件添加到Configuration对象中的loadedResources
中:
/*加载到的所有*mapper.xml文件*/
protected final Set<String> loadedResources = new HashSet<>();
3.注册Mapper接口
private void bindMapperForNamespace() {
//获取命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//通过命名空间获取mapper接口的class对象
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {//是否已经注册过该mapper接口?
//将命名空间添加至configuration.loadedResource集合中
configuration.addLoadedResource("namespace:" + namespace);
//将mapper接口添加到mapper注册中心
configuration.addMapper(boundType);
}
}
}
}
这里会通过命名空间拿到对应的Mapper接口,并将其添加到Configuration
对象的Mapper
接口的动态代理注册中心里:
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
//将mapper接口的工厂类添加到mapper注册中心
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//实例化Mapper接口的代理工厂类,并将信息添加至knownMappers
knownMappers.put(type, new MapperProxyFactory<T>(type));
//解析接口上的注解信息,并添加至configuration对象
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
knownMappers
记录了mapper接口与对应MapperProxyFactory
之间的映射关系
以上是初始化的关键流程。
初始化涉及到的设计模式:
-
建造者模式
SqlSessionFactoryBuilder
创建SqlSessionFactory
XMLConfigBuilder
、XMLMapperBuilder
、XMLStatementBuilder
创建Configuration
对象。这几个虽然没有建造者模式的链式编程的feel,但建造者模式的灵魂还是有的。CacheBuilder
创建二级缓存ResultMapping.Builder
创建ResultMapping
对象MappedStatement.Builder
创建MappedStatement
对象 -
组合模式(动态SQL中,不同的SqlNdoe,递归调用apply方法解析)
4.2 代理封装阶段(binding)
4.2.1 SqlSession
SqlSession是对外提供的核心接口,相当于一个门面模式的对外接口,提供了对数据库的读写、获取映射器、管理事物等操作,SqlSession
由SqlSessionFactory
创建,其默认实现DefaultSqlSession
内部维护了一个单例Configuration
对象,SqlSession
代表一次与数据库的连接。
刚刚提到了SqlSession
的默认实现DefaultSqlSession
:
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
}
其内部维护了一个Executor
对象,以单一职责为原则,SqlSession对数据库的各种查询、插入、修改等操作最终都交给Executor
对象完成。
4.2.2 两种编程方式
private static SqlSessionFactory ssf;
private static Reader reader;
static {
try {
reader = Resources.getResourceAsReader("mybatis-config.xml");
ssf = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
}
}
public List<User> userList() {
SqlSession sqlSession=ssf.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.userList();
return users;
}
第一种就是通过sqlSession.getMapper(xxMapper.class)
拿到对应Mapper接口的实例(此处的UserMapper为动态代理对象),然后调用对应的方法进行处理。
如上,可以看到代理类是MapperProxy
,被代理的接口就是我们的UserMapper
接口。
private static final String NAME_SPACE = "UserMapper.";
public List<User> userList() {
SqlSession sqlSession=ssf.openSession();
List<User> users = sqlSession.selectList(NAME_SPACE + "userList");
return users;
}
第二种就是直接通过sqlSession.selectList(NAME_SPACE + "userList");
传入对应的命名空间+方法id(名)来操作。
其实第一种最终也会转化为第二种进行处理,那么这期间是如何转换的呢?
4.2.3 binding模块
4.2.3.1 getMapper获取代理实例
从sqlSession.getMapper(UserMapper.class);
开始分析:
进入DefaultSqlSession
的getMapper
:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
接着调用configuration
对象的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
这边会涉及到几个关键的类,我们先了解下这几个类的作用:
MapperRegistry
可以看到继续通过mapperRegistry
获取,而mapperRegistry
前面分析过,是mapper接口和对应的代理对象工厂的注册中心;在4.1第3部分注册Mapper接口中提到,会将当前mapper接口的class对象和对应的代理工厂类添加到knownMappers
中:
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
.....
try {
//实例化Mapper接口的代理工程类,并将信息添加至knownMappers
knownMappers.put(type, new MapperProxyFactory<T>(type));
.....
}
}
}
knownMappers
维护在注册中心MapperRegistry
中:
//记录了mapper接口与对应MapperProxyFactory之间的关系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
MapperProxyFactory
用于生成mapper接口动态代理的实例对象;
public class MapperProxyFactory<T> {
//mapper接口的class对象
private final Class<T> mapperInterface;
//key是当前mapper接口中的某个方法的method对象,value是对应的MapperMethod,MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
}
MapperMethod
封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息;它是mapper接口与映射配置文件中sql语句的桥梁
public class MapperMethod {
//从configuration中获取方法的命名空间.方法名以及SQL语句的类型
private final SqlCommand command;
//封装mapper接口方法的信息
private final MethodSignature method;
SqlCommand:
public static class SqlCommand {
//sql的名称,命名空间+方法名称
private final String name;
//获取sql语句的类型
private final SqlCommandType type;
}
MethodSignature:
public static class MethodSignature {
private final boolean returnsMany;//返回参数是否为集合类型或数组
private final boolean returnsMap;//返回参数是否为map
private final boolean returnsVoid;//返回值为空
private final boolean returnsCursor;//返回值是否为游标类型
private final boolean returnsOptional;//返回值是否为Optional
private final Class<?> returnType;//返回值类型
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;//该方法的参数解析器
}
回到前面的getMapper,调用MapperRegistry.getMapper
:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
根据当前mapper类型拿到对应的代理工厂后,调用代理工厂的newInstance
方法返回代理实例,如下:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
可以看到,每次调用都会创建新的MapperProxy
代理类对象,(因此当调用多次getMapper(UserMapper.class)
返回的实例不是同一个):
protected T newInstance(MapperProxy<T> mapperProxy) {
//创建实现了mapper接口的动态代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
最后,就是通过动态代理传入相关参数返回被代理接口的代理实例。
到这里,我们就拿到了一开始的UserMapper
实例,接着我们调用接口的某个方法时,由于该实例被动态代理,因此,我们需要从代理类MapperProxy
的invoke
方法中看看如何对mapper接口进行的增强。
4.2.3.2 代理增强
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {//如果是Object本身的方法不增强
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//从缓存中获取mapperMethod对象,如果缓存中没有,则创建一个,并添加到缓存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
//调用execute方法执行sql
return mapperMethod.execute(sqlSession, args);
}
cachedMapperMethod
如下:
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
调用ConcurrentHashMap
的computeIfAbsent
,即如果method不在map的key中,则将method和右边函数的计算结果添加到map中,右侧实例化了一个MapperMethod
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
在以上两个构造中,就会根据configuration
对象和另外两个参数初始化SqlCommand
和MethodSignature
类中的属性,如sql的name(命名空间+方法名)、sql类型、接口方法的入参和出参的信息等。
回到invoke
方法,最后调用MapperMethod
的execute
方法:
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:
....
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;
}
到这里就很清晰了,会根据SqlCommand
的SqlCommandType
的值,进入不同的处理逻辑,可以看到增删改部分,解析完方法入参后,就直接调用SqlSession.insert/update/delete
方法进行处理,到此就转化为了我们的第二种编程方法。
这里看一下SELECT
的处理:
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {//返回值为void
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {//返回值为集合或者数组
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {//返回值为map
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 = OptionalUtil.ofNullable(result);
}
}
break;
可以看到,SELECT中,会根据方法返回值的不同分别处理,其处理逻辑基本一致,都是先解析参数,然后调用对应的sqlsession.select/selectMap/selecList/selectOne
方法获得返回值,再将结果值转化为对应的类型返回。
这里以最后一个selectOne,也就是返回值为某个实体类的方法,进行解析:
首先调用MethodSignature
的convertArgsToSqlCommandParam
解析参数:
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
这里借助参数解析器进行解析,而该解析器在MethodSignature
构造的时候进行初始化,内部关键的数据结构为private final SortedMap<Integer, String> names;
,该names中存储了当前方法的所有入参的位置和入参名的映射关系。
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//key:参数名 value:参数具体值
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
//这里主要是添加了参数的通用名称:key: param1 param2... value:参数具体值
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
该方法就是遍历names,将其值,即参数名作为param
的key,再通过参数位置从传入的args
中拿到对应的参数值,最终返回一个 参数名:参数值 的map结构。
这么处理主要是因为sqlsession.select
或者其他方法只接受一个参数,因此原本的参数数组Object[] args
无法传入,需要进行转化。
OK,最后一步就是result = sqlSession.selectOne(command.getName(), param);
,将SqlCommand的name(即命名空间+方法名)和所需参数传入,就对上了原始的操作方式。
代理封装涉及到的设计模式
动态代理
通过MapperProxy
对Mapper
接口进行增强,先通过configuration
拿到对应mapper
的代理工厂,然后通过动态代理返回mapper
接口的代理实例,在增强中调用MapperMethod
的execute
方法,拿到当前方法的参数和处理的方法名(命名空间+方法名)(相关信息在初始化MapperMethod
的时候初始化),最后调用SqlSession
对应的方法进行操作。
策略模式
SqlSessionFactory
的默认实现DefaultSqlSessionFactory
的openSession
方法,会调用openSessionFromDataSource
方法,如下:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//获取mybatis配置文件中的environment对象
final Environment environment = configuration.getEnvironment();
//从environment获取transactionFactory对象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//创建事务对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根据配置创建executor
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();
}
}
该方法中,transactionFactory
创建事务对象时,会根据mybatis配置文件中的配置的不同,创建JDBC的事务对象或是Managed事务对象,如下:
我们在配置文件中的配置如下:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" 或 type="Managed"/>
.............
</environment>
</environments>
4.3 数据读写阶段
该阶段就是处理SqlSession.xxx
的阶段,进入默认实现DefaultSqlSession
看一下:
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;//是否自动提交事务
private boolean dirty;//当前缓存是否有脏数据
private List<Cursor<?>> cursorList;
}
这里有一个核心组件Executor
,相关操作最终都借助该组件实现,如:
DefaultSqlsession#select
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这里先根据statement拿到对应的MappedStatement
对象,statement
就是我们传入的命名空间加方法名,MappedStatement
就是对应的mapper.xml中该方法的所有信息,在初始化阶段时已经讲过了。
接着调用executor.query()
方法处理。
这里我们先了解下 Executor 组件 :
Executor
首先看一下它的类图:
这里面有两个直接实现类:
BaseExecutor
一个基础抽象类,实现了 executor 接口的大部分方法,主要提供了缓存管理和事务管理的能力,其他子类需要实现的抽象方法为:doUpdate
,doQuery
,doQueryCursor
等方法;
这里就使用了模板方法模式,即在抽象的BaseExecutor
类中定义了方法的执行模板,某些具体的实现交给不同的子类进行实现处理。
先看一下该类的成员属性:
protected Transaction transaction;//事务对象
protected Executor wrapper;//封装的Executor对象,就是this
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;//延迟加载的队列
protected PerpetualCache localCache;//一级缓存的实现,PerpetualCache
protected PerpetualCache localOutputParameterCache;//一级缓存用于缓存输出的结果
protected Configuration configuration;//全局唯一configuration对象的引用
protected int queryStack;//用于嵌套查询的的层数
private boolean closed;
再看一下一些查询的基本骨架:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取sql语句信息,包括占位符,参数等信息
BoundSql boundSql = ms.getBoundSql(parameter);
//拼装缓存的key值
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
首先通过MappedStatement
拿到对应的SQL信息BoundSql
,再封装一级缓存值CacheKey
:
@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) {//检查当前executor是否关闭
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {//非嵌套查询,并且FlushCache配置为true,则需要清空一级缓存
clearLocalCache();
}
List<E> list;
try {
queryStack++;//查询层次加一
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) {//如果当前sql的一级缓存配置为STATEMENT,查询完既清空一集缓存
// issue #482
clearLocalCache();
}
}
return list;
}
这部分很简单,先从一级缓存拿,如果一级缓存为空,就从数据库加载数据,接着做一些延迟加载的处理,最后判断当前sql的一级缓存范围的类型:
主要有两个:
session:就会有数据的共享:
statement:语句范围,这样不会有数据的共享
如果是statement类型的,则查询完就清空一级缓存
调用数据库查询的操作如下:
//真正访问数据库获取结果的方法
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 {
//调用抽象方法doQuery,方法查询数据库并返回结果,可选的实现包括:simple、reuse、batch
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);//在缓存中删除占位符
}
localCache.putObject(key, list);//将真正的结果对象添加到一级缓存
if (ms.getStatementType() == StatementType.CALLABLE) {//如果是调用存储过程
localOutputParameterCache.putObject(key, parameter);//缓存输出类型结果参数
}
return list;
}
该部分,调用doQuery
方法后,就将查询的结果放到一级缓存中返回结果。而该方法就是当前模板类的需要子类实现的方法。
接着看BaseExecutor的三个子类:
SimpleExecutor默认
默认配置,在 doQuery 方法中使用 PrepareStatement
对象访问数据库, 每次访问都要创建新的 PrepareStatement
对象;
可从前面提过的DefaultSqlSessionFactory
的openSessionFromDataSource
中看到
//从数据源获取数据库连接
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
...
//根据配置创建executor
final Executor executor = configuration.newExecutor(tx, execType);
//创建DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
}....
}
这里的execType就是从默认的openSession()
中传入:
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
默认值为:
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
来看下其doQuery
的实现:
@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();//获取configuration对象
//创建StatementHandler对象,
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
stmt = prepareStatement(handler, ms.getStatementLog());
//通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
首先创建了StatementHandler
对象,这个是什么作用后面再说,接着调用prepareStatement
创建Statement
对象,最后调用handler.query
方法完成查询。而doUpdate
也是最终由handler处理。暂且放一放,先看创建Statement对象的方法:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取connection对象的动态代理,添加日志能力;
Connection connection = getConnection(statementLog);
//通过不同的StatementHandler,利用connection创建(prepare)Statement
stmt = handler.prepare(connection, transaction.getTimeout());
//使用parameterHandler处理占位符
handler.parameterize(stmt);
return stmt;
}
首先获取Connection连接对象:
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
这里transaction.getConnection()
会从真实数据库中获取连接,如果需要打印日志,则通过动态代理创建一个动态代理的Connection对象返回。
接着又借助handler.prepare()
创建了Statement
对象,最后又借助handler处理了占位符才返回。
OK,到这里似乎不讲handler不行了,转下面。
BatchExecutor
在 doQuery 方法中,提供批量执行多条 SQL 语句的能力;
ReuseExecutor
与SimpleExecutor
不同的是,在 doQuery 方法中,使用预编译 PrepareStatement
对象访问数据库,访问时,会重用缓存中的 statement
对象,而不是每次都创建新的PrepareStatement
CachingExecutor
CachingExecutor
直接实现Executor接口,实际使用装饰器模式提供缓存能力。先从缓存查,缓存没有再调用delegate.query从数据库查,再将结果添加到缓存中。
在前面SimpleExecutor中,讲创建executor的Configuration#newExecutor方法中,
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
.....
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);
}
//如果有<cache>节点,通过装饰器,添加二级缓存的能力
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
...;
return executor;
}
如果开启了cache节点,则会创建CachingExecutor
。
StatementHandler
上面我们发现executor执行的时候会后会借用这个handler进行处理,其作用主要是使用数据库的Statement或PrepareStatement执行操作,包括:创建statement对象,为sql语句绑定参数,执行增删改查等SQL语句、将结果映射集进行转化等功能。
其类图如下:
BaseStatementHandler
所有子类的抽象父类,使用模板设计模式定义了获取statement
的步骤,由子类实现具体的实例化不同的statement(模板模式);
我们可以在每个sql语句标签上配置statement类型:
不配置,则默认为PREPARED
,对应以下三个具体子类:
SimpleStatmentHandler
直接使用statement
对象访问数据库,无须参数化;
PreparedStatmentHandler
使用预编译PrepareStatement
对象访问数据库;
CallableStatmentHandler
调用存储过程
RoutingStatementHandler:
Excutor组件真正实例化的子类,使用静态代理模式,根据上下文(Mapper中sql标签配置的statement类型)决定创建哪个具体实体类;
OK,回到刚刚的doQuery
方法,仔细梳理下:
@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();//获取configuration对象
//创建StatementHandler对象,
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
stmt = prepareStatement(handler, ms.getStatementLog());
//通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
4.3.1.获取Configuration
对象
4.3.2.创建StatementHandler
对象
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
这里就是看到,借助RoutingStatementHandler
进行构造StatementHandler
,如下:
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//RoutingStatementHandler最主要的功能就是根据mappedStatment的配置,生成一个对应的StatementHandler对象并赋值给delegate
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
其会根据statementType的不同,来创建不同子类的StatementHandler
,默认就会创建PreparedStatementHandler
,具有预编译功能。
4.3.3.创建Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取connection对象的动态代理,添加日志能力;
Connection connection = getConnection(statementLog);
//通过不同的StatementHandler,利用connection创建(prepare)Statement
stmt = handler.prepare(connection, transaction.getTimeout());
//使用parameterHandler处理占位符
handler.parameterize(stmt);
return stmt;
}
4.3.3.1 获取connection
对象
如果开启日志,则获取其动态代理对象,赋予日志能力;
4.3.3.2 prepare()
创建Statement
对象
@Override
//使用模板模式,定义了获取Statement的步骤,其子类实现实例化Statement的具体的方式;
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
......
statement = instantiateStatement(connection);
//设置超时时间
setStatementTimeout(statement, transactionTimeout);
//设置数据集大小
setFetchSize(statement);
return statement;
....
}
}
首先进入抽象的BaseStatementHandler
,接着调用instantiateStatement
,根据不同的子类进行创建,我们看默认的PreparedStatementHandler
:
@Override
//使用底层的prepareStatement对象来完成对数据库的操作
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
//根据mappedStatement.getKeyGenerator字段,创建prepareStatement
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {//对于insert语句
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
//返回数据库生成的主键
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
//返回数据库生成的主键填充至keyColumnNames中指定的列
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
//设置结果集是否可以滚动以及其游标是否可以上下移动,设置结果集是否可更新
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
//创建普通的prepareStatement对象
return connection.prepareStatement(sql);
}
}
4.3.3.3 parameterHandler
处理占位符
最终会进入默认的参数解析器DefaultParameterHandler
的setParameters
方法:
参数处理器会对预编译的SQL语句进行参数设置,SQL语句中的占位符“?”替换为具体的值。
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//从boundSql中获取sql语句的占位符对应的参数信息
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//遍历这个参数列表,把参数设置到PreparedStatement中
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {//对于存储过程中的参数不处理
Object value;//绑定的实参
String propertyName = parameterMapping.getProperty();//参数的名字
if (boundSql.hasAdditionalParameter(propertyName)) { // 获取对应的实参值
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();//从parameterMapping中获取typeHandler对象
JdbcType jdbcType = parameterMapping.getJdbcType();//获取参数对应的jdbcType
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//为statment中的占位符绑定参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
这部分总结下,就是遍历parameterMappings
即占位符对应的参数信息,然后通过参数名通过不同方式拿到对应参数值,使用typeHandler等,最关键的一步就是typeHandler.setParameter(ps, i + 1, value, jdbcType);
,这就和我们使用原生的jdbc操作数据一模一样,将参数值设置到对应的占位符上。
OK到这就拿到了Statement
对象,并且处理了占位符。
最后第4步:
调用handler.<E>query
进行查询。
进入PreparedStatementHandler
的query
方法:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
首先调用jdbc的execute()
方法执行,最后进行结果集的处理。
4.4 结果集处理
此处借助DefaultResultSetHandler
对数据库返回的结果集(ResultSet
)进行封装,返回用户指定的实体类型;
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//用于保存结果集对象
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
//statment可能返回多个结果集对象,这里先取出第一个结果集
ResultSetWrapper rsw = getFirstResultSet(stmt);
//获取结果集对应resultMap,本质就是获取字段与java属性的映射规则
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);//结果集和resultMap不能为空,为空抛出异常
while (rsw != null && resultMapCount > resultSetCount) {
//获取当前结果集对应的resultMap
ResultMap resultMap = resultMaps.get(resultSetCount);
//根据映射规则(resultMap)对结果集进行转化,转换成目标对象以后放入multipleResults中
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);//获取下一个结果集
cleanUpAfterHandlingResultSet();//清空nestedResultObjects对象
resultSetCount++;
}
//获取多结果集。多结果集一般出现在存储过程的执行,存储过程返回多个resultset,
//mappedStatement.resultSets属性列出多个结果集的名称,用逗号分割;
//多结果集的处理不是重点,暂时不分析
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
-
创建
multipleResults
集合,用户保存最终返回的结果。 -
取出第一个结果集
-
获取对应的resultMap
-
根据resultMap转化结果集,转换成目标对象后添加到
multipleResults
集合;重点看4这里,一般到这里while循环就结束了。
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) {//处理多结果集的嵌套映射 handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { if (resultHandler == null) {//如果resultHandler为空,实例化一个人默认的resultHandler DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); //对ResultSet进行映射,映射结果暂存在resultHandler中 handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); //将暂存在resultHandler中的映射结果,填充到multipleResults multipleResults.add(defaultResultHandler.getResultList()); } else { //使用指定的rusultHandler进行转换 handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // issue #228 (close resultsets) //调用resultset.close()关闭结果集 closeResultSet(rsw.getResultSet()); } }
不是嵌套结果集的都进入else部分:
4.1 创建默认的
DefaultResultHandler
,内部就是一个暂存结果集的集合:private final List<Object> list;
4.2 映射resultSet,进入如下方法(无resultMap嵌套的):
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { //创建结果上下文,暂时缓存结果对象 DefaultResultContext<Object> resultContext = new DefaultResultContext<>(); //1.根据分页信息,定位到指定的记录 skipRows(rsw.getResultSet(), rowBounds); //2.shouldProcessMoreRows判断是否需要映射后续的结果,实际还是翻页处理,避免超过limit /* private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) { //检测上下文的stop状态,并检测映射的行数是否达到了limit的上限 return !context.isStopped() && context.getResultCount() < rowBounds.getLimit(); } */ while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { //3.进一步完善resultMap信息,主要是处理鉴别器的信息 ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); //4.读取resultSet中的一行记录并进行映射,转化并返回目标对象 Object rowValue = getRowValue(rsw, discriminatedResultMap); //5.保存映射结果对象 storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } }
这里重点看对单行记录的映射:
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // 根据resultMap的type属性,实例化目标对象 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { //对目标对象进行封装得到metaObjcect,为后续的赋值操作做好准备 final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings;//取得是否使用构造函数初始化属性值 if (shouldApplyAutomaticMappings(resultMap, false)) {//是否使用自动映射 //一般情况下 autoMappingBehavior默认值为PARTIAL,对未明确指定映射规则的字段进行自动映射 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues; } // 映射resultMap中明确指定需要映射的列 foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; //如果没有一个映射成功的属性,则根据<returnInstanceForEmptyRow>的配置返回null或者结果对象 rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } return rowValue; }
4.2-1.1首先根据resultMap中配置的type,通过mybatis的反射模块相关组件反射创建目标对象
4.2-1.2.将对象封装为
MetaObject
4.2-1.3.对没有明确指明映射规则的属性进行自动映射
//对未明确指定映射规则的字段进行自动映射 private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { //获取resultSet中存在的,但是ResultMap中没有明确映射的列,填充至autoMapping中 List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); boolean foundValues = false; if (!autoMapping.isEmpty()) { //遍历autoMapping,通过自动匹配的方式为属性复制 for (UnMappedColumnAutoMapping mapping : autoMapping) { //通过typeHandler从resultset中拿值 final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) { // gcode issue #377, call setter on nulls (value is not 'found') //通过metaObject给属性赋值 metaObject.setValue(mapping.property, value); } } } return foundValues; }
4.2-1.4对指明规则的属性进行映射:
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { //从resultMap中获取明确需要转换的列名集合 final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); boolean foundValues = false; //获取ResultMapping集合 final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);//获得列名,注意前缀的处理 if (propertyMapping.getNestedResultMapId() != null) { // the user added a column attribute to a nested result map, ignore it //如果属性通过另外一个resultMap映射,则忽略 column = null; } if (propertyMapping.isCompositeResult()//如果是嵌套查询,column={prop1=col1,prop2=col2} || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))//基本类型映射 || propertyMapping.getResultSet() != null) {//嵌套查询的结果 //获得属性值 Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); // issue #541 make property optional //获得属性名称 final String property = propertyMapping.getProperty(); if (property == null) {//属性名为空跳出循环 continue; } else if (value == DEFERED) {//属性名为DEFERED,延迟加载的处理 foundValues = true; continue; } if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) { // gcode issue #377, call setter on nulls (value is not 'found') //通过metaObject为目标对象设置属性值 metaObject.setValue(property, value); } } } return foundValues; }
4.2-1.5.最后,如果所有属性都映射成功,则返回映射成功的目标对象,否则需根据configuration的
returnInstanceForEmptyRow
判断是返回null还是目标对象。4.2-2:拿到目标对象后,调用storeObject保存该对象。
private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException { if (parentMapping != null) {//如果是嵌套结果或嵌套查询,将对象保存至父对象 linkToParents(rs, parentMapping, rowValue); } else {//普通映射则把对象保存至resultHandler和resultContext callResultHandler(resultHandler, resultContext, rowValue); } } private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) { resultContext.nextResultObject(rowValue); ((ResultHandler<Object>) resultHandler).handleResult(resultContext); } //DefaultResultHandler#handleResult public void handleResult(ResultContext<? extends Object> context) { list.add(context.getResultObject()); }
最终将当期目标对象添加到了
DefaultResultHandler
中的list中。如果返回值是一个集合,则会继续4.2 的while循环,最终将所有对象添加到集合中。
-
将暂存在
DefaultResultHandler
的结果添加到最终的multipleResults
集合; -
resultset.close()
关闭结果集
OK到这处理单结果集的流程就结束了,将multipleResults
集合返回就拿到了查询的所需结果。
多结果集的不是重点暂不分析。
数据读写阶段涉及的设计模式
1.模板方法
抽象的BaseExecutor编写了查询的基本骨架,doQuery、doUpdate等具体的操作实现交给三个不同子类实现
2.装饰器模式
如果开启了二级缓存,则会创建CachingExecutor
,对Executor增强,增加缓存功能。
3.静态代理
RoutingStatementHandler,实现StatementHandler
接口,内部持有被代理的StatementHandler
对象delegate
,根据statementType
为delegate
创建不同的子类,接口的所有方法由delegate
调用。
另外,mybatis的插件实现,是利用了责任链模式。
本文地址:https://blog.csdn.net/weixin_43696529/article/details/107396049
上一篇: C#中使用MVC架构(四)
下一篇: js Math方法