1. Mybatis是什么?
官方给的定义:MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
mybatis作为一款java开发人员最常用的orm框架,重要性不言而喻。除了会使用mybatis进行基本的CRUD业务操作外,对mybatis的源码阅读也是java开发路上绕不开的关卡。接下来一段时间,分上下两篇来记录下自己学习mybatis的源码的过程。
2.准备工作。
构建一个简单mybatis项目,目录结果如下:
重要的类如下:
mybatis-config.xml,配置了一些额外属性,如别名,拦截器,不同的mapper加载方式,这里是为了后面分析源码加载解析mybatis-config.xml准备的。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- <properties resource="jdbc.properties"></properties>-->
<!--为实体类com.demo.dao.User配置一个别名User-->
<typeAliases>
<typeAlias type="com.example.mybatis.pojo.User" alias="user"/>
</typeAliases>
<!-- <typeHandlers>-->
<!-- <!– -->
<!-- 当配置package的时候,mybatis会去配置的package扫描TypeHandler-->
<!-- <package name="com.dy.demo"/>-->
<!-- –>-->
<!-- <!– handler属性直接配置我们要指定的TypeHandler –>-->
<!-- <typeHandler handler=""/>-->
<!-- <!– javaType 配置java类型,例如String, 如果配上javaType, 那么指定的typeHandler就只作用于指定的类型 –>-->
<!-- <typeHandler javaType="" handler=""/>-->
<!-- <!– jdbcType 配置数据库基本数据类型,例如varchar, 如果配上jdbcType, 那么指定的typeHandler就只作用于指定的类型 –>-->
<!-- <typeHandler jdbcType="" handler=""/>-->
<!-- <!– 也可两者都配置 –>-->
<!-- <typeHandler javaType="" jdbcType="" handler=""/>-->
<!-- </typeHandlers>-->
<!-- 为com.demo.dao包下的所有实体类配置别名,
MyBatis默认的设置别名的方式就是去除类所在的包后的简单的类名,
比如com.demo.dao.User这个实体类的别名就会被设置成User
-->
<!-- <typeAliases>-->
<!-- <package name="com.example.mybatis.pojo"/>-->
<!-- </typeAliases>-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="root123"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--mapper resource方式-->
<!--xxxMapper.xml放在src/main/java的包路径下-->
<!--<mapper resource="com/example/mybatis/mapper/UserMapper.xml"/>-->
<!--xxxMapper.xml放在src/main/resources某个文件夹下-->
<!--<mapper resource="mapper/UserMapper.xml"/>-->
<!--如下两种方式要求interface和mapper.xml在同一包下,且除扩展名外的名称要一样。-->
<!--mapper class方式-->
<!--<mapper class="com.example.mybatis.mapper.UserMapper"/>-->
<!--package name方式-->
<package name="com.example.mybatis.mapper"/>
</mappers>
</configuration>
实体类User
package com.example.mybatis.pojo;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
//@Data
//@Getter
//@Setter
public class User {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User={" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
接口类,接口类的位置和名称需要根据mapper的引入方式有关,具体见mybatis-config.xml中的说明。错误的位置和名称可能会导致报错:Invalid bound statement(not found)
package com.example.mybatis.mapper;
import com.example.mybatis.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper {
List<User> findAll();
}
UserMapper.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.example.mybatis.mapper.UserMapper">
<select id="findAll" resultType="user">
select * from t_user
</select>
</mapper>
建表语句:
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_bin NOT NULL,
`age` int(11) NOT NULL,
`remark` varchar(255) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
测试类,以上配置保证项目可以正常运行后开始mybatis的源码学习之旅。
package com.example.mybatis.demo;
import com.example.mybatis.mapper.UserMapper;
import com.example.mybatis.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class Test {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.findAll();
list.stream().forEach(System.out::println);
}
}
3.源码跟踪
测试类的前两行代码就是读取mybatis的配置文件mybatis-config.xml,转换为资源流。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
第三行四行代码通过调用SqlSessionFactoryBuilder()构建器的build(inputStream)方法创建了一个SqlSessionFactory工厂对象,继而创建了SqlSession会话对象,有了该对象我们就可以进行CRUD操纵了。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
3.1 build方法跟踪
调用SqlSessionFactoryBuilder的build方法,继续跟踪
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
继续调用重载的build方法,代码如下。主要作用就是根据传入的配置文件资源流,构建一个XMLConfigBuilder对象,然后调用parse方法解析。这里环境environment和和jdbc的properties没有指定都为null,使用默认参数。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// 根据配置信息构建XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//解析配置文件,构建一个SqlSessionFactory,具体为 DefaultSqlSessionFactory 。
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
//返回的默认实现
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
3.2 parser.parse()方法
parser.parse()用于解析上一步构建的XMLConfigBuilder对象中的标签节点对象。
public Configuration parse() {
if (this.parsed) {//只允许解析一次
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
//首先解析<configuration>节点,然后解析<configuration>下的所有节点,并注册到Configuration中。
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
对下的所有节点解析,并注册到Configuration中(Configuration为mybatis的配置中心。)。
private void parseConfiguration(XNode root) {
try {
//properties节点的解析,如jdbc.properties节点
this.propertiesElement(root.evalNode("properties"));
//setting节点解析,如设置缓存
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
//别名节点解析
this.typeAliasesElement(root.evalNode("typeAliases"));
//插件节点解析,即注册拦截器,如分页插件
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
//environments节点解析,即数据源解析
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//handler解析,加载默认的handler和用户自定义的typeHandler,用于javaType和jdbcType转换
this.typeHandlerElement(root.evalNode("typeHandlers"));
//解析mapper.xml
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
4.mybatis配置节点解析跟踪
mybatis-config中能配置的节点很多,我们只需要关注实际开发中可能会碰到的几个重要节点来分析跟踪下。
4.1 typeAliasesElement(root.evalNode(“typeAliases”))节点解析
typeAlias节点解析主要分两种情况:1.package批量扫描注册方式,2.单个别名注册方式。
单个别名注册
<typeAliases>
<typeAlias type="com.example.mybatis.pojo.User" alias="user"/>
</typeAliases>
批量扫描注册别名
<typeAliases>
<package name="com.example.mybatis.pojo"/>
</typeAliases>
typeAlias节点解析的代码如下,
private void typeAliasesElement(XNode parent) {
//配置了别名才解析
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {//别名配置不为空
XNode child = (XNode)var2.next();
String alias;
if ("package".equals(child.getName())) {//批量扫描包注册别名
alias = child.getStringAttribute("name");//包路径,如:com.example.mybatis.pojo
this.configuration.getTypeAliasRegistry().registerAliases(alias);
} else {//单个注册
alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
this.typeAliasRegistry.registerAlias(clazz);
} else {
this.typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException var7) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);
}
}
}
}
}
4.1.1 注册中心批量注册别名this.configuration.getTypeAliasRegistry().registerAliases(alias)
从mybatis配置中心Configuration中获取到别名注册中心TypeAliasRegistry,然后将别名alias注册进去。
TypeAliasRegistry类源码摘抄如下,它除了提供注册别名的服务外,在初始化阶段还会为我们默认注册一些别名,如String类的别名为string,Long的别名为long等。
public class TypeAliasRegistry {
private final Map<String, Class<?>> typeAliases = new HashMap();
public TypeAliasRegistry() {
this.registerAlias("string", String.class);
this.registerAlias("byte", Byte.class);
this.registerAlias("long", Long.class);
。。。。。。。
this.registerAlias("collection", Collection.class);
this.registerAlias("iterator", Iterator.class);
this.registerAlias("ResultSet", ResultSet.class);
}
public void registerAliases(String packageName) {
this.registerAliases(packageName, Object.class);
}
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
resolverUtil.find(new IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
Iterator var5 = typeSet.iterator();
while(var5.hasNext()) {
Class<?> type = (Class)var5.next();
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
this.registerAlias(type);
}
}
}
//获取别名信息
public Map<String, Class<?>> getTypeAliases() {
return Collections.unmodifiableMap(this.typeAliases);
}
}
4.1.2 registerAliases(String packageName, Class<?> superType)
public void registerAliases(String packageName, Class<?> superType) {
//解析出packageName包下的所有类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
resolverUtil.find(new IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
Iterator var5 = typeSet.iterator();
while(var5.hasNext()) {
Class<?> type = (Class)var5.next();
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
//继续调用注册别名方法。
this.registerAlias(type);
}
}
public void registerAlias(Class<?> type) {
//SimpleName即为去掉路径后的类名,如type:com.demo.User的simpleName就为User
String alias = type.getSimpleName();
Alias aliasAnnotation = (Alias)type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
//注解标注的别名,这里可以推测注解别名会覆盖包批量扫描的默认别名(注:这里暂时没有验证过)
alias = aliasAnnotation.value();
}
this.registerAlias(alias, type);
}
真正的注册,将别名alias作为key,全类名type作为value放入到一个map中,如:
(“user”,com.demo.User)
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
} else {
String key = alias.toLowerCase(Locale.ENGLISH);//别名均为小写
//别名不能重复
if (this.typeAliases.containsKey(key) && this.typeAliases.get(key) != null && !((Class)this.typeAliases.get(key)).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + ((Class)this.typeAliases.get(key)).getName() + "'.");
} else {
//放到别名map中
this.typeAliases.put(key, value);
}
}
}
至此,类别名批量包扫描注册源码已过完。接下来回头看下单个类别名注册代码。
4.1.3 单个类的别名注册
private void typeAliasesElement(XNode parent) {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String alias;
if ("package".equals(child.getName())) {
alias = child.getStringAttribute("name");
this.configuration.getTypeAliasRegistry().registerAliases(alias);
} else { //单个类的别名注册
alias = child.getStringAttribute("alias");//自定义的别名,如user
String type = child.getStringAttribute("type");//类的全路径,如com.demo.User
try {
Class<?> clazz = Resources.classForName(type);
{//配置文件没有指定别名,就用默认的类名全小写作为别名,如果有注解别名,则以注解别名为准
if (alias == null)
this.typeAliasRegistry.registerAlias(clazz);
} else {//指定了别名,就是用指定的别名注册 (这种情况下,mybatis忽略了@Alias的存在)
this.typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException var7) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);
}
}
}
}
}
至此,AliasesElement节点解析源码粗略过了一遍。接下来看下pluginElement节点解析。
4.2 pluginElement(root.evalNode(“plugins”))节点解析
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
parent-->plugins节点;
child --> plugin节点;
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();//plugin节点集合
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String interceptor = child.getStringAttribute("interceptor");//每一个interceptor
Properties properties = child.getChildrenAsProperties();//interceptor的属性配置
Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);//实例化interceptor
//添加注册到Configuration中的拦截器执行链InterceptorChain中。
this.configuration.addInterceptor(interceptorInstance);
}
}
}
调用InterceptorChain的add方法中。
public void addInterceptor(Interceptor interceptor) {
this.interceptorChain.addInterceptor(interceptor);
}
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList();
public void addInterceptor(Interceptor interceptor) {
this.interceptors.add(interceptor);
}
plugin节点的解析到此结束。
4.3 environmentsElement(root.evalNode(“environments”))节点解析
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="root123"/>
</dataSource>
</environment>
</environments>
代码如下:
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
//没有指定数据源的话,使用默认的数据源,即这里的 default="development"的数据源
if (this.environment == null) {
this.environment = context.getStringAttribute("default");
}
Iterator var2 = context.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String id = child.getStringAttribute("id");
if (this.isSpecifiedEnvironment(id)) {
//解析transactionManager节点
TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
//解析dataSource节点
DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
//创建数据源Environment,注册到Configuration中
this.configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
至此Environment节点源码大概过了一下,比较简单。
4.4 typeHandlerElement(root.evalNode(“typeHandlers”))节点解析
4.4.1typeHandler拦截器的作用是在POJO的类型javaType和数据库的类型jdbcType之间做转换,如将String类型转换为Varchar类型。Mybatis内置了日常常用的类型转换,我们也可以自定typeHandler处理特殊需求,我之前一篇博客写过这方面的记录。
4.4.2 typeHandler节点解析注册和alias节点解析类似,也分为批量扫描注册和单个类注册两种情况,当然也可以两中情况并存,但是我们一般都会把拦截器放在同一个包下,而非杂乱分布。源码如下:
<typeHandlers>
<!--当配置package的时候,mybatis会去配置的package扫描TypeHandler-->
<package name="com.dy.demo"/>
<typeHandler javaType=" xx" jdbcType="xx " handler="handler1 " />
<typeHandler javaType="yy " jdbcType=" yy" handler="handler2 " />
</typeHandlers>
private void typeHandlerElement(XNode parent) {
if (parent != null) {//typeHandler节点存在
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String typeHandlerPackage;
if ("package".equals(child.getName())) { //typeHandler的配置形式为批量包扫描
typeHandlerPackage = child.getStringAttribute("name");
this.typeHandlerRegistry.register(typeHandlerPackage);
} else { //配置形式为单个拦截器类注册
typeHandlerPackage = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = this.resolveClass(typeHandlerPackage);
JdbcType jdbcType = this.resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = this.resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
this.typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
this.typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
this.typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
4.4.3 批量注册拦截器
批量注册拦截器
if ("package".equals(child.getName())) {
typeHandlerPackage = child.getStringAttribute("name");//包路径
this.typeHandlerRegistry.register(typeHandlerPackage);//注册
}
register方法
public void register(String packageName) {
//下面这段代码的作用大概就是扫描指定的包路径下的所有类,筛选出TypeHandler类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
resolverUtil.find(new IsA(TypeHandler.class), packageName);//util工具筛选,IsA用到了是个内部类,主要方法isAssignableFrom可百度了解下
Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();//get出筛选结果
Iterator var4 = handlerSet.iterator();
while(var4.hasNext()) {
Class<?> type = (Class)var4.next();
//要求typeHandler满足非接口类,非匿名类,非抽象类
if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
// 继续调用
this.register(type);
}
}
}
javaType类型处理,分有配置javaType类型和没有配置javaType类型属性两种情况。
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
//handler可以处理的javaType类型集合
MappedTypes mappedTypes = (MappedTypes)typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {//配置了javaType属性
Class[] var4 = mappedTypes.value();
int var5 = var4.length;
for(int var6 = 0; var6 < var5; ++var6) {
Class<?> javaTypeClass = var4[var6];
this.register(javaTypeClass, typeHandlerClass);
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {//未配置javaType属性
this.register(this.getInstance((Class)null, typeHandlerClass));
}
}
配置了javaType的情况: this.register(javaTypeClass, typeHandlerClass);
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
MappedJdbcTypes mappedJdbcTypes = (MappedJdbcTypes)typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypes != null) {
JdbcType[] var4 = mappedJdbcTypes.value();
int var5 = var4.length;
for(int var6 = 0; var6 < var5; ++var6) {
JdbcType handledJdbcType = var4[var6];
this.register(javaType, handledJdbcType, typeHandler);
}
if (mappedJdbcTypes.includeNullJdbcType()) {
this.register((Type)javaType, (JdbcType)null, (TypeHandler)typeHandler);
}
} else {
this.register((Type)javaType, (JdbcType)null, (TypeHandler)typeHandler);
}
}
该种情况下又分为了jdbType属性为不为空两种情况,但最终都是调用了如下方法注册。
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = (Map)this.typeHandlerMap.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap();
}
((Map)map).put(jdbcType, handler);
this.typeHandlerMap.put(javaType, map);
}
this.allTypeHandlersMap.put(handler.getClass(), handler);
}
最终所有的typeHandler都注册进了TypeHandlerRegistry的私有属性allTypeHandlersMap这个Map中。
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap;
回头看javaType类型为空的情况源码,继续往下跟,可以发现和上面的情况非常类似,也是接着判断jdbcType,最后都注册进TypeHandlerRegistry的私有属性allTypeHandlersMap这个Map中。不再贴源码出来了。这个Map的key为注册的handler的Class对象,value为handler对象本身。
4.5 mapperElement(root.evalNode(“mappers”))节点解析
这是一个很重要的方法,主要作用是解析根据配置加载mapper.xml,并解析mapper.xml里面的元素。
mapper的4种配置方式:
<!--mapper resource方式-->
<mapper resource="com/example/mybatis/mapper/UserMapper.xml"/>
<mapper resource="mapper/UserMapper.xml"/>
<!--如下两种方式要求interface和mapper.xml在同一包下,且除扩展名外的名称要一样。-->
<mapper class="com.example.mybatis.mapper.UserMapper"/>
<package name="com.example.mybatis.mapper"/>
4.5.1 mapper节点解析源码
草看上面的4中mapper配置方式,分析下面的解析代码。鉴于篇幅及内容相似性,这里只分析package-name,mapper-resource情况。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
if ("package".equals(child.getName())) {//package-name方式
resource = child.getStringAttribute("name");//包路径resource
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {//mapper-resource配置方式
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {{//mapper-url 配置方式
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
跟踪批量包扫描的源码,调用了MapperRegistry的addMappers(package,clazz)方法
public void addMappers(String packageName, Class<?> superType) {
//解析出指定包路径下的所有类并迭代遍历
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
resolverUtil.find(new IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
Iterator var5 = mapperSet.iterator();
while(var5.hasNext()) {
Class<?> mapperClass = (Class)var5.next();
this.addMapper(mapperClass);//调用的MapperRegistery的addMapper(Class clazz)方法
}
}
addMapper(Class type)方法
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {//需要是接口类
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//将每一个接口类作为key,封装的MapperProxyFactory对象作为value存到已知的Mappers容器中。
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
parse方法源码
public void parse() {
String resource = this.type.toString();//接口类的全路径
if (!this.configuration.isResourceLoaded(resource)) {//只能加载一次
this.loadXmlResource();//加载解析xml资源
this.configuration.addLoadedResource(resource);//标记已加载过
this.assistant.setCurrentNamespace(this.type.getName());//设置名称空间
this.parseCache();//解析二级缓存
this.parseCacheRef();//解析引用缓存
Method[] methods = this.type.getMethods();
Method[] var3 = methods;
int var4 = methods.length;
for(int var5 = 0; var5 < var4; ++var5) {
Method method = var3[var5];
try {
if (!method.isBridge()) {
this.parseStatement(method);
}
} catch (IncompleteElementException var8) {
this.configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
this.parsePendingMethods();
}
看下 this.loadXmlResource()源码
该方法的作用是根据接口类得到对应mapper.xml文件,加载该xml文件并解析
private void loadXmlResource() {
if (!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) {
//如根据接口com.demo.mapper得到对应的com/demo/mapper.xml资源
String xmlResource = this.type.getName().replace('.', '/') + ".xml";
InputStream inputStream = this.type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
try {
inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource);
} catch (IOException var4) {
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName());
//解析mapper.xml资源文件
xmlParser.parse();
}
}
}
xmlParser.parse()源码
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
//下面3个可以不看
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
继续跟进configurationElement
这里解析了cache,sql,resultMap,parameterMap和select|insert|update|delete等mapper.xml中的标签。
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.equals("")) {
this.builderAssistant.setCurrentNamespace(namespace);
this.cacheRefElement(context.evalNode("cache-ref"));
this.cacheElement(context.evalNode("cache"));
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));//解析<parameterMap>标签
this.resultMapElements(context.evalNodes("/mapper/resultMap"));//解析<resultMap>标签
this.sqlElement(context.evalNodes("/mapper/sql"));解析<sql>片段标签
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//解析<select>,<insert>,<update>,<delete>标签。
} else {
throw new BuilderException("Mapper's namespace cannot be empty");
}
} catch (Exception var3) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
}
}
上面四个标签类的解析过程大概都是先获取到xml中所有的标签类,然后循环解析每一类标签,将解析到的参数封装成一个对象(ParameterMapping,ResultMapping,MappedStatement),添加到集合中(List,Map),最后都注册到Configuration对象中。
我们看下解析CRUD的buildStatementFromContext(XNode node)方法,最终调用的方法是parseStatementNode,源码如下,简单看下。这里的context就是XNode节点对象,也就是mapper.xml文件中解析出来的每一个select,update,delete,insert标签封装成的对象。如:
<select resultType="user" id="findAll"> select * from t_user </select>
public void parseStatementNode() {
String id = this.context.getStringAttribute("id");//即上面XNode对象里面的id,也就是接口里的方法名
String databaseId = this.context.getStringAttribute("databaseId");//使用默认值
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
String nodeName = this.context.getNode().getNodeName();//nodeName:节点类型,这里是select
//拿到SqlCommandType类型,即大写的SELECT
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;//是否是select语句
boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);//如果是select,则不刷新缓存
boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);//如果是select,则使用缓存
boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
includeParser.applyIncludes(this.context.getNode());
String parameterType = this.context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = this.resolveClass(parameterType);
String lang = this.context.getStringAttribute("lang");
LanguageDriver langDriver = this.getLanguageDriver(lang);//不同SQL厂商的语言驱动
this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
String keyStatementId = id + "!selectKey";
keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
Object keyGenerator;
//主键生成策略,如oracle可以使用useGeneratedKeys和selectKey生成自增主键
if (this.configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
//STATEMENT:直接操作sql,$获取数据;PREPARED:预处理,参数 #获取数据;CALLABLE:执行存储过程
StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = this.context.getIntAttribute("fetchSize");
Integer timeout = this.context.getIntAttribute("timeout");
String parameterMap = this.context.getStringAttribute("parameterMap");
String resultType = this.context.getStringAttribute("resultType");
Class<?> resultTypeClass = this.resolveClass(resultType);
String resultMap = this.context.getStringAttribute("resultMap");
String resultSetType = this.context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = this.configuration.getDefaultResultSetType();
}
String keyProperty = this.context.getStringAttribute("keyProperty");
String keyColumn = this.context.getStringAttribute("keyColumn");
String resultSets = this.context.getStringAttribute("resultSets");
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
上述一大堆代码的作用时获取校验每条SQL语句的各种参数,最终调用了 this.builderAssistant.addMappedStatement(. . . . . . )来构建了一个MappedStatement对象,并将其注册进Mybatis的注册中心Configuation中。addMappedStatement源码如下:
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) {
if (this.unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
} else {
id = this.applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);
ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
this.configuration.addMappedStatement(statement);
return statement;
}
}
至此,mybatis加载资源文件,build(…)的过程大概粗略的过了一下。总结起来大概的过程为:
1.加载mybatis核心配置文件mybatis-config.xml.
2.解析上述xml资源文件中各种标签对象,并添加到Configuration注册中心,如数据源,plugins,handlers,mappers等。
下一篇将会分析学习下getMapper(. . .)的过程,即mybatis执行sql的过程。