mybatis通过xpath进行解析xml,将xml描述的内容转化成java定义,全部维护在Configuration对象中,解析映射配置文件的ResultMap时,对应的描述java类为ResultMap
ResultMap标签中的元素被映射为java类型ResultMapping,ResultMapping分为id属性描述,构造器参数描述,普通属性描述,以下是ResultMapping的属性结构
public class ResultMapping {
//配置对象
private Configuration configuration;
//属性名
private String property;
//列名
private String column;
//java类型
private Class> javaType;
//jdbc类型
private JdbcType jdbcType;
//类型处理器
private TypeHandler> typeHandler;
//嵌入的ResultMapId,比如某些复杂对象,需要用其他的ResultMap进行映射,通常会使用association
//collection进行映射
private String nestedResultMapId;
//嵌入的查询id,有点时候可能对于一些复杂对象,可能没有直接使用ResultMap,可以直接指定查询的id
private String nestedQueryId;
//{id,name,age}
private Set notNullColumns;
//列名前缀
private String columnPrefix;
//标识,ResultFlag.ID,ResultFlag.CONSTRUCTOR
private List flags;
//复合列名设置的ResultMapping,比如{id=user_id, name=user_name}
private List composites;
//结果集,用于多结果集的情况
private String resultSet;
//外键
private String foreignColumn;
//对这个值是否懒加载
private boolean lazy;
ResultMapping() {
}
。。。。。。
}
ResultMap的结构如下
public class ResultMap {
//resultMap标签上的id属性
private String id;
//resultMap映射的复杂java对象类型
private Class> type;
//当前resultMap标签下定义的所有resultMapping
private List resultMappings;
//当前resultMap标签下定义的id resultMapping
//当没有定义id resultMapping,那么这个结合存储的内容和resultMappings集合是一样的
private List idResultMappings;
//当前resultMap标签下定义的构造参数 resultMapping
private List constructorResultMappings;
//当前resultMap标签下定义的属性resultMapping
private List propertyResultMappings;
//当前resultMap标签下定义的所有列名,包括每个resutMapping中的复合resultMapping的字段名
private Set mappedColumns;
//鉴别器,维护着一个Map
private Discriminator discriminator;
//是否存在嵌入的resultMap
private boolean hasNestedResultMaps;
//是否存在嵌入的查询,某属性是通过另外一个select获取的属性
private boolean hasNestedQueries;
//是否进行自动映射,没有进行映射配置的字段
private Boolean autoMapping;
private ResultMap() {
}
。。。。。。
}
解析完了ResultMap后开始解析sql语句,sql最后会包装成MappedStatement
public final class MappedStatement {
//mapper配置文件的路径,牢记自己是从什么进化而来
private String resource;
//配置对象
private Configuration configuration;
//MappedStatement语句的id
private String id;
//每次最大获取量
private Integer fetchSize;
//获取超时时间
private Integer timeout;
//语句类型,STATEMENT, PREPARED, CALLABLE
private StatementType statementType;
//结果集类型,比如滚动敏感等
private ResultSetType resultSetType;
//解析后SqlNode封装对象
private SqlSource sqlSource;
//二级缓存对象
private Cache cache;
//参数映射
private ParameterMap parameterMap;
//返回值映射
private List resultMaps;
//是否需要刷新
private boolean flushCacheRequired;
//是否使用缓存
private boolean useCache;
//结果集顺序
private boolean resultOrdered;
//sql命令类型
private SqlCommandType sqlCommandType;
//主键生成器
private KeyGenerator keyGenerator;
//主键属性名
private String[] keyProperties;
//主键列名
private String[] keyColumns;
//嵌入的ResultMap
private boolean hasNestedResultMaps;
//数据库id
private String databaseId;
//mybatis的日志对象
private Log statementLog;
//用于解析sql的脚本解析器
private LanguageDriver lang;
//结果集,用于多结果集
private String[] resultSets;
MappedStatement() {
// constructor disabled
}
。。。。。。
}
在添加接口映射时,还会对接口进行注解的解析,比如@Insert,@Update,@Delete,@Select等,如果这些注解上的sql语句是以
MapperScannerConfigurer是一个实现了BeanDefinitionRegistryPostProcessor接口的类,在spring构建完BeanFactory之后会进行后置调用,这是spring提供给用户最后添加BeanDefinition的机会,mybatis继承了spring的ClassPathBeanDefinitionScanner,实现了自己的ClassPathMapperScanner扫描器,扫描指定路径下的接口,将这些接口封装成MapperFactoryBean,MapperFactoryBean是一个实现了FactoryBean接口的类。
MapperProxyFactory创建接口代理对象,逻辑实现为MapperProxy,它实现了JDK的InvocationHandler接口,它在invoke方法中调用了MapperMethod的execute方法,在MapperMethod的构造器中解析方法参数,多个参数的会封装成ParamMap,除了mybatis的参数RowBounds,ResultHandler之外,如果只有一个参数的话,那么不会包装成ParamMap,但如果是集合类型(包括数组),它又会在在接下来的sqlSession.wrapCollection方法中包装成StrictMap
//如果参数是集合类型
if (object instanceof Collection) {
StrictMap
接着创建对应的StatementHandler,如果有必要的话会被插件代理,解析sqlSource,解析${},替换需要的值,解析#{},构建成表达式级别的ParamMapping,通过ParamHandler处理参数,获取TypeHandler设置对应的参数,执行sql获取结果,如果是插入语句,并且需要获取主键,那么会填充主键,最后根据ResultMap映射参数,如果映射不完整,会触发自动映射。
在mybatis3.4中可以解析一下几种类型的#{}表达式
#{age} 译为{“property”:“age”}
#{age,typeHandler=int,javaType=java.lang.Integer} 译为{“property”:“age”, “typeHandler”:“int”,“javaType”:“java.lang.Integer”}
#{age:int} 译为{“property”:“age”, “jdbcType”:“int”}
#{age:int,typeHandler=int,javaType=java.lang.Integer} 译为{“property”:“age”, “jdbcType”:“int”, “typeHandler”:“int”,“javaType”:“java.lang.Integer”}
#{(表达式)} 译为{“expression”:“表达式”}
#{(表达式),typeHandler=int,javaType=java.lang.Integer} 译为{“expression”:“表达式”, “typeHandler”:“int”,“javaType”:“java.lang.Integer”}
#{(表达式):varchar} 译为{“expression”:“表达式”, “jdbcType”:“int”}
#{(表达式):varchar,typeHandler=int,javaType=java.lang.Integer} 译为{“expression”:“表达式”, “jdbcType”:“int”, “typeHandler”:“int”,“javaType”:“java.lang.Integer”}
解析后的BoundSql
public class BoundSql {
//解析#{}后的sql语句
private String sql;
//#{}解析出来的参数映射
private List parameterMappings;
//参数
private Object parameterObject;
//偏好设置
private Map additionalParameters;
//偏好参数封装的MetaObject
private MetaObject metaParameters;
。。。。。。
}
懒加载的逻辑在ResultLoader中,原理很简单就是创建一个Executor,然后从事物工厂中获取事务,然后调用query即可
对于嵌入式的ResultMap的解析,主要是要知道cachekey的生成逻辑,mybatis生成cacheKey分以下三种:
对于嵌入属性的嵌入属性,那么把父cacheKey加入到cacheKey中,保证唯一性
插件执行责任链示意图: