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,settingstypeAliasespluginsobjectFactoryobjectWrapperFactoryreflectorFactoryenvironmentsdatabaseIdProvidertypeHandlers - 解析
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()) {//packageif ("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.xmlInputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());//解析xxx/mapper.xmlmapperParser.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);//使用接口的模式,xxxMapperconfiguration.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设置namespacebuilderAssistant.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);}//discriminatorelse if ("discriminator".equals(resultChild.getName())) {discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);} else {List<ResultFlag> flags = new ArrayList<>();//idif ("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() {//获取节点idString id = context.getStringAttribute("id");//数据库标示,99的情况是nulString 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,arrayString 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);//NodeincludeParser.applyIncludes(context.getNode());//解析 selectKey节点processSelectKeyNodes(id, parameterTypeClass, langDriver);//解析 xml/annotation 获取sqlSqlSource 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
