Spring Boot啓動時,會去實例化jar包mybatis-spring-boot-autoconfigure-starter中MybatisAutoConfiguration中的配置bean,包括SqlSessionFactory、SqlSessionTemplate。流程圖以下:java
其中,設置typeHandler以及xml配置文件是在sqlSessionFactory方法中完成。node
其中,xml配置文件包路徑指定方式以下:spring
mybatis.mapperLocations=classpath:mapper/**/*.xml
properties.resolveMapperLocations()會將mybatis.mapperLocations指定的路徑(classpath:mapper/**/*.xml)下的全部符合規則的文件都解析成一個個的資源。具體代碼以下:sql
MyBatis的xml配置文件解析方法調用鏈以下:apache
MybatisAutoConfiguration.sqlSessionFactory()--->SqlSessionFactoryBean.getObject()--->SqlSessionFactoryBean.afterPropertiesSet()--->SqlSessionFactoryBean.buildSqlSessionFactory()mybatis
在SqlSessionFactoryBean的buildSqlSessionFactory方法中解析xml文件的代碼以下:app
TypeHandler在mybatis進行sql操做過程當中,起到重要做用,主要是在數據插入(更新)與查詢時,對指定的字段進行數據轉換。開發中,常常使用,在mybatis啓動過程當中,也會去加載TypeHandler,這裏會加載默認的TypeHandler和自定義的TypeHandler。spring-boot
自定義的TypeHandler包路徑經過mybatis.type-handlers-package來指定,在mybatis啓動過程當中,會去讀取包下面的TypeHandler。處理代碼在SqlSessionFactoryBean.buildSqlSessionFactory()中,代碼片斷以下:fetch
if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Stream.of(typeHandlersPackageArray).forEach(packageToScan -> { targetConfiguration.getTypeHandlerRegistry().register(packageToScan); LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers"); }); }
TypeHadnlerRegistry.register方法就是去註冊(加載)TypeHandler,將java type與對應的TypeHandler放入指定的map中。代碼以下:ui
public void register(String packageName) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName); Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses(); for (Class<?> type : handlerSet) { //Ignore inner classes and interfaces (including package-info.java) and abstract classes if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { register(type); } } } public void register(Class<?> typeHandlerClass) { boolean mappedTypeFound = false; MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> javaTypeClass : mappedTypes.value()) { register(javaTypeClass, typeHandlerClass); mappedTypeFound = true; } } if (!mappedTypeFound) { register(getInstance(null, typeHandlerClass)); } } private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { if (javaType != null) { Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType); if (map == null || map == NULL_TYPE_HANDLER_MAP) { map = new HashMap<JdbcType, TypeHandler<?>>(); TYPE_HANDLER_MAP.put(javaType, map); } map.put(jdbcType, handler); } ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); }
默認的TypeHandler加載入口爲:
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments())
這裏面會調用org.apache.ibatis.builder.BaseBuilder#BaseBuilder方法,代碼以下:
新建TypeHandlerRegistry時,就會默認加載全部默認的TypeHandler,以下:
public TypeHandlerRegistry() { register(Boolean.class, new BooleanTypeHandler()); ...... register(java.sql.Date.class, new SqlDateTypeHandler()); register(java.sql.Time.class, new SqlTimeTypeHandler()); register(java.sql.Timestamp.class, new SqlTimestampTypeHandler()); // mybatis-typehandlers-jsr310 if (Jdk.dateAndTimeApiExists) { this.register(Instant.class, InstantTypeHandler.class); this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class); this.register(LocalDate.class, LocalDateTypeHandler.class); this.register(LocalTime.class, LocalTimeTypeHandler.class); this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class); this.register(OffsetTime.class, OffsetTimeTypeHandler.class); this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class); this.register(Month.class, MonthTypeHandler.class); this.register(Year.class, YearTypeHandler.class); this.register(YearMonth.class, YearMonthTypeHandler.class); this.register(JapaneseDate.class, JapaneseDateTypeHandler.class); } // issue #273 register(Character.class, new CharacterTypeHandler()); register(char.class, new CharacterTypeHandler()); }
這樣就完成了默認TypeHandler的加載了。
xml配置文件的解析始於org.apache.ibatis.builder.xml.XMLMapperBuilder#parse。代碼以下:
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
其中configurationElement(parser.evalNode("/mapper"))服務xml的解析工做。parser.evalNode("/mapper")的意思就是解析xml中的mapper節點。UserMapperExt.xml文件的內容以下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.iwill.mybatis.dao.mapper.ext.UserMapperExt"> <resultMap id="BaseResultMap" type="com.iwill.mybatis.dao.model.UserDTO"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="age" jdbcType="INTEGER" property="age" /> </resultMap> <select id="findUserListByName" parameterType="java.lang.String" resultMap="BaseResultMap"> SELECT * FROM USER WHERE name =${name} </select> </mapper>
其中解析mapper節點,就是解析整個xml了。方法configurationElement的代碼以下:
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); 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); } }
第一句:String namespace = context.getStringAttribute("namespace")就是獲取namespace的值,這裏就是com.iwill.mybatis.dao.mapper.ext.UserMapperExt。
parameterMapElement(context.evalNodes("/mapper/parameterMap"))就是解析全部的parameterMap標籤, resultMapElements(context.evalNodes("/mapper/resultMap"))就是解析全部的resultMap標籤。這裏咱們詳細瞭解一下解析resultMap的過程。
具體的解析方法爲:
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); String extend = resultMapNode.getStringAttribute("extends"); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); Class<?> typeClass = resolveClass(type); Discriminator discriminator = null; List<ResultMapping> resultMappings = new ArrayList<ResultMapping>(); resultMappings.addAll(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<ResultFlag>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
上述代碼中,解析出來的id就是標籤resultMap的id,即爲:BaseResultMap;type即爲resultMap的type,解析出得type=com.iwill.mybatis.dao.model.UserDTO。以後就開始解析resultMap的子標籤了。
<resultMap id="BaseResultMap" type="com.iwill.mybatis.dao.model.UserDTO"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="age" jdbcType="INTEGER" property="age" /> </resultMap>
具體解析子標籤的方法爲:org.apache.ibatis.builder.xml.XMLMapperBuilder#buildResultMappingFromContext,其代碼爲:
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 = 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 = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); }
其中id的解析中間值以下:
進一步解析在以下方法中:
public ResultMapping buildResultMapping( Class<?> resultType, String property, String column, Class<?> javaType, JdbcType jdbcType, String nestedSelect, 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 = 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<ResultFlag>() : flags) .composites(composites) .notNullColumns(parseMultipleColumnNames(notNullColumn)) .columnPrefix(columnPrefix) .foreignColumn(foreignColumn) .lazy(lazy) .build(); }
其中:Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType)會找出id屬性對應的java type,若是參數javaType有值,就使用參數javaType的值,若是參數javaType沒值,就解析resultType(即com.iwill.mybatis.dao.model.UserDTO)
中對應的屬性id的類型。
private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) { if (javaType == null && property != null) { try { MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory()); javaType = metaResultType.getSetterType(property); } catch (Exception e) { //ignore, following null check statement will deal with the situation } } if (javaType == null) { javaType = Object.class; } return javaType; }
後面會給id設置對應的TypeHandler。解析id標籤的獲得的resultMapping以下:
解析後獲得的resultMap以下:
buildStatementFromContext(context.evalNodes("select|insert|update|delete"))會去解析文件中的select、insert、update、detele等標籤。負責解析的方法爲:org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode:
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)) ? 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); }
其中,SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass)這個會解析出對應的sql。
進一步的解析過程當中,就調用以下代碼:
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; }
這裏的MappedStatement在後面com.iwill.mybatis.dao.mapper.ext.UserMapperExt#findUserListByName方法被調用到的時候,會被調用到。其值爲:
其id爲:com.iwill.mybatis.dao.mapper.ext.UserMapperExt.findUserListByName,這個值會和mapper中的方法映射在一塊兒,即和com.iwill.mybatis.dao.mapper.ext.UserMapperExt#findUserListByName會映射在一塊兒。
configuration.addMappedStatement(statement)的做用就是將MappedStatement放在一個map中,key爲MappedStatement的id,值爲MappedStatement的對象,這樣存儲後,方便後面mapper中方法調用時,能夠根據id方便的找到對應的MappedStatement。
xml中的其餘標籤的解析相似。
至此,mybatis xml的解析就完成了。