前言 本文分享Mybatis的Configuration初始化流程,配置参考 。 重点分析XML资源文件解析和功能实现。
一、加载配置 Mybatis加载配置文件分为两个步骤:
将文件转换为资源
1 2 3 String resource = "mybatis-config.xml" ;InputStream inputStream = inputStream = Resources.getResourceAsStream(resource);
将资源解析为Configuration对象
1 2 3 XMLConfigBuilder parser = new XMLConfigBuilder (inputStream, null , null );return build(parser.parse());
二、解析Config 解析Configuration的由XMLConfigBuilder 实现。
1. XMLConfigBuilder TypeAliasRegistry:在Configuration中默认创建,配置参考 。 TypeHandlerRegistry:在Configuration中默认创建,配置参考 。
实际处理XML中Configuration 节点由**parseConfiguration(…)**方法,我们从这个方法开始。
2. 属性(properties) 配置参考 ,方便编写配置,进行动态替换,配置如下:
1 2 3 4 <properties resource ="config.properties" > <property name ="driver" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/mybatis-test?characterEncoding=utf8& zeroDateTimeBehavior=convertToNull& useSSL=false& useJDBCCompliantTimezoneShift=true& useLegacyDatetimeCode=false& serverTimezone=GMT%2B8& allowMultiQueries=true& allowPublicKeyRetrieval=true" /> </properties >
1 2 3 # config.properties username = root password = 123456
配置属性有三种方法:
直接在properties节点中定义property。
properties标签resource属性。
properties标签url属性。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 propertiesElement(root.evalNode("properties" )); private void propertiesElement (XNode context) throws Exception { if (context != null ) { Properties defaults = context.getChildrenAsProperties(); String resource = context.getStringAttribute("resource" ); String url = context.getStringAttribute("url" ); if (resource != null && url != null ) { throw new BuilderException ("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other." ); } if (resource != null ) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null ) { defaults.putAll(Resources.getUrlAsProperties(url)); } Properties vars = configuration.getVariables(); if (vars != null ) { defaults.putAll(vars); } parser.setVariables(defaults); configuration.setVariables(defaults); } }
实际上在调用evalNode()方法时已经用到了 properties :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public String getStringAttribute (String name, Supplier<String> defSupplier) { String value = attributes.getProperty(name); return value == null ? defSupplier.get() : value; } private Properties parseAttributes (Node n) { Properties attributes = new Properties (); NamedNodeMap attributeNodes = n.getAttributes(); if (attributeNodes != null ) { for (int i = 0 ; i < attributeNodes.getLength(); i++) { Node attribute = attributeNodes.item(i); String value = PropertyParser.parse(attribute.getNodeValue(), variables); attributes.put(attribute.getNodeName(), value); } } return attributes; }
3. 设置(settings) 配置参考 ,设置启动 / 关闭功能,配置如下:
1 2 3 4 <settings > <setting name ="cacheEnabled" value ="true" /> <setting name ="defaultExecutorType" value ="SIMPLE" /> </settings >
设置属性太多,以这两个举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Properties settings = settingsAsProperties(root.evalNode("settings" ));loadCustomVfs(settings); loadCustomLogImpl(settings); settingsElement(settings); private Properties settingsAsProperties (XNode context) { if (context == null ) { return new Properties (); } Properties props = context.getChildrenAsProperties(); MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException ("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)." ); } } return props; }
4. 类型别名(typeAliases) 配置参考 ,设置缩写的别名,配置如下:
1 2 3 4 <typeAliases > <typeAlias alias ="student" type ="com.example.entity.StudentEntity" /> <package name ="com.example.entity" /> </typeAliases >
XMLConfigBuilder UML图中展示了Configuration 类中有一个成员变量:TypeAliasRegistry ,我相信大家已经想到了Mybatis是如何做的? 但需要一点注意,别名注册有两种形式:
package:该包名下Java Bean都会注册别名(具体请看配置参考)。
typeAlias:单个类注册别民。
Mybatis已经帮我注册了一些常用的别名,如:int、long…。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 typeAliasesElement(root.evalNode("typeAliases" )); private void typeAliasesElement (XNode parent) { if (parent != null ) { for (XNode child : parent.getChildren()) { 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" ); try { Class<?> clazz = Resources.classForName(type); if (alias == null ) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException ("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } }
5. 类型处理器(typeHandlers) 配置参考 ,TypeHandler相对复杂,用于Statement参数映射、ResultSet返回处理。将Java Type 与 JDBC Type相互转换,配置如下:
1 2 3 <typeHandlers > <typeHandler handler ="com.example.handler.ExampleTypeHandler" /> </typeHandlers >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @MappedJdbcTypes(JdbcType.VARCHAR) public class ExampleTypeHandler extends BaseTypeHandler<String > { @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName); } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex); } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex); } }
和类型别名 大体相同,同样支持两种方式,注册与类型别名 相差无几,所以在此省略。
6. 对象工厂(objectFactory) 配置参考 ,DefaultResultSetHandler 会在处理结果集 时使用对象工厂创建返回对象,配置如下:
1 2 3 <objectFactory type ="com.example.handler.ExampleObjectFactory" > <property name ="someProperty" value ="100" /> </objectFactory >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class ExampleObjectFactory extends DefaultObjectFactory { @Override public Object create (Class type) { return super .create(type); } @Override public void setProperties (Properties properties) { System.out.println(properties); super .setProperties(properties); } @Override public <T> boolean isCollection (Class<T> type) { return Collection.class.isAssignableFrom(type); } } {someProperty=100 }
ObjectFactory 比较简单,默认实现为DefaultObjectFactory 。
1 2 3 4 5 6 7 8 9 10 11 12 private void objectFactoryElement (XNode context) throws Exception { if (context != null ) { String type = context.getStringAttribute("type" ); Properties properties = context.getChildrenAsProperties(); ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance(); factory.setProperties(properties); configuration.setObjectFactory(factory); } }
7. 插件(plugins) 配置参考 ,Mybatis plugin提供的功能非常强大,可以实现相当丰富的功能。如:PageHelper(分页工具),强烈建议参考文档学习,对于一些特定的场景有奇效。配置如下:
1 2 3 4 <plugins > <plugin interceptor ="com.github.pagehelper.PageInterceptor" > </plugin > </plugins >
以PageHelper为例,注意plugin节点中可以和objectFactory 一样配置property 标签,我在这里并没有体现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void pluginElement (XNode parent) throws Exception { if (parent != null ) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor" ); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } }
8. environments(环境配置) 配置参考 ,提供三个功能:
配置事务管理器
提供事务管理,比如:Executor 提供commit 、rollback 等事务方法,实际上由Transaction 执行。
配置数据源
配置数据源,可选择具体数据库连接池(这里默认Mybatis自带)。
支持切换环境
发布不同版本,切换不同的环境。 配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="${driver}" /> <property name ="url" value ="${url}" /> <property name ="username" value ="${username}" /> <property name ="password" value ="${password}" /> </dataSource > </environment > </environments >
根据设置默认环境,选择读取哪一个环境。 创建事务管理工厂、数据源工厂,设值进configuration成员中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void environmentsElement (XNode context) throws Exception { if (context != null ) { if (environment == null ) { environment = context.getStringAttribute("default" ); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("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.setEnvironment(environmentBuilder.build()); } } } }
9. databaseIdProvider(数据库厂商标识) 配置参考 ,根据不同数据库,可以选择不同的MappedStatement,不是很好用。配置如下:
1 2 3 4 5 6 7 8 9 10 11 <databaseIdProvider type ="DB_VENDOR" > <property name ="MySQL" value ="mysql" /> <property name ="Oracle" value ="oracle" /> </databaseIdProvider > <select id ="selectOne" resultType ="com.example.entity.StudentEntity" databaseId ="mysql" > select id, name from student </select > <select id ="selectOne" resultType ="com.example.entity.StudentEntity" databaseId ="oracle" > select id, name from student </select >
只能设置单个数据源databaseId,如果想用多数据源,得创建多个SqlSessionFactory做切换。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 databaseIdProviderElement(root.evalNode("databaseIdProvider" )); private void databaseIdProviderElement (XNode context) throws Exception { DatabaseIdProvider databaseIdProvider = null ; if (context != null ) { String type = context.getStringAttribute("type" ); if ("VENDOR" .equals(type)) { type = "DB_VENDOR" ; } Properties properties = context.getChildrenAsProperties(); databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance(); databaseIdProvider.setProperties(properties); } Environment environment = configuration.getEnvironment(); if (environment != null && databaseIdProvider != null ) { String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource()); configuration.setDatabaseId(databaseId); } }
10. mappers(映射器) 配置参考 ,告诉Mybatis去哪里寻找Mapper文件进行注册。配置如下:
1 2 3 4 5 6 7 <mappers > <mapper resource ="StudentMapper.xml" /> <mapper url ="file:///var/mappers/BlogMapper.xml" /> <mapper class ="org.mybatis.builder.BlogMapper" /> <package name ="org.mybatis.builder" /> </mappers >
11. autoMapping(自动映射) 可以区分为两大类:
XML:resource(资源路径)、url(URL)。
Annotation:class(类路径)、package(包名下)。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 mapperElement(root.evalNode("mappers" )); 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." ); } } } } }
到此处,整个MybatisConfig全部完成工作,下面开始解析Mapper。
三、解析Mapper 1. XMLMapperBuilder parse 方法与XMLConfigBuilder有所不同。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void parse () { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper" )); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
2. cache(缓存) 配置参考 ,Mybatis一级缓存默认开启,二级缓存需要手动配置开启。 区别: 一级缓存:作用域Statement,多个Statement不共享。 二级缓存:作用域namespace,同一个namespace中Statement共享同一个缓存。有缓存淘汰机制,支持定制化操作。 配置如下:
二级缓存使用条件较为苛刻,一般不启动,以最简单的默认配置进行分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cacheElement(context.evalNode("cache" )); private void cacheElement (XNode context) { if (context != null ) { String type = context.getStringAttribute("type" , "PERPETUAL" ); Class<? extends Cache > typeClass = typeAliasRegistry.resolveAlias(type); String eviction = context.getStringAttribute("eviction" , "LRU" ); Class<? extends Cache > evictionClass = typeAliasRegistry.resolveAlias(eviction); Long flushInterval = context.getLongAttribute("flushInterval" ); Integer size = context.getIntAttribute("size" ); boolean readWrite = !context.getBooleanAttribute("readOnly" , false ); boolean blocking = context.getBooleanAttribute("blocking" , false ); Properties props = context.getChildrenAsProperties(); builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }
3. cache-ref(缓存引用) 配置参考 ,使多个namespace共用一个cache。有几个要注意的地方:
cache配置会覆盖cache-ref(由于执行顺序cache-ref总是被先执行)
如果使用cache-ref,如果引用Mapper在此时还没有初始化,解析cache-ref、解析Statement将在后续的错误尝试中执行。
配置如下:
1 <cache-ref namespace ="com.example.mapper.TestMapper" />
涉及到缓存依赖问题,如果依赖的缓存还没有创建怎么办? Mybatis选择在后续处理,而不是JVM / SpringBean 加载类时进行交叉加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 cacheRefElement(context.evalNode("cache-ref" )); private void cacheRefElement (XNode context) { if (context != null ) { configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace" )); CacheRefResolver cacheRefResolver = new CacheRefResolver (builderAssistant, context.getStringAttribute("namespace" )); try { cacheRefResolver.resolveCacheRef(); } catch (IncompleteElementException e) { configuration.addIncompleteCacheRef(cacheRefResolver); } } } public Cache resolveCacheRef () { if (namespace == null ) { throw new BuilderException ("cache-ref element requires a namespace attribute." ); } try { unresolvedCacheRef = true ; Cache cache = configuration.getCache(namespace); if (cache == null ) { throw new IncompleteElementException ("No cache for namespace '" + namespace + "' could be found." ); } currentCache = cache; unresolvedCacheRef = false ; return cache; } catch (IllegalArgumentException e) { throw new IncompleteElementException ("No cache for namespace '" + namespace + "' could be found." , e); } }
4. resultMap(结果映射) 配置参考 ,查询返回结果集进行映射。支持复杂结果处理,有四种方式:
constructor: 调用构造方法
association: 一对一
collection: 一对多
discriminator: 根据匹配字段的value,使用结果值来决定使用哪个 resultMapresult
1. 支持功能 默认的子节点:
1 2 <id property ="id" column ="id" /> <result property ="name" column ="name" />
id :一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能result :注入到字段或 JavaBean 属性的普通结果
1. constructor 1 2 3 4 5 6 7 <resultMap id ="map" type ="com.example.entity.StudentEntity" > <constructor > <idArg column ="id" name ="id" javaType ="long" /> <arg column ="name" name ="name" javaType ="string" /> </constructor > </resultMap >
constructor:拥有idArg、arg两个子节点, 在对结果集进行映射后,会调用构造方法进行创建对象。 需要注意一点:如果使用了该功能,java一般情况下反射是无法获取到形参名称,有两种解决方案:
添加 @Param 注解
1 2 3 4 5 public StudentEntity (@Param("id") Long id, @Param("name") String name) { this .id = id; this .name = name; }
开启编译选项**-parameter**
1 2 3 4 5 6 7 8 9 10 11 12 13 <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <configuration > <source > 1.8</source > <target > 1.8/</target > <compilerArgs > <arg > -parameters</arg > </compilerArgs > </configuration > </plugin > </plugins >
2. association 江湖人称:一对一关联关系,可嵌套。
1 2 3 4 5 <association property ="child" javaType ="com.example.entity.T4Entity" > <id property ="id" column ="id5" /> <result property ="username" column ="name5" /> </association > <!ELEMENT association (constructor ?,id *,result *,association *,collection *, discriminator ?)>
3. collection 江湖人称:一对多关联关系,可嵌套。
1 2 3 4 <collection property ="childList" ofType ="com.example.entity.T5Entity" > <result property ="id" column ="id5" /> <result property ="username" column ="name5" /> </collection >
4.discriminator 监听器:根据字段的值case匹配的value返回不同的对象。
1 2 3 4 5 <discriminator javaType ="long" column ="id" > <case value ="3" resultType ="com.example.entity.T1Entity" /> <case value ="2" resultType ="com.example.entity.T2Entity" /> <case value ="1" resultMap ="map" /> </discriminator >
2.功能解析 1. 解析resultMap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 private ResultMap resultMapElement (XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) { 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 <>(additionalResultMappings); List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { if ("constructor" .equals(resultChild.getName())) { 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); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } String id = resultMapNode.getStringAttribute("id" , resultMapNode.getValueBasedIdentifier()); String extend = resultMapNode.getStringAttribute("extends" ); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping" ); ResultMapResolver resultMapResolver = new ResultMapResolver (builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
2. 处理constructor 1 2 3 4 5 6 7 8 9 10 11 12 13 private void processConstructorElement (XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) { List<XNode> argChildren = resultChild.getChildren(); for (XNode argChild : argChildren) { List<ResultFlag> flags = new ArrayList <>(); flags.add(ResultFlag.CONSTRUCTOR); if ("idArg" .equals(argChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags)); } }
3. 处理 / 构建鉴别器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private Discriminator processDiscriminatorElement (XNode context, Class<?> resultType, List<ResultMapping> resultMappings) { String column = context.getStringAttribute("column" ); String javaType = context.getStringAttribute("javaType" ); String jdbcType = context.getStringAttribute("jdbcType" ); String typeHandler = context.getStringAttribute("typeHandler" ); Class<?> javaTypeClass = resolveClass(javaType); Class<? extends TypeHandler <?>> typeHandlerClass = resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); Map<String, String> discriminatorMap = new HashMap <>(); for (XNode caseChild : context.getChildren()) { String value = caseChild.getStringAttribute("value" ); String resultMap = caseChild.getStringAttribute("resultMap" , processNestedResultMappings(caseChild, resultMappings, resultType)); discriminatorMap.put(value, resultMap); } return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap); }
4. 处理 id / result / collection 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 private ResultMapping buildResultMappingFromContext (XNode context, Class<?> resultType, List<ResultFlag> flags) { String property; if (flags.contains(ResultFlag.CONSTRUCTOR)) { property = context.getStringAttribute("name" ); } else { 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.emptyList(), resultType)); 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); Class<? extends TypeHandler <?>> typeHandlerClass = resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); } private String processNestedResultMappings (XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) { if (Arrays.asList("association" , "collection" , "case" ).contains(context.getName()) && context.getStringAttribute("select" ) == null ) { validateCollection(context, enclosingType); ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType); return resultMap.getId(); } return null ; }
5. 构建ResultMapping 不必太过研究,只需要有哪些成员有个印象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public ResultMapping buildResultMapping ( Class<?> resultType, String property, String column, Class<?> javaType, JdbcType jdbcType, // 需要select执行 MappedStatement String nestedSelect, // 需要注意一下,resultSetHandler解析需要嵌套执行 String nestedResultMap, String notNullColumn, String columnPrefix, Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags, String resultSet, String foreignColumn, boolean lazy) { Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType); TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler); List<ResultMapping> composites; if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) { composites = Collections.emptyList(); } else { composites = parseCompositeColumnName(column); } return new ResultMapping .Builder(configuration, property, column, javaTypeClass) .jdbcType(jdbcType) .nestedQueryId(applyCurrentNamespace(nestedSelect, true )) .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true )) .resultSet(resultSet) .typeHandler(typeHandlerInstance) .flags(flags == null ? new ArrayList <>() : flags) .composites(composites) .notNullColumns(parseMultipleColumnNames(notNullColumn)) .columnPrefix(columnPrefix) .foreignColumn(foreignColumn) .lazy(lazy) .build(); }
3. 发现问题 当constructor 元素和discriminator case 元素中resultMap 属性同时使用,会导致创建resultMap , 获取构造方法名称没有类型导致NPE。 已经提交PR到mybatis github ,目前还没有同意。https://github.com/mybatis/mybatis-3/pull/2353
5. sql 配置参考 ,可被其它语句引用的可重用语句块。
1 <sql id="userColumns" > ${alias}.id,${alias}.username,${alias}.password </sql>
一般使用都是存放数据库的所有字段:
1 2 3 <sql id ="baseColumn" > t1.id, t1.account, t1.nick_name, t1.phone, t1.email, t1.pwd_reset_time, t1.avatar_name, t1.avatar_path, t1.status </sql >
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private final Map<String, XNode> sqlFragments;private void sqlElement (List<XNode> list) { if (configuration.getDatabaseId() != null ) { sqlElement(list, configuration.getDatabaseId()); } sqlElement(list, null ); } private void sqlElement (List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { String databaseId = context.getStringAttribute("databaseId" ); String id = context.getStringAttribute("id" ); id = builderAssistant.applyCurrentNamespace(id, false ); if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) { sqlFragments.put(id, context); } } }
四、解析Statement 配置参考 ,解析Statement是在Mapper中解析的,由于篇幅过长,并且Statement很重要所有单独开一个小结。Statement :select、insert、update、delete四大标签,对于执行器来说只有Query 和Update 方法。具体的配置就不在阐述了。
1. 解析CRUD元素 1 buildStatementFromContext(context.evalNodes("select|insert|update|delete" ));
2. 解析Statement节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 public void parseStatementNode () { String id = context.getStringAttribute("id" ); String databaseId = context.getStringAttribute("databaseId" ); 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); boolean useCache = context.getBooleanAttribute("useCache" , isSelect); 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); processSelectKeyNodes(id, parameterTypeClass, langDriver); 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; } SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); 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); if (resultSetTypeEnum == null ) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } String keyProperty = context.getStringAttribute("keyProperty" ); String keyColumn = context.getStringAttribute("keyColumn" ); String resultSets = context.getStringAttribute("resultSets" ); builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } public MappedStatement addMappedStatement ( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { 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; }
五、总结 照着官网走了一遍解析配置的流程,总体下来写的不是很满意,太粗糙了。 但是大部分常用的功能还是研究了,resultMap交叉解析这一块花了大功夫研究,遇到了很多问题,还碰见了Mybatis的BUG。 语言表达能力、写作能力有待训练,下面着重研究缓存和结果集解析功能。 后续可能会修改