Mybatis的Demo可以直接去官网下载,如果不下载的话那么可以根据下面的步骤搭建一个Demo,测试MyBatis
源码。
在maven项目中的pom文件引入Mybatis依赖,pom依赖项如下所示:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.1</version>
</dependency>
新建一个mybatis-config.xml文件作为Mybatis文件配置,这个配置文件对应mybatis包下面的Configuration类,内容如下:
<?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>
<settings>
<!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存-->
<setting name="cacheEnabled" value="true"/>
<!--延迟加载的全局开关,查询时,关闭关联对象即时加载以提高性能-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--是否允许单个语句返回多结果集-->
<setting name="multipleResultSetsEnabled" value="true"/>
<!--使用列标签代替列名-->
<setting name="useColumnLabel" value="true"/>
<!--允许JDBC支持自动生成主键-->
<setting name="useGeneratedKeys" value="false"/>
<!--指定 MyBatis 应如何自动映射列到字段或属性-->
<!--NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。
FULL 会自动映射任何复杂的结果集(无论是否嵌套)-->
<setting name="autoMappingBehavior" value="PARTIAL"/>
<!--配置默认的执行器->
<!--SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement);
BATCH 执行器不仅重用语句还会执行批量更新-->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!--当没有为参数指定特定的 JDBC 类型时,空值的默认JDBC 类型->
<setting name="jdbcTypeForNull" value="OTHER"/>
</settings>
<!--定义SqlSession的环境配置-->
<environments default="development">
<environment id="development">
<!--定义事务管理器配置-->
<transactionManager type="JDBC"/>
<!--数据源配置 采用连接池-->
<dataSource type="POOLED">
<!--jdbc驱动器-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!--mysql的url-->
<property name="url" value="实际路径"/>
<!--用户名-->
<property name="username" value="实际用户名"/>
<!--密码-->
<property name="password" value="实际密码"/>
</dataSource>
</environment>
</environments>
<!--配置mapper文件-->
<mappers>
<mapper resource="DemoMapper.xml"/>
</mappers>
</configuration>
本文demo的Mybatis配置文件只配置了以下三个内容,实际使用当中这三个配置也比较常用,具体如下所示:
下面的demo是摘抄自官网,实际中应根据项目的具体路径配置
<!-- 第一种使用相对于类路径的资源引用 也是本文中使用的方式-->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
<!-- 第二种使用相对于类路径的资源引用 -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<!-- 第三种使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!-- 第四种使用映射器接口实现类的完全限定类名 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
根据官网介绍,MyBatis配置文件共有9个配置标签可供使用,具体如下:
DemoMapper接口内容如下:
public interface DemoMapper {
/**
* 查询用户信息
*
* @return 返回结果
*/
List<BaseUserInfoDO> queryBaseUserInfoDO();
}
此次测试中选择的是base_user_info数据库表,本次只测试select查询语句,然后分析其底层源码实现。DemoMapper.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.test.mapper.DemoMapper">
<!--用as将数据表中的字段映射到实体类字段上-->
<select id="queryBaseUserInfoDO" resultType="com.test.entity.BaseUserInfoDO">
/**UserMapper.queryBaseUserInfoDO 用户信息查询*/
SELECT
id as id,
user_id as userId,
user_name as userName,
phone as phone,
status as status,
sign_status as signStatus,
sex as sex
FROM
base_user_info
</select>
</mapper>
对应接收的实体类如下:
@Getter
@Setter
@ToString(callSuper = true)
public class BaseUserInfoDO implements Serializable {
private Long id;
private Long userId;
private String userName;
private String phone;
private String status;
private String signStatus;
private String sex;
}
测试类代码如下:
public class TestMyBatis {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
//获取MyBatis配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过SqlSessionFactoryBuilder的build()方法获取SqlSessionFactory 对象
//此时已将MyBatis配置文件的相关配置已获取到,即Configuration类已获取到值
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过SqlSessionFactory的openSession()方法获取SqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//通过SqlSession对象执行SQL语句
List<BaseUserInfoDO> result = sqlSession.selectList("com.mybatis.DemoMapper.queryBaseUserInfoDO");
System.out.println(result.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}
}
Demo搭建过程如下:
Demo运行结果如下:
从上图中可以看到,MyBatis的的整个执行流程,解析如下:
终于到了源码分析阶段了,相信到了这一步,你应该非常想看到Myabtis到底是如何解析SQL语句的吧。不要急,慢慢来,心急可吃不了热豆腐哦。
通过官网的介绍,可以知道MyBatis的作用是用来连接数据库,然后执行SQL语句的,根据Demo的执行结果也验证了官网的正确性。所以此次源码分析主要分析这两大块内容,
根据Demo的内容可以知道,数据源和配置类信息都配置在mybatis-config.xml文件中,在测试类TestMyBatis 中,先是将配置文件解析成InputStream流,放到SqlSessionFactoryBuilder的build(inputStream)方法中,然后获取到SqlSessionFactory对象,所以现在就来看一下build()方法源码里面执行了哪些内容吧。 即解析下面这条语句的源码
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
源码执行过程如下所示。部分无关代码已省略:
public class SqlSessionFactoryBuilder {
/**
* build方法的入口,调用下面的build方法
**/
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
/**
* 在这里真正的对mybatis-config.xml文件进行解析
**/
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
//这一步中主要的操作是将mybatis-config.xml文件加载进parser对象,然后初始化Configuration类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//先调用parser.parse()方法获取mybatis-config.xml文件配置信息,然后将里面的信息赋值给Configuration类
//然后调用下面的build方法,将已经赋值的Configuration类作为参数传递进DefaultSqlSessionFactory类中
//这一步中Configuration类已经完全获取mybatis-config.xml文件配置信息
return build(parser.parse());
}
/**
*最后调用的返回方法
**/
public SqlSessionFactory build(Configuration config) {
//将已经初始化的Configuration类作为参数传递进DefaultSqlSessionFactory类,并返回
return new DefaultSqlSessionFactory(config);
}
}
将上面的步骤拆开来分析,先看下面这个语句,
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
从名字就可以看出来,这个类的初始化应该是去加载mybatis-config.xml文件,但是还没有真正的去解析,下面看一下这个XMLConfigBuilder类的这个构造函数:
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private XPathParser parser;
private String environment;
/**
* 这个方法主要是加载mybatis-config.xml文件
**/
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props)
{
//初始化XPathParser对象,加载mybatis-config.xml文件
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
/**
* 这个方法主要通过super(new Configuration())方法将初始化的Configuration类传递进去。
* 主要是将Configuration类进行初始化
**/
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//调用父类的构造函数,同时Configuration类进行初始化
//非常关键
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
}
先来看一下XMLConfigBuilder 类的父类BaseBuilder 类的构造函数吧,主要是看 super(new Configuration())这一个语句的一个执行内容。
public abstract class BaseBuilder {
//MyBatis的配置类
protected final Configuration configuration;
//JAVA类型别名对象,主要对应JAVA类型。如整型、布尔型、Float类型等
protected final TypeAliasRegistry typeAliasRegistry;
//JDBC类型处理器,主要是将JDBC类型转换成JAVA类型,如将CHAR、VARCHAR类型转化为String类型
protected final TypeHandlerRegistry typeHandlerRegistry;
//super(new Configuration())调用的就是这个方法,
//其实主要的就是Configuration类的初始化
public BaseBuilder(Configuration configuration) {
//初始化后的Configuration类进行赋值
this.configuration = configuration;
//JAVA类型别名对象随着Configuration类的初始化也进行初始化了
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
//JDBC类型处理器随着Configuration类的初始化也进行初始化了
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
}
下面看一下Configuration 类中成员变量吧,几乎整个MyBatis的配置数据以及存储数据都是通过Configuration类进行读和写的。 下面看一下Configuration类型主要成员变量,无关的成员变量暂时省略。
/**
* Configuration类包含了mybatis-config.xml中所有的配置选项
**/
public class Configuration {
//Environment对象对应的就是mybatis-config.xml文件中environments标签
//对应的就是数据库的事务管理和数据源配置
protected Environment environment;
//下面所有的变量对应的都是mybatis-config.xml文件中setting标签中的配置
//settings标签映射开始
protected boolean safeRowBoundsEnabled = false;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase = false;
protected boolean aggressiveLazyLoading = true;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys = false;
protected boolean useColumnLabel = true;
//MyBaits一级缓存默认是开启
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls = false;
protected String logPrefix;
protected Class <? extends Log> logImpl;
protected ProxyFactory proxyFactory;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
//默认执行器SIMPLE类型的,共有三种,其它两种是REUSE、BATCH
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected Properties variables = new Properties();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
//延时加载默认关闭
protected boolean lazyLoadingEnabled = false;
//settings标签映射结束
//数据库ID
protected String databaseId;
//驱动器
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//默认拦截器
protected final InterceptorChain interceptorChain = new InterceptorChain();
//JAVA类型别名对象 进行实例化
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
//JDBC类型处理器 进行实例化
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
//对应SQL操作对象,用Map进行存储
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
//mapper文件的节点容器
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
//SQL语句查询结果的缓存容器,就是查询一次后,再次查询相同的SQL语句,可以从此处直接获取缓存数据
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
//SQL语句执行完成后返回的结果集
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
//SQL语句的参数容器
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
//mapper文件(存放SQL语句)中SQL语句构造器容器
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
//SQL语句的主键容器
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<String>();
//缓存解析器容器
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
//JDBC结果解析器容器
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
//方法解析器容器
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
//Configuration对象的默认构造函数
//这个构造函数的主要作用是向JAVA类型别名对象中增加一些JAVA类型对象
public Configuration() {
//...已省略部分代码
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
}
}
JAVA类型别名对象TypeAliasRegistry初始化内容如下:
/**
* 这个类只展示部分代码,主要展示它的内容,理解其功能
**/
public class TypeAliasRegistry {
//用Map来存储类型别名和对应的JAVA类型
private final HashMap<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
}
//registerAlias方法,主要是将JAVA类型别名和对应的JAVA类型放进Map容器中
public void registerAlias(String alias, Class<?> value) {
//省略无用代码.......
//就是将JAVA类型的别名和对应的类传到TYPE_ALIASES这个Map容器中
TYPE_ALIASES.put(key, value);
}
}
JDBC类型处理器对象TypeHandlerRegistry,在查询完SQL语句之后会调用这个类将JDBC类型映射成对应的JAVA类型,它的初始化内容如下:
/**
* 这个类只展示部分代码,主要展示它的内容,理解其功能
**/
public final class TypeHandlerRegistry {
//JDBC类型处理器容器
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
/**
* 初始化时将JDBC类型对应的类型处理器存放在Map容器中
**/
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler());
register(String.class, new StringTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
//.......省略部分代码
}
//....register函数就是将对应的Map中存储对应的类型和处理器对象
至此,下面的整个语句已经解析结束。
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
此时,Configuration类初始化结束,mybatis-config.xml配置文件也加载到parser对象,parser对象的内容截图如下:
其中
从上面的内容可以知道,mybatis-config.xml配置文件存储在parser对象中, 但是还没有进行解析, 那么接下来看下面这个函数是怎么解析mybatis-config.xml文件的吧
parser.parse()
这个方法的具体内容如下所示:
public class XMLConfigBuilder extends BaseBuilder {
/**
* parse()方法入口
**/
public Configuration parse() {
//如果已解析,则抛一个已解析的异常出去,
if (parsed) {
throw new BuilderException("Each MapperConfigParser can only be used once.");
}
//将是否已解析的标志字段置为true,开始解析配置文件了
parsed = true;
//parser.evalNode("/configuration")方法是获取mybatis-config.xml文件中的标签
//...配置信息.... 中的所有内容
//parseConfiguration()方法则开始对中间的所有标签进行解析
parseConfiguration(parser.evalNode("/configuration"));
//返回结果
return configuration;
}
/**
* 开始对mybatis-config.xml文件进行解析
**/
private void parseConfiguration(XNode root) {
try {
//从这里就可以可以看到这些属性其实就是对应mybatis-config.xml文件中的所有配置标签
//这个方法执行完成之后就可以获取到mybatis-config.xml文件中的所有信息了
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
//解析JAVA类型别名类标签
typeAliasesElement(root.evalNode("typeAliases"));
//解析拦截器标签
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析settings标签
settingsElement(root.evalNode("settings"));
//解析environments标签即获取事务管理和数据源
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//JDBC结果处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mapper文件标签 获取SQL语句
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
}
此次测试类主要下面两个方法:
environmentsElement() 方法源码如下:
public class XMLConfigBuilder extends BaseBuilder {
/**
* 方法入口:解析environments标签内容,获取数据源配置
**/
private void environmentsElement(XNode context) throws Exception {
//节点内容肯定不为空
if (context != null) {
//如果environment标签内容为空,则获取默认配置
if (environment == null) {
environment = context.getStringAttribute("default");
}
//循环遍历environments标签下的所有节点,知道找到environment标签的ID
for (XNode child : context.getChildren()) {
//获取当前节点的ID
String id = child.getStringAttribute("id");
//environment标签的id与当前节点的ID匹配
if (isSpecifiedEnvironment(id)) {
//获取事务管理器
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//获取数据源
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//设置configuration对象中的Environment对象
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
}
方法执行完成之后可以看到如下结果
environmentsElement() 方法至此结束,此时已经获取到了数据源和事务管理配置了。
接下来看mapperElement() 方法里面是如何获取SQL语句的,即获取StateMent对象,这个过程有点复杂,因为mapper文件中的sql一般如下所示:
<mapper namespace="com.mybatis.DemoMapper">
<select id="queryBaseUserInfoDO" resultType="com.test.entity.BaseUserInfoDO">
/**UserMapper.queryBaseUserInfoDO 用户信息查询*/
SELECT
id as id,
user_id as userId,
user_name as userName,
phone as phone,
status as status,
sign_status as signStatus,
sex as sex
FROM
base_user_info
</select>
</mapper>
一个StateMent对象其实就是一个完整的SQL语句,要拼接完整的StateMent内容,需要先获取
public class XMLConfigBuilder extends BaseBuilder {
/**
* 方法入口:解析mapper文件,获取对应的SQL语句
**/
private void mapperElement(XNode parent) throws Exception {
//mappers标签肯定不为空
if (parent != null) {
//遍历mappers标签下的所有子标签
for (XNode child : parent.getChildren()) {
//这里将package扫描放在第一位,所以package配置的优先级最高
//但是我们此次是根据mapper resource进行配置的,所以主要跟踪这个源码
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//获取根据resource配置的mapper文件
String resource = child.getStringAttribute("resource");
///获取根据url配置的mapper文件
String url = child.getStringAttribute("url");
//获取根据class配置的mapper文件
String mapperClass = child.getStringAttribute("class");
//如果是根据resource配置的mapper文件则进入以下方法,这个方法也是本次测试类的配置方式
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//根据配置的resource获取到对应的mapper.xml文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//解析mapper.xml文件,获取对应的Statement
mapperParser.parse();
}
//如果是根据url配置的mapper文件则进入以下方法
else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
//如果是根据class配置的mapper文件则进入以下方法
else if (resource == null && url == null && mapperClass != null) {
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.");
}
}
}
}
}
}
mapperParser.parse() 主要就是解析mapper.xml文件,获取对应的Statement,它的源码如下:
public class XMLMapperBuilder extends BaseBuilder {
/**
* 方法入口:解析mapper文件,并将mapper文件内容设置到Configuration对象中去
**/
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//这个方法就是真正的解析mapper.xml文件,获取对应的Statement
//然后把解析后的Statement对象设置到configuration对象中
configurationElement(parser.evalNode("/mapper"));
//添加mapper.xml文件到配置类configuration
configuration.addLoadedResource(resource);
//绑定namespace,即mapper.xml文件对应的mapper接口
bindMapperForNamespace();
}
}
/**
* 进入到mapper.xml文件内部,进行解析
**/
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
//获取二级缓存标签
cacheElement(context.evalNode("cache"));
//获取参数集
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//获取结果集
resultMapElements(context.evalNodes("/mapper/resultMap"));
//获取sql标签内容,标签内容里面是数据库表字段
sqlElement(context.evalNodes("/mapper/sql"));
//构建StateMent对象, 根据(select|insert|update|delete)标签构建完整的SQL语句
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
}
}
/**
* 循环遍历select|insert|update|delete 节点,解析具体的StateMent
**/
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
//循环遍历节点对象
for (XNode context : list) {
//根据节点标签获取到此节点具体的SQL内容
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析上面获取到的SQL内容,然后构建完整的Statement对象
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
}
statementParser.parseStatementNode() 方法就是拼接具体的StateMent对象,这里直接看解析各个标签后获得的结果,然后将解析出来的结果当做参数传进addMappedStatement() 方法内,在这个方法里面将各个标签解析出来的结果拼接成完整的StateMent对象,然后将此Statement对象添加到Confiuration类中mappedStatements容器中,addMappedStatement() 方法源码内容如下:
public MappedStatement addMappedStatement
(
//当前NameSpace的StateMent的唯一ID
String id,
//具体的SQL对象
SqlSource sqlSource,
//一般默认的为PrepareStament
StatementType statementType,
//SQL命令类型 此次为Select
SqlCommandType sqlCommandType,
//查询数量
Integer fetchSize,
//超时设置
Integer timeout,
//SQL入参
String parameterMap,
//SQL参数类型
Class<?> parameterType,
//SQL返回结果
String resultMap,
//SQL返回结果类型
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
//默认使用一级缓存
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang
)
{
if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved");
id = applyCurrentNamespace(id, false);
//查询语句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//构建SQL StateMent
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
statementBuilder.resource(resource);
statementBuilder.fetchSize(fetchSize);
statementBuilder.statementType(statementType);
statementBuilder.keyGenerator(keyGenerator);
statementBuilder.keyProperty(keyProperty);
statementBuilder.keyColumn(keyColumn);
statementBuilder.databaseId(databaseId);
statementBuilder.lang(lang);
statementBuilder.resultOrdered(resultOrdered);
setStatementTimeout(timeout, statementBuilder);
setStatementParameterMap(parameterMap, parameterType, statementBuilder);
setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
MappedStatement statement = statementBuilder.build();
//将构建好的Statement设置到Configuration对象的MappedStatement容器中
configuration.addMappedStatement(statement);
//返回statement对象
return statement;
}
通过Debug,查看一下此时的Statement对象的内容如下:
至此mapperElement() 方法结束,Configuration对象已获取到完整的SQL语句了。
也标志着测试类的第一条语句已全部执行结束 。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
此时 mybatis-config.xml文件内容已全部解析完成,并且将文件中的配置信息都赋值到sqlSessionFactory类的configuration对象中,语句执行结束后sqlSessionFactory 的environment对象内容如下,从图中可以知道此时已获取到数据源配置了。
sqlSessionFactory 的mappedStatements对象内容如下,从下图中可以知道,此时已获取到Statement对象了,即具体的SQL语句。
总结:解析整个mybatis-config.xml的整个流程如下图所示:
请简单介绍一下你对MyBatis的认识?
答:
MyBatis加载mapper文件的方式有几种?哪种加载方式的优先级最高?
答:四种,分别是package、resource、url、class, 其中package的加载方式最高,因为加载mapper文件的时候是第一个对它进行判断的。
你知道MyBatis的缓存吗?请简单介绍一下。
答:MyBatis的一级缓存是默认开启的,每查询一次SQL的时候都是将此次的结果保存到一个Map容器中,第二次查询相同的SQL语句时会从缓存中取数据,当然当数据有变动的时候,MyBatis会清除掉缓存。
MyBatis的二级缓存需要自己在mapper文件中配置cache标签。
MyBatis的执行器有几种?
答:三种,分别是SIMPLE, REUSE, BATCH。
链接如下:
Mybatis源码解析之数据库连接与SQL执行