文章目录
前言
SqlSession对象创建后,接下来是执行Mapper。执行Mapper的过程可以拆解为三步:注册Mapper接口与XML配置文件;注册MappedStatement对象;调用Mapper方法。
5.5 Mapper接口与XML配置文件的注册过程
Mapper接口用于定义执行SQL语句相关的方法,方法名一般和Mapper XML配置文件中的<select|update|insert|delete>标签的id属性相同,Mapper接口的完全限定名一般和Mapper XML配置文件的命名空间相同。例如:
public interface UserMapper {
List<User> selectAll();
@Select("select * from user where id = #{id, jdbcType=INTEGER}")
User selectById(@Param("id") Integer id);
}
<!--UserMapper.xml-->
<mapper namespace="com.star.mybatis.mapper.UserMapper">
<select id="selectAll" resultType="User">
select * from user
</select>
</mapper>
如上面的代码所示,UserMapper接口的selectAll()
方法名与UserMapper.xml中的<select>标签的id属性相同;UserMapper接口的完全限定名与<mapper>标签的namespace属性相同。
如果要将Mapper接口或Mapper XML配置文件注册到Configuration组件中,需要在主配置文件mybatis-config.xml中配置<mappers>标签。
<!--mybatis-config.xml-->
<mappers>
<!--方式一:通过指定XML文件的类路径来注册-->
<mapper resource="mapper/UserMapper.xml"/>
<!--方式二:通过指定XML文件的完全限定资源定位符来注册-->
<mapper url="file:///C:\workspace\mybatis_demo2\src\main\resources\mapper\UserMapper.xml"/>
<!--方式三:通过Mapper接口的类路径来注册-->
<mapper class="com.star.mybatis.mapper.UserMapper"/>
<!--方式四:通过Mapper接口所在包路径类注册-->
<package name="com.star.mybatis.mapper"/>
</mappers>
如上面的代码所示,<mappers>标签支持四种配置方式,其中方式一和方式二指定XML配置文件的相对和绝对路径,方式三和方式四指定Mapper接口的类路径和所在包路径。实际开发中,选择其一即可。
在【MyBatis3源码深度解析(十四)Configuration与SqlSession的创建过程 5.2 Configuration实例创建过程】中已经研究过,在mappersElement()
方法中,会对<mappers>标签的四种配置方式分别进行解析。
源码1:org.apache.ibatis.builder.xml.XMLConfigBuilder
private void mappersElement(XNode context) throws Exception {
if (context == null) {
return;
}
for (XNode child : context.getChildren()) {
if ("package".equals(child.getName())) {
// 方式四的处理
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// 方式一的处理 ...
ErrorContext.instance().resource(resource);
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
// 方式二的处理 ...
ErrorContext.instance().resource(url);
try (InputStream inputStream = Resources.getUrlAsStream(url)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,
configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
// 方式三的处理 ...
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
// throw ...
}
}
}
5.5.1 Mapper接口的注册过程
由 源码1 可知,如果是方式三或方式四这两种配置Mapper接口的方式,则会调用Configuration对象的addMappers()
方法。
源码2:org.apache.ibatis.session.Configuration
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// 用于处理指定包路径下的所有Mapper接口的注册
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
// 用于处理指定的Mapper接口的注册
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
由 源码2 可知,Configuration组件中组合了一个MapperRegistry对象,其addMappers(String)
方法用于处理指定包路径下的所有Mapper接口的注册,其addMapper(Class<T>)
方法用于处理指定的Mapper接口的注册。
先来看addMapper(Class<T>)
方法:
源码3:org.apache.ibatis.binding.MapperRegistry
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new ConcurrentHashMap<>();
public <T> void addMapper(Class<T> type) {
// 判断是否是Mapper接口
if (type.isInterface()) {
// 判断是否已经注册过
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 注册Mapper接口到knownMappers属性中
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
由 源码3 可知,MapperRegistry类中有一个knownMappers属性,用于注册Mapper接口对应的Class对象和MapperProxyFactory对象之间的关系。addMapper(Class<T>)
方法经过type.isInterface()
和hasMapper(type)
两个前置检查之后,然后为Mapper接口对应的Class对象创建一个MapperProxyFactory对象,并添加到knownMappers属性中。
再来看addMappers(String)
方法:
源码4:org.apache.ibatis.binding.MapperRegistry
public void addMappers(String packageName, Class<?> superType) {
// 借助ResolverUtil工具类获取指定包路径下的全部Mapper接口对应的Class对象
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
// 遍历Class对象集合,分别调用addMapper方法注册到MapperRegistry类的knownMappers属性中
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
由 源码4 可知,addMappers(String)
方法借助ResolverUtil工具类获取指定包路径下的全部Mapper接口对应的Class对象,然后遍历Class对象集合,分别调用addMapper(Class<T>)
方法将Class对象注册到MapperRegistry类的knownMappers属性中。
再深入resolverUtil.find()
方法的源码可以发现,ResolverUtil是使用VFS工具扫描指定包路径下所有Class文件,然后再交给addMapper(Class<T>)
方法进行筛选。
至此,Mapper接口的注册完毕,MapperRegistry类的knownMappers属性中保存了Mapper接口对应的Class对象与MapperProxyFactory对象之间的映射关系。
5.5.2 XML配置文件的注册过程
回到 源码1 可知,如果是方式一或方式二这两种配置Mapper XML配置文件的方式,则会先获取Mapper XML配置文件的输入流,再调用XMLMapperBuilder类的parse()
方法来完成注册。
源码5:org.apache.ibatis.builder.xml.XMLMapperBuilder
private final XPathParser parser;
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 调用XPathParser的evalNode方法获取mapper根节点对应的XNode对象
// 调用configurationElement方法进一步解析
configurationElement(parser.evalNode("/mapper"));
// 将资源路径添加到Configuration对象中
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
// ......
}
由 源码5 可知,parse()
方法首先调用XPathParser的evalNode()
方法获取<mapper>根节点对应的XNode对象,然后调用configurationElement()
方法进一步解析XNode对象。
源码6:org.apache.ibatis.builder.xml.XMLMapperBuilder
private void configurationElement(XNode context) {
try {
// 获取并设置命名空间
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref>标签
cacheRefElement(context.evalNode("cache-ref"));
// 解析<cache>标签
cacheElement(context.evalNode("cache"));
// 解析<parameterMap>标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析<resultMap>标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析<sql>标签
sqlElement(context.evalNodes("/mapper/sql"));
// 解析<select|insert|update|delete>标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} // catch ......
}
由 源码6 可知,configurationElement()
方法会对Mapper XML配置文件的所有标签进行解析,并封装成一个MappedStatement对象。
5.6 MappedStatement对象的注册过程
重点关注<select|insert|update|delete>标签的解析。由 源码6 可知,获取<select|insert|update|delete>标签节点对应的XNode对象后,调用buildStatementFromContext()
方法做进一步的解析。
源码7:org.apache.ibatis.builder.xml.XMLMapperBuilder
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// 创建XMLStatementBuilder对象
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context,
requiredDatabaseId);
try {
// 真正解析标签
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
由 源码7 可知,buildStatementFromContext()
方法会对所有的XNode对象进行遍历,为每一个XNode对象创建一个XMLStatementBuilder对象。然后调用该对象的parseStatementNode()
方法真正进行解析标签。
源码8:org.apache.ibatis.builder.xml.XMLStatementBuilder
public void parseStatementNode() {
// 获取id、databaseId属性并进行前置判断
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 根据标签名字得到SQL语句的类型
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 获取flushCache属性,如果是SELECT语句,则默认为false
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// 获取useCache属性,如果是SELECT语句,则默认为true
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
// 获取resultOrdered属性,默认为false
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 将<include>标签内容替换为<sql>标签定义的SQL片段
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 获取parameterType属性,并转换成对应的Class对象
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
// 获取lang属性,并转换成对应的LanguageDriver对象
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 解析<selectKey>标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 获取主键生成策略
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 通过LanguageDriver解析SQL内容,生成SqlSource对象
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 获取statementType属性,默认为PREPARED类型
StatementType statementType = StatementType
.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// 获取fetchSize、timeout、parameterMap属性
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
// 获取resultType属性,并转换为对应的Class对象
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
if (resultTypeClass == null && resultMap == null) {
resultTypeClass = MapperAnnotationBuilder.getMethodReturnType(builderAssistant.getCurrentNamespace(), id);
}
// 获取resultSetType属性,并转换为对应的ResultSetType对象
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
// 获取keyProperty、keyColumn、resultSets、affectData属性
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
boolean dirtySelect = context.getBooleanAttribute("affectData", Boolean.FALSE);
// 使用上面获取的所有属性构造一个MappedStatement对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
}
如 源码8 所示,parseStatementNode()
方法篇幅很长,但逻辑很清晰,主要做了以下几件事情:
(1)获取<select|insert|update|delete>标签的所有属性信息。
(2)将<include>标签引用的SQL片段替换为对应的<sql>标签中定义的内容。
(3)获取lang属性指定的LanguageDriver对象,通过该对象创建代表SQL资源的SqlSource对象。
(4)获取KeyGenerator对象。KeyGenerator的不同实例代表不同的主键生成策略。
(5)所有解析工作完成后,使用MapperBuilderAssistant对象的addMappedStatement()
方法创建一个MappedStatement对象。
源码9:org.apache.ibatis.builder.MapperBuilderAssistant
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
LanguageDriver lang, String resultSets, boolean dirtySelect) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
// 创建MappedStatement.Builder对象
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType)
.keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang)
.resultOrdered(resultOrdered).resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType)
.flushCacheRequired(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 创建MappedStatement对象
MappedStatement statement = statementBuilder.build();
// 注册到Configuration组件中
configuration.addMappedStatement(statement);
return statement;
}
由 源码9 可知,addMappedStatement()
方法会通过MappedStatement.Builder对象的build()
方法创建一个MappedStatement对象,最后注册到Configuration组件中。
至此,Mapper接口和Mapper XML配置文件的解析全部完成,保存这些信息的位置是Configuration组件中的mappedStatements属性和mapperRegistry属性。
借助Debug,可以查看到这两个属性保存的信息:
5.7 Mapper接口的动态代理对象的获取
Mapper接口和Mapper XML配置文件解析注册完成后,接下来是执行Mapper接口中定义的方法。
为了执行Mapper接口中定义的方法,首先需要调用SqlSession对象的getMapper()
方法获取一个Mapper接口的动态代理对象,然后通过代理对象调用Mapper方法。
代码如下:
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAll();
SqlSession对象的getMapper()
方法的源码如下:
源码10:org.apache.ibatis.session.defaults.DefaultSqlSession
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
源码11:org.apache.ibatis.session.Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
源码12:org.apache.ibatis.binding.MapperRegistry
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new ConcurrentHashMap<>();
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);
}
}
由 源码10-12 可知,SqlSession对象的getMapper()
方法最终会从MapperRegistry对象的knownMappers属性中,根据传入的Class对象取出一个MapperProxyFactory对象,并调用其newInstance()
方法创建一个实例并返回。
由类名可知,MapperProxyFactory是一个代理工厂类,其newInstance()
方法会创建一个代理对象实例。
源码13:org.apache.ibatis.binding.MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
由 源码13 可知,MapperProxyFactory对象的newInstance()
方法会创建一个MapperProxy对象,并调用Proxy类的newProxyInstance()
方法创建一个代理对象实例。
源码14:org.apache.ibatis.binding.MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
由 源码14 可知,MapperProxy类实现了InvocationHandler接口,使用的是JDK内置的动态代理。
通过以上分析可以知道,SqlSession对象的getMapper()
方法返回的是一个MapperProxy动态代理对象。
…
本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析