MapperAnnotationBuilder(org.apache.ibatis.builder.annotation.MapperAnnotationBuilder),mapper註解構建器。java
它的職責很簡單,就是解析指定的mapper接口對應的Class對象中,包含的全部mybatis框架中定義的註解,並生成Cache、ResultMap、MappedStatement三種類型對象。
sql
MapperAnnotationBuilder是以Class.toString()方法生成的字符串,做爲Class對象的惟一標識的,在解析完Class對象後,會調用Configuration.addLoadedResource()方法把這個字符串加入配置對象的內部集合中,以防止重複解析。apache
MapperAnnotationBuilder總會優先解析xml配置文件,而且這個xml配置文件必須與Class對象所在的包路徑一致,且文件名要與類名一致。在解析完xml配置文件後,纔會開始解析Class對象中包含的註解。數組
源碼解讀:
緩存
MapperAnnotationBuilder數據結構以下:數據結構
// 只有4個元素:@Select、@Insert、@Update、@Delete,sql語句保存在註解中 private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>(); // 只有4個元素:@SelectProvider、@InsertProvider、@UpdateProvider、@DeleteProvider // sql語句保存在註解指定的類的指定方法中 private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>(); // 核心配置對象 private Configuration configuration; // Mapper構建助手,用於組裝解析出來的配置,生成Cache、ResultMap、MappedStatement等對象, // 並添加到Configuration配置對象中 private MapperBuilderAssistant assistant; // 要解析的目標mapper接口的Class對象 private Class<?> type;
MapperAnnotationBuilder解析邏輯以下:mybatis
public void parse() { // Class對象的惟一標識,如: // TopicMapper對應的字符串爲"interface com.lixin.mapper.TopicMapper" String resource = type.toString(); // 若是當前Class對象已經解析過,則不在解析 if (!configuration.isResourceLoaded(resource)) { // 加載並解析指定的xml配置文件,Class所在的包對應文件路徑,Class類名對應文件名稱,如: // com.lixin.mapper.TopicMapper類對應的配置文件爲com/lixin/mapper/TopicMapper.xml loadXmlResource(); // 把Class對應的標識添加到已加載的資源列表中 configuration.addLoadedResource(resource); // 設置當前namespace爲接口Class的全限定名 assistant.setCurrentNamespace(type.getName()); // 解析緩存對象 parseCache(); // 解析緩存引用,會覆蓋以前解析的緩存對象 parseCacheRef(); // 獲取全部方法,解析方法上的註解,生成MappedStatement和ResultMap Method[] methods = type.getMethods(); // 遍歷全部獲取到的方法 for (Method method : methods) { try { // 解析一個方法生成對應的MapperedStatement對象 // 並添加到配置對象中 parseStatement(method); } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } // 解析掛起的方法 parsePendingMethods(); }
loadXmlResource()方法,就是在解析指定的xml配置文件。
app
這裏存在一個須要注意的命名問題:框架
XMLMapperBuilder每解析一個xml配置文件,都會以文件所在路徑爲xml文件的惟一標識,並把標識添加到已加載的資源文件列表中(如:com/lixin/mapper/topicMapper.xml)。可是loadXmlResource()方法中避免重複加載檢查的key的倒是"namespace:"+類全限定名的格式。ide
private void loadXmlResource() { // 若是已加載資源列表中指定key已存在,則再也不解析xml文件 // 資源名稱爲namepace:全限定名 if (!configuration.isResourceLoaded("namespace:" + type.getName())) { // 根據Class對象生成xml配置文件路徑 String xmlResource = type.getName().replace('.', '/') + ".xml"; InputStream inputStream = null; try { // 獲取文件字節流 inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e) { // ignore, resource is not required } // 若是xml文件存在,則建立XMLMapperBuilder進行解析 if (inputStream != null) { XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); xmlParser.parse(); } } }
之因此使用這種格式,是由於當前並不知道xml文件的真實名稱是什麼,與Class全限定名只是約定(換句話說,xml文件路徑徹底能夠不遵循這種約定)。所以,爲了不重複加載,XMLMapperBuilder在解析完配置文件後,會調用bindMapperForNamespace()方法,嘗試加載配置文件中根元素的namespace屬性獲取Class對象,而且添加"namespace:"+全限定名格式的額外的key到已加載資源列表中,來通知MapperAnnotationBuilder。
解析緩存對象:
private void parseCache() { // 獲取接口上的@CacheNamespace註解 CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class); // 若是存在該註解,則調用構建助手建立緩存對象 if (cacheDomain != null) { assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), cacheDomain.flushInterval(), cacheDomain.size(), cacheDomain.readWrite(), null); } }
@CacheNamespace註解對應mapper.xml配置文件中的<cache>元素,可是註解方式不支持properties自定義屬性的配置。
解析緩存引用:
private void parseCacheRef() { // 獲取接口上的@CacheNamespaceRef註解 CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class); // 若是存在該註解,則調用構建助手添加引用關係, // 以Class對象的全限定名爲目標namespace if (cacheDomainRef != null) { assistant.useCacheRef(cacheDomainRef.value().getName()); } }
@CacheNamespaceRef註解對應mapper.xml配置文件中的<cache-ref namespace=""/>元素。
解析MappedStatement和ResultMap:
MapperAnnotationBuilder會遍歷Class對象中的全部方法,一個Method對象對應一個MappedStatement對象,ResultMap的定義與xml配置文件方式不一樣,配置文件由單獨的<resultMap>元素定義,而註解方式則定義在方法上。每一個方法能夠建立新的ResultMap對象,也能夠引用已經存在的ResultMap對象的id。
void parseStatement(Method method) { // 獲取輸入參數的類型,這個參數沒用, // 由於如今都是以輸入參數對象爲根,經過ognl表達式尋值的 Class<?> parameterTypeClass = getParameterType(method); // 經過方法上的@Lang註解獲取語言驅動 LanguageDriver languageDriver = getLanguageDriver(method); // 經過方法上的@Select等註解獲取SqlSource SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver); // 若是成功建立了SqlSource,則繼續 if (sqlSource != null) { // 獲取方法上的@Options註解 Options options = method.getAnnotation(Options.class); // 映射語句id爲類的全限定名.方法名 final String mappedStatementId = type.getName() + "." + method.getName(); Integer fetchSize = null; Integer timeout = null; StatementType statementType = StatementType.PREPARED; ResultSetType resultSetType = ResultSetType.FORWARD_ONLY; // 經過註解獲取Sql命令類型 SqlCommandType sqlCommandType = getSqlCommandType(method); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = !isSelect; boolean useCache = isSelect; KeyGenerator keyGenerator; String keyProperty = "id"; String keyColumn = null; // 若是是insert或update命令 if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { // 首先檢查@SelectKey註解 ,它會覆蓋任何其餘的配置 // 獲取方法上的SelectKey註解 SelectKey selectKey = method.getAnnotation(SelectKey.class); // 若是存在@SelectKey註解 if (selectKey != null) { keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver); keyProperty = selectKey.keyProperty(); } else { if (options == null) { keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); } else { keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); keyProperty = options.keyProperty(); keyColumn = options.keyColumn(); } } } else { // 其餘sql命令均沒有鍵生成器 keyGenerator = new NoKeyGenerator(); } if (options != null) { flushCache = options.flushCache(); useCache = options.useCache(); fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; // issue #348 timeout = options.timeout() > -1 ? options.timeout() : null; statementType = options.statementType(); resultSetType = options.resultSetType(); } //////////////////////////////////////////////////////////////////////////// // 處理方法上的@ResultMap註解 String resultMapId = null; // 獲取註解,@ResultMap註解表明引用已經存在的resultMap對象的id ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class); // 若是方法上存在@ResultMap註解,則生成引用id便可 if (resultMapAnnotation != null) { // 獲取指定的多個resultMapId String[] resultMaps = resultMapAnnotation.value(); StringBuilder sb = new StringBuilder(); // 遍歷String數組,拼接爲一個String,逗號分隔 for (String resultMap : resultMaps) { if (sb.length() > 0) sb.append(","); sb.append(resultMap); } resultMapId = sb.toString(); // 不存在@ResultMap註解,且語句爲select類型, // 則經過解析@Args、@Results等註解生成新的ResultMap對象 } else if (isSelect) { // 生成新的ResultMap對象,加入Configuration配置對象 // 同時返回resultMapId resultMapId = parseResultMap(method); } // 構建MappedStatement並添加到配置對象中 assistant.addMappedStatement(mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout, null, // ParameterMapID parameterTypeClass, resultMapId, // ResultMapID getReturnType(method), resultSetType, flushCache, useCache, false, // TODO // issue // #577 keyGenerator, keyProperty, keyColumn, null, languageDriver, null); } }
從代碼邏輯能夠看出,若是不存在@Select、@Insert、@Update、@Delete或者對應的@xxxProvider中的任何一個,則後續註解全是無效,只有@Lang會起做用。當使用@ResultMap註解引用已存在的結果映射時,後續關於建立新的結果映射的註解將失效。
解析註解生成新的ResultMap對象邏輯以下:
private String parseResultMap(Method method) { // 獲取目標bean的類型 Class<?> returnType = getReturnType(method); // 獲取方法上的@ConstructorArgs註解 ConstructorArgs args = method.getAnnotation(ConstructorArgs.class); // 獲取方法上的@Results Results results = method.getAnnotation(Results.class); // 獲取方法上的@TypeDiscriminator註解 TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class); // 根據方法生成resultMap的惟一標識,格式爲:類全限定名.方法名-參數類型簡單名稱 String resultMapId = generateResultMapName(method); // resultMapId和返回值類型已經解析完畢, // 再解析剩下的構造方法映射、屬性映射和鑑別器,以後添加結果映射到配置對象中 applyResultMap(resultMapId, returnType, argsIf(args), resultsIf(results), typeDiscriminator); return resultMapId; }
註解方式只支持屬性映射時使用另外的select語句,不支持嵌套的屬性映射的配置。
源碼的重點邏輯已經講解完畢,具體的註解與配置文件元素的一一對應關係,並無逐一解釋,若是讀者有疑問能夠留言。另外關於註解的配置,我將會單獨講解。
最後給一點建議,註解配置是xml方式配置的子集,且配置後不易修改和查看,所以仍是建議用戶使用xml的方式來配置mapper。