mybatis 初始化流程
步骤:
1、通过 ClassLoader 类加载器读取某个路径的 xml 文件来获取 InputStream 流对象.
2、通过 SqlSessionFactoryBuilder 对象来解析流, 返回工厂
3、通过 SqlSessionFactory 工厂获取 SqlSession对象
4、通过 SqlSession 可以操作. (查询、删除、修改、添加)
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sessionFactory.openSession();
sqlSession.insert(".....");
我们通过 SqlSesseionFactoryBuilder 类来分析,它是如何通过 InputStream 来构建工厂的.
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null); //方法重载
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//创建XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//封装Configuration对象成SqlSessionFactory
return build(parser.parse()); //parser.parse()解析返回Configuration 对象
} 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.
}
}
}
//通过Configuration 创建 SqlSessionFactory 工厂 (此方法相当于中转)
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
可以发现 XMLConfigBuilder 对象中的 parse() 方法的作用。主要是把 InputStream 解析成一个 Configuration 对象。然 Configuration 对象在 mybatis 中是非常重要的。它是 mybatis 全局唯一个配置对象。通过它可以获取到所有的配置及信息。咱先暂时不解读 Configuration 对象.
XMLConfigBuilder
对象解析, 先看其构造器:
//调用的构造器(实例化 XPathParser)
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
//parser 为mybatis 解析器. 暂不分析
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new 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")); //解析<configuration>标签
return configuration;
}
// 解析文件推荐先看下 mybatis 官方文档: http://www.mybatis.org/mybatis-3/zh/getting-started.html
private void parseConfiguration(XNode root) {
try {
//解析<properties>标签
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//解析<typeAliases>标签 别名
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
// 在objectFactory和objectWrapperFactory第631期之后阅读它
//解析environments节点, 如若为空配置默认的
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mappers 节点对应的映射文件
//解析<mappers>标签 ** 解析映射文件 (我们重点解析这个节点下的, 有兴趣的话可以看下其它的解析步骤)
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
//解析映射文件 如: TestDao.xml
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"); //获取到xml路径
String url = child.getStringAttribute("url"); //空
String mapperClass = child.getStringAttribute("class"); //空
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//获取 mapper.xml 文件InputStream
InputStream inputStream = Resources.getResourceAsStream(resource);
// 映射文件对应一个XmlMapperBuilder 对象
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.");
}
}
}
}
}
发现在解析下的节点时, mybatis 会通过 XMLMapperBuilder
对象来解析. 在构造 XMLMapperBuilder 对象会创建一个 XPathParser 对象及一个 XMLMapperEntityResolver
对象来帮助解析. 这二个对象暂时不分析, 有兴趣的话可以去了解下。 继续看源码
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
//创建了 XPathParser 与 XMLMapperEntityResolver
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;
}
映射文件解析:
1、解析节点做对应的储存.
2、在配置中心 Configuration
储存该命名空间
3、解析未解决的语句及结果集对象映射
注意: 这里解析的顺序. 先解析parameterMap 节点在 resultMap 节点在 sql 节点然后在 select… 的节点0
public void parse() {
// 如果当前的映射文件未被加载, 就开始解析. (resource xml的命名空间)
if (!configuration.isResourceLoaded(resource)) {
//配置mapper节点下的属性
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource); //储存映射文件的命名空间
bindMapperForNamespace(); //绑定命名空间路径对应的 Class 对象
}
//解析未解决的语句及结果集对象映射
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
//继续解析节点
private void configurationElement(XNode context) {
try {
//mapper.xml的命名空间路径
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) { // 不设置命名空间会抛异常
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace); // 給构建器助手设置当前命名空间
//解析<mapper>标签下的 <cache-ref>标签 ======>> 不经常用不分析
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析<parameterMap>节点
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析<resultMap>节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析<sql>节点
sqlElement(context.evalNodes("/mapper/sql"));
//解析<select> <insert> <update> <delete> 节点
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
//解析<select> <insert> <update> <delete> 节点
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//DataBaseId 在mybatis-config.xml文件中若未设置就为null
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// 创建 XMLStatementBuilder 对象
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();// 分析语句节点
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
XMLStatementBuilder
这个对象先不需要太过于的深入。理解成解析 select 或者 insert 或者 update 或者 delete 标签就会去创建一个对应的XMLStatementBuilder
对象. 创建完处理其中的属性. 如:id、parameterMap、parameterType、resultMap、 resultType…等等。然后通过这些属性创建一个MappedStatement
对象 并把 MappedStatement 加入到 Configuration 对象中 mappedStatements 属性储存.
XMLStatementBuilder
代码如下:
private final MapperBuilderAssistant builderAssistant; //命名空间助手
private final XNode context; //对应的节点
private final String requiredDatabaseId; //在 mybatis-config.xml 中未配置就为 null
//解析语句节点
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId"); //null
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); //是否刷新缓存, 如果为 select 标签不使用刷新
boolean useCache = context.getBooleanAttribute("useCache", isSelect); //select 标签使用缓存
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); //不使用结果集排序
// 在解析之前包含片段
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 参数类型
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang"); // 语言, 一般不用
LanguageDriver langDriver = getLanguageDriver(lang); //为null, 使用默认语言驱动 XMLLanguageDriver 对象
// 在include之后解析selectKey并删除它们
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; // 方法名 + "!selectKey"
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); // currentNamespace + "." + keyStatementId
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// 如果不是 insert 节点和不使用生成主键 则 KeyGenerator 为 NoKeyGenerator
// configuration.isUseGeneratedKeys() 默认 false
keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// XMLScriptBuilder 中 parseScriptNode() 方法创建
// SqlSource ==>> DynamicSqlSource : RawSqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); //创建 SqlSource 对象
// 获取节点中属性值
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize"); // 取长度
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 创建 MappedStatement 对象储存到 Configuration 中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, // 创建 MappedStatement
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
SqlSource 的创建过程分析下. 通过 XMLLanguageDriver 对象语言驱动创建 SqlSource, 而 XMLScriptBuilder 是通过是否动态创建不同的 SqlSource
对象, 如 DynamicSqlSource 对象创建需要SqlNode 对象构成.
SqlSource 接口作用:通过传入的参数对象获取到 BoundSql 语句对象
SqlNode 接口作用: 就是解析sql文本时封装的对象。 如: select * from table ⇒ TextSqlNode 如:where标签 ⇒ WhereSqlNode 如:if标签 ⇒ IfSqlNode
/**
* 封装的sql语句
*/
public class BoundSql {
private final String sql; //sql语句
private final List<ParameterMapping> parameterMappings; //定义的parameterMap 对象
private final Object parameterObject; //传入的参数对象
private final Map<String, Object> additionalParameters; //附加参数 test[0].item[0].name
private final MetaObject metaParameters;
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
public String getSql() {
return sql;
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public Object getParameterObject() {
return parameterObject;
}
//是否含附加参数名
public boolean hasAdditionalParameter(String name) {
String paramName = new PropertyTokenizer(name).getName();
return additionalParameters.containsKey(paramName);
}
public void setAdditionalParameter(String name, Object value) {
metaParameters.setValue(name, value);
}
public Object getAdditionalParameter(String name) {
return metaParameters.getValue(name);
}
}
大概思路:就是解析啥标签都会有对应的类来封装,如果来处理。 处理完统一保存到配置中心 Configuration 对象中。
上面提到的解析顺序, 先是parameterMap 在 resultMap 在 sql 标签最后时才是 insert / select / update / delete 标签这种。
我们想象一下, 解析 parameterMap 标签也有个对应的 ParameterMap 对象。 这个对象有对应的id及对应的type属性, 还有很parameter 元素标签, 就会有一个 ParameterMapping
对应的集合储存。就不贴代码了感兴趣可以去查看
ResultMap ==>> ResultMapping
//解析<resultMap>标签
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren(); // 获取所有子节点 result
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) { // constructor 标签
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 构建 ResultMapping 对象(处理完所有的 result节点)
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); //构建 ResultMapping 对象
}
}
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String extend = resultMapNode.getStringAttribute("extends"); // null
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); // null
// 分解器
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
// 创建 ResultMap 并添加到 Configuration
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
sql节点处理不同, 就是把id 处理成命名空间+"."+id 格式 如: com.pazz.dao.TestDao.testSqlId 及解析的Node对象储存给 XMLMapperBuilder 中的 Map<String, XNode> sqlFragments 属性保存.
代码不贴自行看代码
mybatis 解析篇暂时大概就这样了… 其中 Configuration 为核心 ,通过Configuration 对象构建 SqlSessionFactory 工厂对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(Configuration);
下篇为大家带来mybatis 核心逻辑处理篇…