1 扫描并解析mapper文件
1.1 引入mapper文件的四种方式
1.1.1 使用类路径
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
1.1.2 使用绝对url路径
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
1.1.3 使用Java类名
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
1.1.4 自动扫描包下所有mapper
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
接下来我们看看mybatis是如何处理这四种情况的。
1.2 解析mapper
public class XMLConfigBuilder extends BaseBuilder {
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
/**
* 遍历解析mapper节点
*/
for (XNode child : parent.getChildren()) {
/**
* 首先解析package节点
* <mappers>
* <package name="org.mybatis.builder"/>
* </mappers>
*/
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
/**
* 1.2.1 自动扫描包下所有类
*/
configuration.addMappers(mapperPackage);
} else {
/**
* <mappers>
* <mapper resource="UserMapper.xml" class="" url=""/>
* </mappers>
*/
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
/**
* 三个属性只能有一个是有值的
*/
if (resource != null && url == null && mapperClass == null) {
//1.2.3 使用resource
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
/**
* 解析mapper.xml
* 映射器比较复杂,调用XMLMapperBuilder
* 注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
*/
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
/**
* 开始解析
*/
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//1.2.3 使用url
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
//1.2.2 指定Java类名
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
}
1.2.1 自动扫描包下所有mapper
Configuration#addMappers()
public class Configuration {
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
}
public class MapperRegistry {
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
//扫描mapper类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
//遍历
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
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 {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//解析接口的注解
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
解析接口 MapperAnnotationBuilder#parse
public class MapperAnnotationBuilder {
/**
* 解析mapper对象
*/
public void parse() {
String resource = type.toString();
/**
* 如果之前解析了xml,这里就不会再解析
*/
if (!configuration.isResourceLoaded(resource)) {
//解析xml
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
/**
* 解析注解
*/
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
/**
* 解析注解
*/
parsePendingMethods();
}
//解析xml
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
//解析xml 这里先暂不展开 在解析另外三种方式时再聊这块
xmlParser.parse();
}
}
}
}
1.2.2 使用java类名
public class Configuration {
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
}
使用java类名的方式 属于 自动扫描包下所有映射器的特殊情况,这里就不重复了。
1.2.3 使用resource和使用绝对url路径的方式
使用resource和使用绝对url路径都是通过XMLMapperBuilder#parse解析的
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
//判断是否解析过
if (!configuration.isResourceLoaded(resource)) {
//解析mapper.xml文件
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
/**
* 绑定namespace里面的class对象
*/
bindMapperForNamespace();
}
/**
* 重新解析之前解析不了的节点
*/
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
2 解析mapper文件
/**
* 解析mapper文件里面的节点
* 拿到配置的配置项,最终封装成一个MapperStatement
* //配置mapper元素
* // <mapper namespace="org.mybatis.example.BlogMapper">
* // <select id="selectBlog" parameterType="int" resultType="Blog">
* // select * from Blog where id = #{id}
* // </select>
* // </mapper>
*
*/
private void configurationElement(XNode context) {
try {
/**
* 1、配置namespace
*/
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//2.1 解析cacheRef标签
cacheRefElement(context.evalNode("cache-ref"));
//2.2 解析cache标签
cacheElement(context.evalNode("cache"));
//2.3 解析parameterMap标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//2.4 解析resultMap标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
//2.5 解析sql标签
sqlElement(context.evalNodes("/mapper/sql"));
/**
* 2.6 解析select update insert 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);
}
}
2.1 解析cacheRef标签
cache(二级缓存)只对特定的Namespace使用,即每个namespace使用一个cache实例,如果要多个namespace使用同一个cache实例,则可以使用cache-ref来引用
/**
* <cache-ref namespace=""/>
* @param context
*/
private void cacheRefElement(XNode context) {
if (context != null) {
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
//解析cacheRef
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
CacheRefResolver#resolveCacheRef
public class CacheRefResolver {
private final MapperBuilderAssistant assistant;
private final String cacheRefNamespace;
public Cache resolveCacheRef() {
return assistant.useCacheRef(cacheRefNamespace);
}
}
MapperBuilderAssistant#useCacheRef
public class MapperBuilderAssistant extends BaseBuilder {
public Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true;
//根据namespace从configuration找将要引用的cache实例
Cache cache = configuration.getCache(namespace);
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
//为cache赋值
currentCache = cache;
unresolvedCacheRef = false;
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
}
2.2 解析cache标签
每个namesapce的二级缓存默认是关闭的,需要通过cache标签进行开启。
/**
* <cache type="" eviction="" readOnly="" size="" blocking="" flushInterval=""></cache>
* @param context
*/
private void cacheElement(XNode context) {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//创建cache
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
MapperBuilderAssistant#useNewCache
public class MapperBuilderAssistant extends BaseBuilder {
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
}
2.3 解析parameterMap标签
/**
* <parameterMap id="account" type="org.apache.learning.parameter_map.Account">
* <parameter property="id" javaType="int"/>
* <parameter property="name" javaType="string" jdbcType="VARCHAR"/>
* <parameter property="balance" javaType="double" jdbcType="DOUBLE"/>
* </parameterMap>
*
* @param list
*/
private void parameterMapElement(List<XNode> list) {
for (XNode parameterMapNode : list) {
String id = parameterMapNode.getStringAttribute("id");
String type = parameterMapNode.getStringAttribute("type");
Class<?> parameterClass = resolveClass(type);
List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
List<ParameterMapping> parameterMappings = new ArrayList<>();
for (XNode parameterNode : parameterNodes) {
String property = parameterNode.getStringAttribute("property");
String javaType = parameterNode.getStringAttribute("javaType");
String jdbcType = parameterNode.getStringAttribute("jdbcType");
String resultMap = parameterNode.getStringAttribute("resultMap");
String mode = parameterNode.getStringAttribute("mode");
String typeHandler = parameterNode.getStringAttribute("typeHandler");
Integer numericScale = parameterNode.getIntAttribute("numericScale");
ParameterMode modeEnum = resolveParameterMode(mode);
Class<?> javaTypeClass = resolveClass(javaType);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
parameterMappings.add(parameterMapping);
}
builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
}
}
2.4 解析resultMap标签
public class XMLMapperBuilder extends BaseBuilder {
/**
* <!-- 订单查询关联用户的resultMap将整个查询的结果映射到cn.itcast.mybatis.po.Orders中 -->
* <resultMap type="cn.itcast.mybatis.po.Orders" id="OrdersUserResultMap">
* <!-- 配置映射的订单信息 -->
* <!-- id:指定查询列中的唯 一标识,订单信息的中的唯 一标识,如果有多个列组成唯一标识,配置多个id ,column:订单信息的唯 一标识列 ,property:订单信息的唯 一标识 列所映射到Orders中哪个属性 -->
*
* <id column="id" property="id"/>
* <result column="user_id" property="userId"/>
* <result column="number" property="number"/>
* <result column="createtime" property="createtime"/>
* <result column="note" property=note/>
*
* <!-- 配置映射的关联的用户信息 -->
* <!-- association:用于映射关联查询单个对象的信息property:要将关联查询的用户信息映射到Orders中哪个属性 -->
*
* <association property="user" javaType="cn.itcast.mybatis.po.User">
* <!-- id:关联查询用户的唯 一标识
* column:指定唯 一标识用户信息的列
* javaType:映射到user的哪个属性-->
* <id column="user_id" property="id"/>
* <result column="username" property="username"/>
* <result column="sex" property="sex"/>
* <result column="address" property="address"/>
* </association>
* </resultMap>
* @param list
* @throws Exception
*/
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList(), null);
}
/**
* 处理 <resultMap> 节点, 将节点解析成 ResultMap 对象, 下面包含有 ResultMapping 对象组成的列表
* @param resultMapNode resultMap 节点
* @param additionalResultMappings 另外的 ResultMapping 列
* @return ResultMap 对象
* @throws Exception
*/
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 获取 ID , 默认值会拼装所有父节点的 id 或 value 或 property
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// 获取 type 属性, 表示结果集将被映射为 type 指定类型的对象
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 获取 extends 属性, 其表示结果集的继承
String extend = resultMapNode.getStringAttribute("extends");
// 自动映射属性。 将列名自动映射为属性
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 解析 type, 获取其类型
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
// 记录解析的结果
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
// 处理子节点
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
// 处理 constructor 节点
if ("constructor".equals(resultChild.getName())) {
// 解析构造函数元素,其下的没每一个子节点都会生产一个 ResultMapping 对象
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
// 处理 discriminator 节点
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// 处理其余节点, 如 id, result, assosation d等
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 创建 resultMapping 对象, 并添加到 resultMappings 中
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
// 创建 ResultMapResolver 对象, 该对象可以生成 ResultMap 对象
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
// 如果无法创建 ResultMap 对象, 则将该结果添加到 incompleteResultMaps 集合中
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
}
2.5 解析sql标签
/**
* <sql id="sql1">username,age</sql>
*
* <select id="getPerson" parameterType="int" >
* select
* <include refid="sql1"></include>
* from Person where id=#{id}
* </select>
* @param list
*/
private void sqlElement(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
2.6 解析select update insert delete标签
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
解析sql比较重要,下文会单独说明