Mybatis——Mapper解析
Mapper的注册入口在Configuration的addMapper方法中,其会调用MapperRegistry的addMapper方法。
Mapper的注册过程分为两个步骤:
1.创建MapperProxyFactory,将其与mapper的class进行映射
2.解析mapper对应xml文件和其方法上的注解,生成MappedStatement。
public class MapperRegistry {
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
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 {
//1.创建MapperProxyFactory,将其与mapper的class进行映射
knownMappers.put(type, new MapperProxyFactory<T>(type));
2.解析mapper对应xml文件和其方法上的注解,生成MappedStatement。
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
MapperAnnotationBuilder解析过程
MapperAnnotationBuilder的解析过程分为两个步骤:
1.解析xml文件
2.解析mapper方法上的注解
public void parse() {
//校验mapper是否已经解析
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//1.解析xml文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
//2.解析mapper方法上的注解
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));
}
}
}
//3.重新解析由于首次解析因为父元素未解析导致解析的方法
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";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
xml文件的路径为:String xmlResource = type.getName().replace('.', '/') + ".xml";mapper的全路径,并将后缀改为xml,因为要求xml文件与mapper class文件位于相同的路径下。
xmlParser.parse()解析流程:
public void parse() {
//mapper节点解析
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
//解析由于父ResultMap未解析的ResultMap
parsePendingResultMaps();
parsePendingCacheRefs();
//解析由于依赖元素未解析的Insert/update/delete/select节点
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析parameterMap节点为ParameterMap,存放于configuration的parameterMaps中
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap节点为ResultMap,存放于configuration的ResultMaps中
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql片段,存放在configuration的sqlFragments中
sqlElement(context.evalNodes("/mapper/sql"));
//解析select|insert|update|delete节点生成MappedStatement,存放在configuration的mappedStatements中
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);
}
}
method注解解析
void parseStatement(Method method) {
//获取parameterType,如果有多个参数为ParamMap,单个参数为参数类型
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
//解析@Select|Update|Delete|Insert中的value值,或者@SelectProvider|@UpdateProvider|@DeleteProvider|@InsertProvider的方法,生成SqlSource
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
//解析@Options
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
//解析@SelectKey生成KeyGenerator
KeyGenerator keyGenerator;
String keyProperty = "id";
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();
}
//解析@ResultMap获取ResultMapId,或者解析@Resutls生成ResultMap
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {//解析@ResultMap获取ResultMapId
String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
} else if (isSelect) {//解析@Resutls生成ResultMap
resultMapId = parseResultMap(method);
}
//根据上面解析得到的ResultMap、ParamterType、SqlSource等生成MapperStatement
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
ParameterMap解析
ParameterMap结构:

ParameterMap映射节点:
<parameterMap id="BaseParameterMap" type="com.example.demo.User">
<parameter property="id" javaType="long" jdbcType="BIGINT"/>
<parameter property="username" javaType="string" jdbcType="VARCHAR"/>
<parameter property="password" javaType="string" jdbcType="VARCHAR"/>
</parameterMap>
parameterMap对应上面xml配置节点,该节点转换的parameterMap会注册到configuration中。解析逻辑在org.apache.ibatis.builder.xml.XMLMapperBuilder#parameterMapElement中。
此外还有select|insert|update|delete的parameter节点的parameterType属性,method注解的参数都是转换为parameterMap,但是只有id和type属性,且不会注册到configuration中,只会存放于mappedStatement中。生成逻辑位于org.apache.ibatis.builder.MapperBuilderAssistant#getStatementResultMaps中
ResultMap解析
resultMap映射节点:
xml
<resultMap id="BaseResultMap" type="com.example.demo.User">
<id column="id" property="id" />
<result column="username" property="username" />
<result column="password" property="password" />
</resultMap>
注解
@Results(id = "BaseResultMap2",
value = {
@Result(property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "password", column = "password"),
}
)
xml解析逻辑位于org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElement(org.apache.ibatis.parsing.XNode, java.util.List<org.apache.ibatis.mapping.ResultMapping>)
注解解析逻辑位于org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseResultMap
解析结果会注册到Configuration的resultMaps中
SqlSource解析
1.RawSqlSource:纯sql语句,不包含动态判断节点,例如:
select * from user where id = #{id}
创建过程如下为:
@Test
public void testRawSqlSource(){
RawSqlSource rawSqlSource = new RawSqlSource(new Configuration(), "select * from user where id = #{id}", Long.class);
System.out.println(rawSqlSource.getBoundSql(1L).getSql());
}

2.DynamicSqlSource:动态sql语句,例如:
select * from user where
<if test="id != null">
id = #{id}
</if>
创建过程如下为:
@Test
public void testDynamicSqlSource(){
List<SqlNode> contents = new ArrayList<>();
contents.add(new StaticTextSqlNode("select * from user where 1=1"));
contents.add(new IfSqlNode(new StaticTextSqlNode("and id = #{id}"), "id != null"));
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(new Configuration(), mixedSqlNode);
BoundSql boundSql = dynamicSqlSource.getBoundSql(null);
System.out.println(boundSql.getSql());
MapperMethod.ParamMap<Object> objectParamMap = new MapperMethod.ParamMap<>();
objectParamMap.put("id", 1);
System.out.println(dynamicSqlSource.getBoundSql(objectParamMap).getSql());
}
3.ProviderSqlSource:@SelectProvider|@UpdateProvider|@DeleteProvider|@InsertProvider修饰注解方法,例如:
@Results(id = "BaseResultMap2",
value = {
@Result(property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "password", column = "password"),
}
)
@SelectProvider(type = UserSelectProvider.class, method = "selectById")
User selectById2(@Param("id") Long id);
public class UserSelectProvider{
public String selectById(){
return new SQL().SELECT("username", "password")
.FROM("user")
.WHERE("id = #{id}").toString();
}
}
创建过程如下:
@Test
public void testProviderSqlSource() {
Method[] methods = UserMapper.class.getMethods();
for (Method method : methods) {
SelectProvider selectProvider = method.getAnnotation(SelectProvider.class);
if(selectProvider != null) {
ProviderSqlSource providerSqlSource = new ProviderSqlSource(new Configuration(), selectProvider, UserMapper.class, method);
System.out.println(providerSqlSource.getBoundSql(null).getSql());
}
}
}
4.StaticSqlSource:静态sqlSource,内部String类型的sql语句,上述3种最终都会转换为该SqlSource
解析逻辑位于org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
MappedStatement解析
mappedStatement映射节点:
xml
<select id="selectById" resultType="com.example.demo.User" parameterType="long"> select * from user where id = #{id};
</select>
注解
@Results(id = "BaseResultMap2",
value = {
@Result(property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "password", column = "password"),
}
)
@Select("select * from user")
@Options(fetchSize = 10)
User selectById2(@Param("id") Long id);
mappedStatement的主要字段:
1.sqlCommandType:由使用的标签Select|Insert|Update|Delete等决定
2.parameterMap:传入参数,由属性parameterType|parameterMap决定
3.resultMaps:返回结果集,由属性resultMap|resultType决定
4.sqlSource:标签包围的sql节点,即:select * from user where id = #{id}
xml解析逻辑位于org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
注解解析逻辑位于org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement
整体创建一个MappedStatement如下:
<select id="selectById" resultType="com.example.demo.User" parameterType="long"> select * from user where id = #{id};
</select>
@Test
public void testMappedStatement() {
List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
ParameterMap parameterMap = new ParameterMap.Builder(
configuration,
"selectById-Inline",
Long.class,
parameterMappings).build();
ResultMap inlineResultMap = new ResultMap.Builder(
configuration,
"selectById-Inline",
User.class,
new ArrayList<ResultMapping>(),
null).build();
RawSqlSource rawSqlSource = new RawSqlSource(configuration, "select * from user where id = #{id}", Long.class);
MappedStatement.Builder builder = new MappedStatement
.Builder(configuration, "selectById", rawSqlSource, SqlCommandType.SELECT);
builder.parameterMap(parameterMap);
builder.resultMaps(Collections.singletonList(inlineResultMap));
MappedStatement mappedStatement = builder.build();
configuration.addMappedStatement(mappedStatement);
SqlSession sqlSession = sqlSessionFactory.openSession();
System.out.println(sqlSession.selectOne("selectById", 18L));
}
Mybatis——Mapper解析的更多相关文章
- mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)
目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ...
- MyBatis mapper文件中的变量引用方式#{}与${}的差别
MyBatis mapper文件中的变量引用方式#{}与${}的差别 #{},和 ${}传参的区别如下:使用#传入参数是,sql语句解析是会加上"",当成字符串来解析,这样相比于$ ...
- mybatis mapper namespace
http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html#insert_update_and_delete org.apache.ibatis.excep ...
- XML CDATA(Mybatis mapper and XML)
Tip:must be followed by either attribute specifications, ">" or "/>". 所有 X ...
- [DB][mybatis]MyBatis mapper文件引用变量#{}与${}差异
MyBatis mapper文件引用变量#{}与${}差异 默认,使用#{}语法,MyBatis会产生PreparedStatement中.而且安全的设置PreparedStatement參数,这个过 ...
- Mybatis Mapper接口是如何找到实现类的-源码分析
KeyWords: Mybatis 原理,源码,Mybatis Mapper 接口实现类,代理模式,动态代理,Java动态代理,Proxy.newProxyInstance,Mapper 映射,Map ...
- 基于注解的Mybatis mapper 接口注意事项
基于注解的Mybatis mapper 接口功能没有mapper xml配置文件丰富,并且动态sql语句的灵活性不能和xml配置相比. 这里仅仅说一下基于注解的动态sql注意事项: Mybatis提供 ...
- ][mybatis]MyBatis mapper文件中的变量引用方式#{}与${}的差别
转自https://blog.csdn.net/szwangdf/article/details/26714603 MyBatis mapper文件中的变量引用方式#{}与${}的差别 默认情况下,使 ...
- Mybatis的解析和运行原理
Mybatis的解析和运行原理 Mybatis的运行过程大致分为两大步:第一步,读取配置文件缓存到Configuration对象,用以创建 SqlSessionFactory:第二步,SqlSessi ...
随机推荐
- 10、一个action中处理多个方法的调用第一种方法动态调用
我们新建一个用户的action package com.weiyuan.test; import com.opensymphony.xwork2.ActionSupport; /** * * 这里不用 ...
- 【Spring】AOP的代理默认是Jdk还是Cglib?
菜瓜:你觉得AOP是啥 水稻:我觉得吧,AOP是对OOP的补充.通常情况下,OOP代码专注功能的实现,所谓面向切面编程,大多数时候是对某一类对象的方法或者功能进行增强或者抽象 菜瓜:我看你这个理解就挺 ...
- CSS——文本超出隐藏显示省略号
文本超出隐藏显示省略号 1.单行文本的溢出显示省略号 overflow: hidden; text-overflow:ellipsis; white-space: nowrap; // overflo ...
- 作为一个Java开发你用过Jib吗
1. 前言 Jib是Google开发的可以直接构建 Java应用的Docker和OCI镜像的类库,以Maven和Gradle插件形式提供.它最骚操作的是可以在没有Docker守护程序的情况下构建,也就 ...
- script写在head与写在body中的区别
咱先说将Javascript写在head里面的情况吧,如果你要在这里面去操控DOM元素,是会报错的,因为浏览器是先执行head标签里面的内容,在执行时你的DOM元素还没有生成.(使用了windows. ...
- 在eclipse中使用maven构建spring cloud微服务
使用eclipse中使用maven构建spring cloud微服务,springcloud通过maven构建项目.springcloud项目搭建. 工具/原料 eclipse maven spr ...
- JavaScript基础对象创建模式之对象的常量(028)
虽然许多编程语言提供了const关键字来支持常量的声明,但JavaScript里没有表示常量的语义.我们可以用全大写的方式来声明变量,表明它实际上是个常量: Math.PI; // 3.1415926 ...
- CountDownLatch和CyclicBarrier 傻傻的分不清?超长精美图文又来了
你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough ...
- MySQL 事务 异常 事务隔离的级别
MySQL 事务 异常 事务隔离的级别 事务 在你操作数据库的同时,有可能其他用户还会不断地对数据进行增删改查操作.为了避免并行进行时出现混乱,就产生了"事务".事务就是要保证 ...
- UVA11300 Spreading the Wealth 题解
题目 A Communist regime is trying to redistribute wealth in a village. They have have decided to sit e ...