mybatis-启动源码分析
Posted PacosonSWJTU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis-启动源码分析相关的知识,希望对你有一定的参考价值。
【1】测试用例
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>
<!--
1)properteis
1.1、mybatis 可以使用properties 标签来引入外部properties配置文件的内容;
1.2、resource 引入类路径下 的资源;
1.3、url 是引入网络路径或磁盘路径下的资源;
-->
<properties resource="dbconfig.properties"></properties>
<!--
2)settings 包含很多重要的设置项
2.1、setting 用来设置每一个设置项:
name: 设置项名称, value:设置项取值;
-->
<!-- 开启驼峰命名规则,数据库表字段 a_b 可以解析为 类成员变量 aB -->
<!-- 显示指定每个需要更改的配置项,即使有默认值,防止版本更新带来的问题 -->
<settings>
<!-- 开启驼峰命名法 -->
<setting name="mapUnderscoreToCamelCase" value="false"/>
<!-- 懒加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 侵入型懒加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!--
3)别名处理器:可以为java类型起别名(注意:别名不区分大小写)
3.1) typeAlias为某一个java类型取别名, type:指定要起别名的类型全类名,默认别名就是类名小写,employee
alias: 指定新的 别名;
3.3)
-->
<typeAliases>
<!--
3.1) typeAlias为某一个java类型取别名, type:指定要起别名的类型全类名,默认别名就是类名小写,employee
alias: 指定新的 别名;
<typeAlias type="com.swjtu.mybatis.bean.Employee" alias="emp"/>
-->
<!--
3.2)package: 为某个包下的所有类批量起别名:
name: 指定包名,为当前包以及所有的子包的每个类都起一个默认别名(类名小写);
-->
<!--
3.3) 批量起别名的情况下,可以使用注解 Alias()为某个类型指定新的别名;
存在 a 包 和 a.b 包下都有 Employee类的情况;
-->
<package name="com.swjtu.mybatis.bean"/>
</typeAliases>
<!--
4、environments: 环境们, mybatis可以配置多种环境, default指定使用某种环境。可以达到快速切换环境。
4.1)environment:配置一个具体的环境信息;必须有两个标签:id代表当前环境的唯一标识
4.1.1) transactionManager 必须有, 事务管理器,
type 事务管理器类型,type="[JDBC|MANAGED]"
自定义事务管理器:实现 TransactionFactory 接口, type指定全类名
Configuration类中定义了别名: org.apache.ibatis.session.Configuration
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
4.1.2) dataSource 数据源:
type:数据源类型 type="[UNPOOLED|POOLED|JNDI]"
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
自定义数据源: 实现 DataSourceFactory 接口 , type 为自定义数据源的全类名
-->
<environments default="development">
<!-- 测试环境 -->
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<!-- 开发环境 -->
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--
5、databaseIdProvider :支持多数据库厂商的;
type=DB_VENDOR: VendorDatabaseIdProvider
作用就是得到数据库厂商标识(由数据库驱动自己附带的 getDatabaseProductName),mybatis根据该标识执行不同的sql;
并在 sql 标签如<select> 添加属性 databaseId 属性
-->
<databaseIdProvider type="DB_VENDOR">
<!-- 为不同数据库厂商起别名 -->
<property name="mysql" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>
<!--
将我们写好的sql 映射文件 EmployeeMapper.xml 一定要注册到全局配置文件 mybatis-config.xml 中;
6、mappers:将sql映射注册到全局配置中;
-->
<mappers>
<!--
mapper标签: 注册一个sql映射
注册配置文件的两种方法:
方法1:resource: 引用类路径下的sql 映射文件;
方法2:url: 引用网络路径或磁盘路径下的 sql 映射文件;
注册接口的方法:
方法1 class: 直接引用(注册)接口, 写接口的全类名;
补充1: 有sql映射文件,映射文件名必须和接口同名,并且仿造同一个目录下;
补充2: 没有sql映射文件, 所有sql 利用注解修饰接口上;
补充3: 不推荐使用注解,而使用sql映射文件的方式来 进行sql 映射。
-->
<mapper resource="com\\swjtu\\mybatis\\dao\\EmployeeMapper.xml"/>
<!--
批量注册: 前提是要把 Mapper类文件和Mapper sql 映射文件放在同目录下
荔枝:<package name="com.swjtu.mybatis.dao"/> 可以吧 该包下的所有mapper 映射文件注册到全局配置环境中;
-->
<!-- <mapper class="com.swjtu.mybatis.dao.cwn.MYCwnOprDAO"/> -->
</mappers>
</configuration>
执行sql;
public static void main1() {
/*1. 获取 SqlSessionFactory 对象*/
SqlSessionFactory sqlSessionFactory = MyBatisUtils.getSqlSessionFactory();
/*2.获取SqlSession对象, 不会自动提交数据*/
SqlSession session = sqlSessionFactory.openSession();
try {
String flag = "0911A";
List<Map<String, Object>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
String indexFormat = String.format("%8d", i);
Map<String, Object> map = new HashMap<>();
map.put("RCRD_ID", flag + indexFormat);
map.put("CUST_NUM", "CUST_NUM" +flag+ indexFormat);
map.put("CUST_NAME", "CUST_NAME" +flag+ indexFormat);
map.put("WARN_TIMES", i);
list.add(map);
}
// mapper.insert2CustWarn(list);
session.insert("com.swjtu.mybatis.dao.EmployeeMapper.insert2CustWarn", list);
session.commit();
} finally {
session.close();
}
System.out.println("bingo!");
}
mapper文件中的 dml 元素
<insert id="insert2CustWarn">
INSERT INTO my_cust_warn_tbl
(
rcrd_id
, cust_num
, cust_name
, warn_times
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.RCRD_ID}
, #{item.CUST_NUM}
, #{item.CUST_NAME}
, #{item.WARN_TIMES}
)
</foreach>
</insert>
MyBatisUtils.getSqlSessionFactory();
/**
* 获取SqlSessionFactory
* @return
*/
public final static SqlSessionFactory getSqlSessionFactory() {
String resource = "mybatis-config.xml";
InputStream inputStream;
try {
inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
【2】创建 SqlSessionFactory 对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
实际上返回的是 DefaultSqlSessionFactory;
// SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
而 build(Configuration config) 中的 config 是从mybatis-config.xml文件解析而来的;
【2.1】XMLConfigBuilder.parse() 产生Configuration
parser.parse() 方法的逻辑如下(XMLConfigBuilder构造器中新建了 Configuration对象):
// SqlSessionFactoryBuilder
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());// 新建了一个 configuration对象
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));// 解析xml节点
return configuration;
}
private void parseConfiguration(XNode root) {// 解析xml节点
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
我们跟踪了 mapperElement(..) 方法,它是解析 mybatis-config.xml 文件中在 mapper标签中配置的resource属性指定的sql mapper文件,比如:
<mappers>
<mapper resource="com\\swjtu\\mybatis\\dao\\EmployeeMapper.xml"/>
<mapper class="com.swjtu.mybatis.dao.EmployeeMapperAnnotation"/>
</mappers>
其中 root.evalNode("mappers") 获取的值就是 mapper 数组;
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} 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();
} 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.");
}
}
}
}
}
Xnode parent 长这样(就是 mybatis-config.xml 中 mappers元素内容):
以上代码,我们发现,解析 mapper有三种方式;
- 1.通过resource 配置;
- 2.通过url;
- 3.通过mapper class;
下面,我们以resource 为例来讲解;
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder
(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
以上代码发现,每一个mapper文件,都会创建一个 XMLMapperBuilder 对象来解析它;
String resource=“com\\swjtu\\mybatis\\dao\\EmployeeMapper.xml”;
XMLMapperBuilder构造器传入了4个参数
- mapper文件的输入流(com\\swjtu\\mybatis\\dao\\EmployeeMapper.xml文件流);
- mybatis配置对象Configuration;
- resource(sql文件名称com\\swjtu\\mybatis\\dao\\EmployeeMapper.xml);
- sqlFragments-sql片段为空;
XMLMapperBuilder构造器;该构造器新建了 MapperBuilderAssistant对象(解析mapper文件的助手);
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
MapperBuilderAssistant() 长这个样子;
【2.2】XmlMapperBuilder().parse() 解析mappers下的每个mapper文件内容
接着,进入 XmlMapperBuilder().parse() 方法,解析mappers下的mapper元素的内容,mapper元素如 <mapper resource="com\\swjtu\\mybatis\\dao\\EmployeeMapper.xml"/> ;如下:
代码步骤:
- step1)解析mapper文件下 mapper元素内容(主要是 dml标签元素内容);
- step2)把mapper文件名添加到 configuration,以避免二次解析;
- step3)构建命名空间;
【2.2.1】 XmlMapperBuilder.configurationElement(parser.evalNode("/mapper"))如下:
其中 parser.evalNode("/mapper")或context就是 文件 com\\swjtu\\mybatis\\dao\\EmployeeMapper.xml 的内容,如下:
代码步骤:
- step1)设置命名空间;
- step2)解析缓存;
- step3)解析 parameterMap 标签内容;
- step4)解析 resultMap 标签内容;
- step5)解析 sql 标签内容;
- step6) 解析 sql 的dml 标签内容(select或insert或update 或 delete);
最后一步, buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 就是真正解析 sql的 dml语句的标签了,即解析出sql文本并加载到内存中;
List<XNode> list 包含了多个 dml标签的内容数组;
接着 buildStatementFromContext(List<XNode> list, String requiredDatabaseId) 遍历list中的每个dml 标签元素; 如下:
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
以上代码,每个 dml标签元素 就会创建一个 XMLStatementBuilder 对象;
statementParser.parseStatementNode();
就用于解析 context 文本(dml标签元素内容); 如下:
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
以上代码的步骤如下:
step1)解析 dml元素中各种属性,包括 parameterMap,parameterType,resultMap,resultType等;
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
resultType 为map;因为 dml标签元素如下:
<select id="getEmpByLastNameLikeReturnMap" resultType="map">
SELECT id AS ID
, last_name AS LASTNAME
, email AS EMAIL
, gender AS GENDER
FROM emp_tbl
WHERE last_name like #{lastName}
</select>
step2)获取语言驱动器, LanguageDriver langDriver = getLanguageDriver(lang);
step3) Class<?> resultTypeClass = resolveClass(resultType);:返回 resultType的class类型;如 resultType为map,则返回 Map.class ;
protected Class<?> resolveClass(String alias) { // 以alias为map为例
if (alias == null) {
return null;
}
try {
return resolveAlias(alias);
} catch (Exception e) {
throw new BuilderException("Error resolving class. Cause: " + e, e);
}
}
// BaseBuilder.java
protected Class<?> resolveAlias(String alias) {
return typeAliasRegistry.resolveAlias(alias);
}
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
if (TYPE_ALIASES.containsKey(key)) {
value = (Class<T>) TYPE_ALIASES.get(key);
} else {
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
typeAliasRegistry 为类型别名注册器,如下:
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
step4)获取sql语句类型, PREPARED ;
step5)获取sql命令类型 SqlCommandType -SELECT
String nodeName = context.getNode().getNodeName(); // nodeName为select
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
step6)解析是否需要使用缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);//false
boolean useCache = context.getBooleanAttribute("useCache", isSelect);//true
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);//false
step7)解析dml元素中 include元素内容(比如 include sql);
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
public void applyIncludes(Node source) {
Properties variablesContext = new Properties();
Properties configurationVariables = configuration.getVariables();
if (configurationVariables != null) {
variablesContext.putAll(configurationVariables);
}
applyIncludes(source, variablesContext);
}
step8)解析 selectKey;sql中没有,不用考虑;
processSelectKeyNodes(id, parameterTypeClass, langDriver);
step9)创建sql 映射对象;
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
langDriver 指向了 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
public class XMLLanguageDriver implements LanguageDriver {
@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
if (script.startsWith("<script>")) {
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
// issue #127
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}
}
builder.parseScriptNode() 调用的是 XMLScriptBuilder.parseScriptNode() ; 解析动态sql (用${} 包裹的sql );
public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
}
因为我们不是动态sql,所以返回的是 RawSqlSource;
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
step10)解析 resultSets, keyProperty, keyColumn 属性等 (也不用太关心);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
step11)MapperBuilderAssistant.addMappedStatement() 把解析得到的sql映射对象添加到configuration;
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
参数列表如下:
- 1.id,getEmpByLastNameLikeReturnMap;
- 2.sqlSource,
parameterMappins 明显是 参数映射;
- 3.statementType , PREPARED;
- 4.sqlCommandType,SELECT;
- 5.fetchSize,null;
- 6.timeout, null;
- 7.parameterMap,null;
- 8.parameterTypeClass,null;
- 9.resultMap,null;
- 10.resultTypeClass, interface java.util.Map;
- 11.resultSetTypeEnum,null;
- 12.flushCache,false;
- 13.useCache,true;
- 14.resultOrdered,false;
- 15.keyGenerator,org.apache.ibatis.executor.keygen.NoKeyGenerator@5ef8df1e;
- 16.keyProperty,null;
- 17.keyColumn,null;
- 18.databaseId,null;
- 19.langDriver, org.apache.ibatis.scripting.xmltags.XMLLanguageDriver@32057e6;
- 20.resultSets, null;
MapperBuilderAssistant.addMappedStatement(...) 源码如下:
public MappedStatement addMappedStatement(
...) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
id = applyCurrentNamespace(id, false); 这一步,给id添加命名空间前缀,
- 执行前;id为 getEmpByLastNameLikeReturnMap ;
- 执行后,id为 com.swjtu.mybatis.dao.EmployeeMapper.getEmpByLastNameLikeReturnMap;
解析每个dml元素时,都要创建一个 MappedStatement.Builder(...) 用于创建 MappedStatement( sql映射信息)
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
MappedStatement statement = statementBuilder.build(); 源码如下:
public MappedStatement build() {
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
assert mappedStatement.sqlSource != null;
assert mappedStatement.lang != null;
mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
return mappedStatement;
}
补充:MappedStatement对象
MappedStatement 实际是封装了 sql映射信息的对象 ,sql映射是在 MapperBuilderAssistant.addMappedStatement(...) 方法里面构建;结构如下:
每条sql都是一个 MappedStatement对象,解析完成后,将其添加到 configuration对象中;
configuration 就是根据mybatis-config.xml 文件解析出的 Configuration 对象;长这个样子:
【2.2.2】 configuration.addLoadedResource(resource);
把已经解析的resource 文件名添加到 configuration,避免二次解析;
【2.2.3】XMLMapperBuilder.bindMapperForNamespace() 创建命名空间
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
很显然,当 有mapperclass的时候,才会设置 boundType;如果没有mapper class也没有关系;因为catch会忽略异常;
【2.2.4】解析其他
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
【2.2.5】创建 DefaultSqlSession对象并返回
解析完成后,生成Configuration对象;
【3】sqlSessionFactory.openSession()打开会话
调用的是 DefaultSqlSessionFactory.openSession()
// DefaultSqlSessionFactory
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
// 返回的是 DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
【3.1】DefaultSqlSessionFactory 中有两类方法包括,
- 从数据源获取会话:private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit);
- 从连接中获取会话:private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) ;
两者不同点在于
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Transaction tx = transactionFactory.newTransaction(connection);
// 从数据源获取会话
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);// 创建事务工厂
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// 创建事务
final Executor executor = configuration.newExecutor(tx, execType);// 执行器
return new DefaultSqlSession(configuration, executor, autoCommit);// 会话
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// 从数据库连接获取会话
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
获取会话无论采用 datasource,还是connection方式,其步骤如下:
- step1)获取环境对象;
- step2)获取事务工厂;
- step3)创建事务;
- step4)创建执行器;
- step5)创建会话 DefaultSqlSession;
【3.2】事务工厂
// 从环境对象中获取事务工厂
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
如果配置了环境元素,则返回配置的环境的事务工厂;否则默认返回 ManagedTransactionFactory;
此外,事务工厂有3个,如下:
【3.3】创建事务-ManagedTransaction
// 可管理的事务工厂
public class ManagedTransactionFactory implements TransactionFactory {
private boolean closeConnection = true;
@Override
public void setProperties(Properties props) {
if (props != null) {
String closeConnectionProperty = props.getProperty("closeConnection");
if (closeConnectionProperty != null) {
closeConnection = Boolean.valueOf(closeConnectionProperty);
}
}
}
@Override
public Transaction newTransaction(Connection conn) {
return new ManagedTransaction(conn, closeConnection);
}
// 创建事务对象
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
// Silently ignores autocommit and isolation level, as managed transactions are entirely
// controlled by an external manager. It's silently ignored so that
// code remains portable between managed and unmanaged configurations.
return new ManagedTransaction(ds, level, closeConnection);
}
}
可管理事务: ManagedTransaction;
public class ManagedTransaction implements Transaction {
private static final Log log = LogFactory.getLog(ManagedTransaction.class);
private DataSource dataSource;
private TransactionIsolationLevel level;
private Connection connection;
private boolean closeConnection;
public ManagedTransaction(Connection connection, boolean closeConnection) {
this.connection = connection;
this.closeConnection = closeConnection;
}
public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {
this.dataSource = ds;
this.level = level;
this.closeConnection = closeConnection;
}
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
@Override
public void commit() throws SQLException {
// Does nothing
}
@Override
public void rollback() throws SQLException {
// Does nothing
}
@Override
public void close() throws SQLException {
if (this.closeConnection && this.connection != null) {
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + this.connection + "]");
}
this.connection.close();
}
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
this.connection = this.dataSource.getConnection();
if (this.level != null) {
this.connection.setTransactionIsolation(this.level.getLevel());
}
}
}
它的commit, rollback 都是空的;
它的数据库连接的获取有两种方式,
- 上游给定,传入 Connection ;
- 通过 datasource 数据源获取connection;
【3.4】创建执行器
1)ExecutorType 是枚举类型,默认为SIMPLE ;
final Executor executor = configuration.newExecutor(tx, execType);
// Configuration.newExecutor 方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
如果为SIMPLE,则返回 SimpleExecutor 执行器; 但 如果启用缓存,则封装SimpleExecutor到缓存执行器;
最后把 执行器添加到拦截器列表,拦截器 Interceptor 列表如下,本质上是一个 ArrayList数组;
// 拦截器
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
其中SimpleExecutor父类Executor的实现类 包括
2)SimpleExecutor 长这个样子
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>queryCursor(stmt);
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
return Collections.emptyList();
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
}
【3.5】 创建会话 DefaultSqlSession
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
【4】session.insert(...)
【4.1】DefaultSqlSession.insert()
session.insert(...); 它实际调用的是 DefaultSqlSession.insert() 方法;
// DefaultSqlSession
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
executor.update(ms, warpCollection(parameter)); 中的 executor是 SimpleExecutor;
我们接着看下 SimpleExecutor-执行器 ;
// 简单执行器
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
// 执行器基类
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack = 0;
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
BaseExecutor.update(MappedStatement ms, Object parameter) 如下:
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
SimpleExecutor.doUpdate(...)
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
上述是执行sql 语句的地方,依赖的对象包括:
- configuration:根据mybatis-config.xml 解析得到的配置对象;
- StatementHandler:语句处理器;
- PreparedStatement:预编译语句;
Configuration.newStatementHandler(...) 方法如下:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
参数列表为:
参数1-executor;
参数2-mappedStatement;
参数3-parameterObject;
参数4- parameterObject 为:
{collection=[{RCRD_ID=0908A 0,
CUST_NUM=CUST_NUM 0,
WARN_TIMES=0,
CUST_NAM以上是关于mybatis-启动源码分析的主要内容,如果未能解决你的问题,请参考以下文章
Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段
Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段