映射器是MyBatis中最核心的组件之一,他的作用是告诉MyBatis 到哪里去找SQL语句。在MyBatis 3之前,只支持xml映射器,即:所有的SQL语句都必须在xml文件中配置。而从MyBatis 3开始,还支持接口映射器,这种映射器方式允许以Java代码的方式注解定义SQL语句,非常简洁
前言
从XMLConfigBuilder
的parseConfiguration
方法中的mapperElement(root.evalNode("mappers"));
代码片段开始
public class XMLConfigBuilder extends BaseBuilder {
//mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//package标签解析
if ("package".equals(child.getName())) {
//获取package标签属性name名字
String mapperPackage = child.getStringAttribute("name");
// 使用注解方式解析,这是sql映射处理,后面单独写篇文章说明
configuration.addMappers(mapperPackage);
}
//非package标签解析
//resource,url,class属性3者只能存在一个,否则全都不解析
else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// resource属性存在,url和class属性不存在,使用xml方式解析
ErrorContext.instance().resource(resource);
//获取资源文件
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//使用xml方式解析SQL,,这是sql映射处理,后面单独写篇文章说明
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// url属性存在,resource和class属性不存在,使用xml方式解析
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
//使用xml方式解析SQL,,这是sql映射处理,后面单独写篇文章说明
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
class属性存在,resource和url属性不存在,使用注解方式方式解析
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 使用注解方式解析SQL,这是sql映射处理,后面单独写篇文章说明
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("映射器元素只能指定url、资源或类,但不能指定多个.");
}
}
}
}
}
mapper
节点的属性,resource
,url
,class
接口3者只能存在一个,否则全都不解析。
一、mapper属性class映射器逻辑
class映射器逻辑如果使用xml方式解析,但是前提是其xml名字必须和其接口文件在同一路径下其名字相同。如果使用注解方式解析sql,那么可以直接使用@Select等注解
<mappers>
<mapper class="com.clyu.mapper.PersonMapper"/>
</mappers>
1.1 案例
方式1 : 使用xml方式,其xml和接口在同一路径下
public interface PersonMapper {
Person getOne(String id);
}
方式 2 : 使用注解方式,没有xml
public interface PersonMapper {
@Select("select * from person where id = #{id}")
Person getOne( String id);
}
1.2 源码分析
分析从这里开始
configuration.addMapper(mapperInterface);
,其底层是MapperRegistry
的addMapper(Class<T> type)
方法
public class MapperRegistry {
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));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//在从这里进入
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
public class MapperAnnotationBuilder {
public void parse() {
String resource = type.toString();
//如果之前解析了xml 这里就不会在解析了
if (!configuration.isResourceLoaded(resource)) {
//获取同路径下同名的xml文件,如果有使用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();
}
}
二、mapper属性resource映射器逻辑
resource映射器逻辑是提高流获取映射文件信息,
其xml位置可以自定义
其也支持xml和注解方式,但是其在使用注解方式时,必须要有xml
<mappers>
<mapper resource="mapper/PersonMapper.xml"/>
</mappers>
2.1 案例
方式1 : 使用xml方式,其xml位置可以自定义
<select id="getOne" resultType="com.clyu.bean.Person">
select * from person where id = #{id}
</select>
public interface PersonMapper {
Person getOne( String id);
}
方式2 : 使用注解方式,必须有个xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.clyu.mapper.PersonMapper">
</mapper>
public interface PersonMapper {
@Select("select * from person where id = #{id}")
Person getOne( String id);
}
2.2 源码分析
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//解析xml文件,拿到里面配置的配置项 最终封装成一个MapperedStatemanet
configurationElement(parser.evalNode("/mapper"));
//放到已解析资源池中
configuration.addLoadedResource(resource);
//解析绑定Namespace里面的Class对象
bindMapperForNamespace();
}
//重新解析之前解析不了的节点
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
结论:resource映射器逻辑是提高流获取映射文件信息,其只支持XML解析
三、mapper属性url映射器逻辑
url映射器逻辑是提高流获取映射文件信息,其只支持XML解析,
其xml位置可以自定义
resource映射器一般是获取classPath下的xml,而url映射器一般是获取磁盘或者网络资源上的xml文件
<mappers>
<mapper url="file:Users/yuchaolei/Desktop/learnning/log/src/main/resources/person.xml"/>
</mappers>
3.1 案例
方式1 : 纯xml方式
<select id="getOne" resultType="com.clyu.bean.Person">
select * from person where id = #{id}
</select>
public interface PersonMapper {
Person getOne( String id);
}
方式2 : 使用注解方式,必须有个xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.clyu.mapper.PersonMapper">
</mapper>
public interface PersonMapper {
@Select("select * from person where id = #{id}")
Person getOne( String id);
}
3.2 源码
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//绑定Namespace里面的Class对象
bindMapperForNamespace();
}
//重新解析之前解析不了的节点
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
四、package映射器逻辑
class映射器的批量操作就是package映射器
<mappers>
<package name="com.clyu.mapper"/>
</mappers>
4.1 案例
方式1 : 使用xml方式,其xml和接口在同一路径下
public interface PersonMapper {
Person getOne(String id);
}
方式 2 : 使用注解方式,没有xml
public interface PersonMapper {
@Select("select * from person where id = #{id}")
Person getOne( String id);
}
4.2 源码
public class MapperRegistry {
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
//获取当前包下的所有类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
//遍历他们
for (Class<?> mapperClass : mapperSet) {
//其底层逻辑和class映射器逻辑一样
addMapper(mapperClass);
}
}
}
看了这几个映射器的源码,其实都大同小异,其都是为了解析mapper接口和其对于的xml(如果有的话),只不过class和package是直接获取mapper接口的位置,而resource和url映射器是通过xml的namespace属性获取mapper接口的位置。这也是为什么resource和url映射器在使用注解方式时,必须有xml
五、@Mapper注解作用
5.1 纯mybatis环境
mybatis版本3.5.3
<mappers>
<mapper class="com.clyu.mapper.PersonMapper"/>
</mappers>
public interface PersonMapper {
@Select("select * from person where id = #{id}")
Person getOne( String id);
}
测试输出
DEBUG [main] - ==> Preparing: select * from person where id = ?
DEBUG [main] - ==> Parameters: 1(String)
DEBUG [main] - <== Total: 1
如果我们去掉配置文件中的 <mappers><mapper class="com.clyu.mapper.PersonMapper"/> </mappers>
,PersonMapper接口修改如下
@Mapper
public interface PersonMapper {
@Select("select * from person where id = #{id}")
Person getOne( String id);
}
测试输出
Exception in thread "main" org.apache.ibatis.binding.BindingException:
Type interface com.clyu.mapper.PersonMapper is not known to the MapperRegistry.
结论:在纯Mybatis项目中@Mapper不能开启注解模式,如果想开启Mybatis注解方式,就必须在Mybatis配置文件中配置映射器,在纯Mybatis项目中他的作用仅是标记MyBatis映射器的接口
注意:在SSM项目中,开启注解模式也与是否标注@Mapper注解无关