mybatis 源代码
mybatis
的使用非常的简单,简单的加载一下配置文件即可实现对数据库的 curd
操作,如下所示
public class App {
public static void main(String[] args) throws Exception {
String resource = "config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(inputStream, null, null);
try (SqlSession sqlSession = factory.openSession(true)) {
int statement = sqlSession.insert("statement", "12");
}
}
}
在这个简单的示例代码的背后,mybatis
为我们做了些什么,怎么做的。
配置文件说明
- 加载并解析配置文件
配置文件分为2部分,分别是 configuration.xml
和 mapper.xml
部分,核心代码
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(inputStream, null, null);
简单一行即可实现 xml
的解析。内部解析顺序如下
- 获取
configuration.xml
(不一定是这个名字) - 解析
configuration.xml
获取Configuration
对象 - 分别解析
configuration.xml
里边的properties
,settings
typeAliases
plugins
objectFactory
objectWrapperFactory
reflectorFactory
environments
databaseIdProvider
typeHandlers
- 解析
mapper.xml
集合
第三步的一般只会使用
typeAliases
一项,其他的基本上使用默认设置或者Spring整和之后的设置
解析配置文件
解析代码 —> SqlSessionFactoryBuilder#build
public SqlSessionFactory build(Reader reader, @Nullable String environment, @Nullable Properties properties) {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
Configuration configuration = parser.parse(); //核心
return build(configuration);
}
核心代码 ——> XMLConfigBuilder#parse
public Configuration parse() {
//获取root节点 configuration并解析
XNode xNode = parser.evalNode("/configuration");
parseConfiguration(xNode);
return configuration;
}
解析节点配置信息,XMLConfigBuilder#parseConfiguration , 解析之前需要说明—mybatis
的Configuration对象是全局唯一的,解析配置文件本质是为了获取 Configuration
实例对象,即 使用xml中的节点信息去填充 Configuration 实例中对应的字段
,例如别名注册,其实就是一个Map<别名,class>。
private void parseConfiguration(XNode root) {
try {
//先解析properties文件,和Spring整合之后我还没有看到使用的,我司主要使用xbatis+mysql
//基本不用,maven的穿透更加好用
propertiesElement(root.evalNode("properties"));
//解析settings配置,我是一般没有用
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//别名,这个是需要的,简化后续的statement,简单
typeAliasesElement(root.evalNode("typeAliases"));
//插件,执行拦截使用,特殊需求可能会用到
pluginElement(root.evalNode("plugins"));
//一下节点,一般没有使用过
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mappers节点,mybatis核心
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
解析别名节点
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//package 扫描包下的bean,进行注册
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
//单独注册
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
// this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); , 填充 Configuration 的 typeAliasRegistry 字段
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
}
}
}
}
解析 mapper.xml 文件
解析 mapper.xml
这里以 mapper resource = “xxxxx” 为例
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//package
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage); //扫描 mapper
} else {
//resource,url class 只能同时存在一个
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);
//读取xx_mapper.xml
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//解析xxx/mapper.xml
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);
//使用接口的模式,xxxMapper
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
开始解析
public void parse() {
//查看是不是已经被加载过了
if (!configuration.isResourceLoaded(resource)) {
//解析mapper root节点,以/开头,解析基本的
configurationElement(parser.evalNode("/mapper"));
//添加到loadedResources中,set集合
configuration.addLoadedResource(resource);
//搭建命名空间 namespace 需要唯一
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
解析 mapper.xml 的属性
/**
* 解析过程还是和configuration差不多,一个节点一个节点解析
*/
private void configurationElement(XNode context) {
try {
//获取namespace属性 , namespace 不能为null的,也不能为empty的
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 节点 cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select*,+*表示可多个
//当前mapper设置namespace
builderAssistant.setCurrentNamespace(namespace);
//缓存引用,和cache两个节点使用的都不多,可以说基本没用过
cacheRefElement(context.evalNode("cache-ref"));
//缓存
cacheElement(context.evalNode("cache"));
//主要使用的不多,目前见到的是少部分使用场景,1、获取parameterMap节点群,>=0个节点,2、解析节点
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//用的最多,也是mybatis的核心
resultMapElements(context.evalNodes("/mapper/resultMap"));
//sql,必使用,动态sql依赖
sqlElement(context.evalNodes("/mapper/sql"));
//curd 4个节点,绑定使用
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);
}
}
解析resultMap
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
//正在解析那个mapper文件,如果发生异常,将抛出这个ErrorContext中的内容,这种思路可以学习
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//resultMap 没有id会自动产生一个,强烈建议设置一个,dtd文件的验证一般都是有id属性的,确保万无一失,xbatis团队还是加了这个
String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
//type类型是必须的,应该是dtd改了,为了兼容以前的情况加上了这套逻辑
String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
//是否继承了哪个resultMap,复用
String extend = resultMapNode.getStringAttribute("extends");
//是否自动映射
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
//所有字节点
List<XNode> resultChildren = resultMapNode.getChildren();
//constructor?,id*,result*,association*,collection*, discriminator?
for (XNode resultChild : resultChildren) {
//构造器
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
}
//discriminator
else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<>();
//id
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
//核心还是搭建ResultMapping,一个resultMapping的节点 对应了 一个ResultMapping的实例
ResultMapping resultMapping = buildResultMappingFromContext(resultChild, typeClass, flags);
resultMappings.add(resultMapping);
}
}
//一个完整的 resultMapping 就搭建完成了
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
buildResultMappingFromContext 方法
/** resultMap 所有的节点属性都在这里了
* @param context resultMapping 字节点,
* @param resultType resultMapping 对应的类型,这个类型一般都是简化过的
* @param flags flags
*/
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property;
//有没有构造器,目前从我工作角度来看,几乎没有使用到构造器的地方,但是肯定有某些场景是使用这个的
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
//property 节点
property = context.getStringAttribute("property");
}
//改属性对应的类名,获取所有的属性
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",processNestedResultMappings(context, Collections.<ResultMapping>emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
//除非有什么特殊的不行的业务需求,才需要加入自定义的类型处理器,最终还是放到 configuration 实例当中
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
解析 4个 curd
节点
//解析curd节点,属于解析当中最为复杂的部
public void parseStatementNode() {
//获取节点id
String id = context.getStringAttribute("id");
//数据库标示,99的情况是nul
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//-----------------节点属性解析
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
//parameterMap parameterType 这两个我用起来一直不知道他们的区别是什么
String parameterMap = context.getStringAttribute("parameterMap");
//map 已经被移除了,但是type还是用的比较多的,例如常用的hashmap,map,list,array
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
//同理还有这两个,区分度要大一些,但是新人还是会迷糊,虽然xbatis的文档不错
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子节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
//Node
includeParser.applyIncludes(context.getNode());
//解析 selectKey节点
processSelectKeyNodes(id, parameterTypeClass, langDriver);
//解析 xml/annotation 获取sql
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))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
到达这一步,mybatis xml文件就解析完成了,基本上只大概的谈了一下一些基本的属性的解析,不过只要能够理解 mybatis 的解析过程,后续的执行还是蛮好理解的,而且 mybatis 的源代码相对来说还是较为简单的。sql 节点没有说明,因为这里还是和设置参数结合起来讲述会简单一下,如果直接过一篇,反而没有多大的效果。
2018-10-12