前言:
-
这个链接中有mybatis的各个对象说明,一定要结合看:https://blog.youkuaiyun.com/hancoder/article/details/111244516
-
个人源码解析地址:https://blog.youkuaiyun.com/hancoder/article/details/110152732
-
mybatis插件分析:https://blog.youkuaiyun.com/hancoder/article/details/110914534
-
本文主要按解析xml、获取session、执行sql等分为几个大章节
-
有的小章节前面加了【】,是代表面试比较常见的问题,比如@Param、分页和sql注入等问题
-
设计模式的Builder建造者模式、装饰者模式很重要。插件中也用到了代理模式和责任链模式
-
动态代理模式也得看会才能看这个,最起码invocationHandler和invoke知道怎么回事
一、xml文件解析
- XMLConfigBuilder是解析根标签Configuration的
- XMLMapperBuilder是解析根标签Mapper的
SqlSessionFactoryBuilder
- build()的方法是传入一个配置文件流参数,返回一个SqlSessionFactor实例对象。在这里就是传入configuration配置,得到一个SqlSessionFactory实例
// SqlSessionFactoryBuilder.java
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 开始创建sqlSessionFactory
// 1.我们最初调用的build
public SqlSessionFactory build(InputStream inputStream) {
//调用了重载方法
return build(inputStream, null, null);
}
// 2.调用的重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder是专门解析mybatis的配置文件的类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//这里又调用了一个重载方法Configuration。parser.parse()的返回值是Configuration对象
return build(parser.parse());//XMLConfigBuilder的parse()返回一个Configuration对象
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} //省略部分代码
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
总结:
两个Builder,
XMLConfigBuilder是parser
parser.parse()就是返回Builder包含的Configuration对象
sqlSessionFactoryBuilder.build(config)返回都是DefaultSqlSessionFactory对象。
XMLConfigBuilder
- XMLConfigBuilder中的属性configuration在父类BaseBuilder中
- 通过parse()方法返回一个Configuration实例
// XMLConfigBuilder
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());//BaseBuilder
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;// XML里的成员parser = new XPathParser(reader, true, props, new XMLMapperEntityResolver())
}
// 返回Configuration
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;//也就是说这个函数只能进来一次
parseConfiguration(parser.evalNode("/configuration"));//这里进去把config的属性注入好
return configuration;
}
mybatis-config.xml元素解析
解析mybatis.xml配置文件中的各个标签。
值得注意的是,先解析好properties,然后就可以把设置注入到configuration中了
//------XMLConfigBuilder---------------
private void parseConfiguration(XNode root) {
//Xnode是一个XML,properties还没注入
try {
// 解析properties
propertiesElement(root.evalNode("properties")); //issue #117 read properties first // 在这里把配置才拿出来
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));// 解析拦截器插件
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 //<environments> //函数传入的时候后root.evalNode("environments")整体已经把配置拿到了
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
上面解析的是mybatis.xml配置文件,值得注意的是在解析其中<mappers>标签的时候,会解析指定的Xxxmapper.xml文件
①properties配置解析
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="root"/>
<property name="password" value="123123"/>
</properties>
propertiesElement
解析properties
//---------------------
//先解析properties把属性设置好 // propertyConfigurer
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
// 配置文件的属性有url或者resource
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
//不能同时指定
throw new BuilderException("不能同时指定url和res");
}
if (resource != null) {
// 如果指定的是resource
// 得到一个Properties对象,然后放到configuration.variables这个Properties中
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
// 如果指定的是url
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
②解析数据源environment、DataSource
会把解析出来的内容放到configuration 属性中,然后用属性解析数据源
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
environmentsElement
//---------------------
// 解析完properties后就能${}拿到了
private void environmentsElement(XNode context) {
//<environments>
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 循环开发环境、测试环境等
for (XNode child : context.getChildren()) {
// 每个<environment id="development">
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 事物 mybatis有两种:JDBC 和 MANAGED, 配置为JDBC则直接使用JDBC的事务,配置为MANAGED则是将事务托管给容器
// <transactionManager type="JDBC"/>
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// <dataSource type="POOLED">
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));//看下面函数
// 得到数据源
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 设置environment给configuration
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
//---------------------
// 数据源 <dataSource type="POOLED"> <property name="url" value="${url}"/>
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
// 解析多个property标签,数据源的${}肯定从这里面替换
Properties props = context.getChildrenAsProperties();// 获得属性
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();// 获得class对象,然后new
factory.setProperties(props);//把属性设置到数据源
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
// 解析多个property标签
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
/**
* 解析typeAliases 节点
* <typeAliases>
* <!--<package name="com.lpf.entity"></package>-->
* <typeAlias alias="UserEntity" type="com.lpf.entity.User"/>
* </typeAliases>
* @param parent
*/
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//如果子节点是package, 那么就获取package节点的name属性, mybatis会扫描指定的package
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
//TypeAliasRegistry 负责管理别名, 这儿就是通过TypeAliasRegistry 进行别名注册
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
//如果子节点是typeAlias节点,那么就获取alias属性和type的属性值
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
typeHandlerElement
类型转换器的用处:
- 在预处理语句(PreparedStatement)中设置参数
- 从结果集中取出一个值时转换成 Java 类型
注册到typeHandlerRegistry中
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//子节点为package时,获取其name属性的值,然后自动扫描package下的自定义typeHandler
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
//子节点为typeHandler时, 可以指定javaType属性, 也可以指定jdbcType, 也可两者都指定
//javaType 是指定java类型
//jdbcType 是指定jdbc类型(数据库类型: 如varchar)
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
//handler就是我们配置的typeHandler
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
//JdbcType是一个枚举类型,resolveJdbcType方法是在获取枚举类型的值
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
//注册typeHandler, typeHandler通过TypeHandlerRegistry这个类管理
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
pluginElement
解析plugins标签
- StatementHandler
- ParameterHandler
- ResultSetHandler
- Executor
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
// 我们在定义一个interceptor的时候,需要去实现Interceptor
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
// 向configuration对象中注册拦截器
configuration.addInterceptor(interceptorInstance);
}
}
}
PropertyParser
PropertyParser这个类中包含一个内部私有的静态类VariableTokenHandler。VariableTokenHandler实现了TokenHandler接口,包含了一个Properties类型的属性,在初始化这个类时需指定该属性的值。VariableTokenHandler类对handleToken函数的具体实现如下:
https://blog.youkuaiyun.com/u014213453/article/details/40399475
③mapper元素解析
获取sql、加载接口
配置要注册的mapper有四种方式(优先级由高到低)
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
addMapper
mapperElement
// XMLConfigBuilder.java
// 解析configuration标签中的mapper子标签
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//mappers/ mappers/<package name="">
// ① 找包下的接口,然后找同名的xml //这时候没有MSC
if ("package".equals(child.getName())) {
// 拿到包名
String mapperPackage = child.getStringAttribute("name");
// 得到包下所有的类,依次调用addMapper(mapperClass);,而addMapper(mapperClass)第一句就是判断这个类是不是接口,不是直接的话直接跳过
configuration.addMappers(mapperPackage);
} else {
// mappers/<mapper resource="">
// 获取resource/url/class属性
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 上面3个对应下面3个
// ② resource属性不为空,如XxxMapper.xml
if (resource != null && url == null && mapperClass == null) {
// resource不为空
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 解析mapper的xml // 传入了configuration参数,那么肯定就设置到configuration里面了
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
// ③url,和上面的逻辑一样,就是解析xml而已
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
// ④mapperClass
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 这个方法和上面resource里面最终依次调用的方法一样,就是对接口进行操作
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("不是 url, resource or class报错");
}
}
}
}
}
// 上面解析mapper标签主要内容为把标签的内容放到XMLMapperBuilder实例中,然后调用parse方法,parse方法的作用是解析另一个xml文件
在上面我们知道:
- package和class就是扫描接口而已
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);,然后调用parser.parse();- config是MapperRegistry 类持有的私有属性
- MapperRegistry类还有一个属性是knownMappers ,已经加载过的mapper集合,是从接口类到MapperProxyFactory的映射map,在addMapper(接口.class)的时候,第一句就是先knownMappers .put,告诉这个接口加载过了,即每个接口只能加载一次,否则报错
- 而对于xml:
好吧我们先不管class的问题了,先看看如何把xml的内容解析好然后再调用addMapper了
上面解析mapper标签主要内容为把标签的内容放到XMLMapperBuilder实例中,然后调用parse方法,parse方法的作用是解析另一个xml文件(XxxMapper.xml)
addMappers
MapperXml解析好后,我们接着刚才的addMapper分析
[①]addMapper(包)
// 如果<mappers>标签里指定的是接口包路径的话
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
//MapperRegistry
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
// 遍历包下的接口
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
[②]addMapper(接口)与MapperAnnotationBuilder
public <T> void addMapper(Class<T> type) {
// 如果是接口
if (type.isInterface()) {
// 有没有该类型的mapper(用接口全类名作为key查),如果有则报错,每个接口只能解析一遍
if (hasMapper(type)) {
throw new BindingException("MapperRegistry中已经有这个类型了,不要重复解析。解析完的接口都会让MapperRegistry知道");
}
boolean loadCompleted = false;
try {
// 没有被解析过该接口,开始解析该接口,放入已解析的map中,防止后面重复解析
knownMappers.put(type, new MapperProxyFactory<T>(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.
// 比如解析接口方法上的@Select(slect * from a)
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
// 如果未完成解析,移除key
knownMappers.remove(type);
}
}
}
}
虽然还没有学习解析xml,但还是想在这里加几句方便复习。在xml阶段底层还是会走到addMapper(接口)这里。hasMapper(type)这个判断也能倒推出xml的解析并没有解析完,只是把解析的信息放到configuration中,然后调用addMapper(接口),直到解析接口的时候拿到xml的信息,再加载注解的信息。所以注解的信息优先级高。
MapperAnnotationBuilder就是上面这个过程
[③]addMapper(xml)与XMLMapperBuilder
实际上是没有addMapper(xml)这个东西的,只有解析UserMapper.xml的过程
那我们看看如果是xml的话怎么办吧
倒退到解析xml文件
思想:解析xml的<mapper>标签元素,放到对应的接口
private void mapperElement(XNode parent) throws Exception {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
// 得到包下所有的类,依次调用addMapper(mapperClass);
configuration.addMappers(mapperPackage);
} else {
// mappers/<mapper resource="">
if (resource != null && url == null && mapperClass == null) {
// resource不为空
InputStream inputStream = Resources.getResourceAsStream(resource);
// 解析mapper的xml // 传入了configuration参数,那么肯定就设置到configuration里面了
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration,
resource, configuration.getSqlFragments());
mapperParser.parse();
// ③url,和上面的逻辑一样,就是解析xml而已
//XMLConfigBuilder.java
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
//XMLMapperBuilder.java
public void parse() {
// resourse:"mapper/UserMapper.xml"
if (!configuration.isResourceLoaded(resource)) {
//从这里看出解析mapper.xml时会看有没有被解析过,如果解析过了config会知道,config持有一个Set<String>
// 1
/** ★★★解析mapper标签,即解析sql语句,将sql语句的所有信息封装成MappedStatement对象,然后存储在configuration对象中。
*/
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);// 解析后加到config的Set<String>中
// 2
/* 绑定mapper到接口类,里面有addMapper */
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
③.1
解析sql加到configuration中,解析XxxMapper.xml中,<mapper>标签下的子标签,如<select>,当然还包括<resultMap>等
// XMLMapperBuilder.java
// 解析mapperXML里的configuration
private void configurationElement(XNode context) {
//mapper标签里的内容
try {
String namespace = context.getStringAttribute("namespace");// 属性
// 设置当前解析的接口。从这里也可以推测builderAssistant对象是全局单例的
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));//Ele才是子标签
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// ★★★解析CRUD标签 // 参数类型为List<XNode> list
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//select子标签
} catch (Exception e) {
throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
}
}
③.1.1 解析mapper.xml中的每个sql
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// 我们只有一个select标签,所以list.size==1,如果还有别的标签就++
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 去parse标签内容
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
【解析标签】③.1.1.1 parseStatementNode
解析mapperxml中的sql标签
public void parseStatementNode() {
// 此时的id还只是方法名,如"selectUser"
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;
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);
String nodeName = context.getNode().getNodeName();
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
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes,
// in case if IncompleteElementException (issue #291)
List<XNode> selectKeyNodes = context.evalNodes("selectKey");
if (configuration.getDatabaseId() != null) {
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
}
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
// 动态sql和静态sql # 动态sql解析完问号都没有转
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 省略一些解析mapper文件里属性的内容
。。。;
// ★★★重点代码 // id:"selectUser" resource:"mapper/UserMapper.xml"
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
③.1.1.1.1
addMappedStatement
public MappedStatement addMappedStatement(
String id,//"selectUser",一会就拼接上类名了
SqlSource sqlSource,
StatementType statementType,// STATEMENT, PREPARED, CALLABLE
SqlCommandType sqlCommandType,//UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
// 把selectUser加上全类名 "mapper.UserMapper.selectUser"
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)// mapper/UserMapper.xml
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
//为null
statementBuilder.parameterMap(statementParameterMap);
}
// MappedStatement解析,即sql的封装,只是封装了MappedStatement
MappedStatement statement = statementBuilder.build();
// 放到configuration.mappedStatements这个map中,key为全类名+方法mapper.UserMapper.select,value为MappedStatement对象 // 一下放了两个key,全类名+方法、方法两个,总之出来的时候map.size已经是2了。可以通过ms.getId()得到key
configuration.addMappedStatement(statement);
return statement;
}
到这里解析完了xml到configuration.mappedStatements这个map中
③.2 bindMapperForNamespace():
接着往下执行XMLMapperBuilder.parse()
实际上就是解析该sql对应的class,并把该class放到configuration.mapperRegistry中。实际上mybatis的所有配置信息以及运行时的配置参数全部都保存在configuration对象中。mapperRegistry这个东西我们之前提过,是一个map,管理着接口到MapperProxyFactory的映射
// XMLMapperBuilder.java
private void bindMapperForNamespace() {
// 我们是在解析每个mapper,设置过当前解析的接口是哪个,所以能拿到接口的namespace
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 获取接口类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
// 如果接口类型不为空
if (boundType != null) {
// 如果mapperRegistry中没有该接口类型,mapperRegistry和configuration都是全局唯一的,知道接口有没有被解析过
if (!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
// 前面我们也执行过一个同样的方法,只不过现在这个加了个namespace前缀放到这个set,现在这个set中有两个,"mapper/UserMapper.xml"和" configuration.addLoadedResource("namespace:" + namespace);
configuration.addLoadedResource("namespace:" + namespace);
// 又到了addMapper(接口)的时候了,config.addMapper内部就是调用的是mapperRegistry.addMapper(type);// 不管是config还是mapperRegistry都是唯一的,addMapper互相"重载"
configuration.addMapper(boundType);
}
}
}
}
还有一个小疑问:xml解析后怎么设置到configuration中的,让configuration知道xml的解析信息
分析了一下,原来是并没有把解析出来的xml信息和mapper接口关联,而是xml信息仅仅和configuration关联
然后直接去调用config.addMapper(接口)
[④]殊途同归
通过上面分析,不管是接口还是包还是xml,最终都是调用addMapper(接口),只不过包的话是挨个调用,xml的话是先封装xml的信息放到configuration中,addMapper时候再拿出来
回忆addMapper(接口类)
// MapperRegistry.java
public <T> void addMapper(Class<T> type) {
// 如果是接口
if (type.isInterface()) {
// knownMappers<Class,MapperProxyFactory>中有没有该类型的mapper(用接口全类名作为key查),如果有则报错,每个接口只能解析一遍
if (hasMapper(type)) {
throw new BindingException("MapperRegistry中已经有这个类型了,不要重复解析。解析完的接口都会让MapperRegistry知道");
}
boolean loadCompleted = false;
try {
// 没有被解析过该接口,开始解析该接口,put(接口,创建代理工厂),防止后面重复解析
knownMappers.put(type, new MapperProxyFactory<T>(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.
// 比如解析接口方法上的@Select(slect * from a)
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
好吧,就是new了个MapperAnnotationBuilder,然后parse
MapperAnnotationBuilder
下面的parse是addMapper的parse,即对接口的parse,把xml的信息和注解信息都放到接口中
接下来依次解析xml和注解形式的sql。注解会覆盖xml的
// MapperAnnotationBuilder.java
public void parse() {
// type是接口的名字 ,接口的toString方法是形如"interface mapper.UserMapper"
String resource = type.toString();
// 判断接口是否被加载过
if (!configuration.isResourceLoaded(resource)) {
// 如果这个接口关联着之前的xml解析出来的内容,那么添加一下 // 如果我们在<mappers>标签里指定的是class,那么进入到这里去加载对于的xml,而resource的方式我们已经加载过了,所以进去又跳出来了
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
// 获取接口中的方法
Method[] methods = type.getMethods();
// 遍历接口当前的方法看有没有注解 // 理论上接口上的注解会覆盖xml里的,但是实际上是报错
for (Method method : methods) {
try {
// 解析注解,没有注解又跳出来了
parseStatement(method);//解析xml的时候把sql语句翻译成MappedStatement对象,加到configuration全局对象中//也就是放到configuration.mappedStatement这个StrictMap中(继承hashmap),这个map的特点是第二个put时直接抛异常,也就是避免了xml和注解方式的重复
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
//
private void loadXmlResource() {
// config和MapperRegistry是全局唯一的,他们知道xml是否被解析过
// 加载一下,而对于resource方式的,在bindmapper阶段,我们已经加过了,所以不进if
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 拼凑接口对应的xml地址
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
// 看有没有xml文件
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
// 是否存在xml一个文件
if (inputStream != null) {
// 又是一个XML Builder,解析配置文件
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(),
xmlResource, //
configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
@Select
在刚才的解析中,我们说了如果是xml没有相应注解的话直接跳出来了,如果有注解的话会进入parseStatement,他的最

本文围绕MyBatis展开,详细解析了xml文件解析过程,包括配置、数据源、mapper元素等。阐述了创建sqlSession、代理及查询的流程,还介绍了插件与拦截器、缓存问题。同时讲解了MyBatis与Spring的整合方式,分析了sql注入、参数处理和事务等内容,梳理了SqlSession的核心组件。
最低0.47元/天 解锁文章
4798





