標籤:highlight nal ram parent fragments etc 沒有 methods 名稱
目前Mybatis除了可以通過XML配置SQL外還可以通過註解的形式配置SQL,本文中主要介紹了Mybatis是如何處理註解SQL映射的,通過源碼分析處理過程
XML配置
<configuration><settings><setting name="defaultExecutorType" value="SIMPLE"/><setting name="useGeneratedKeys" value="true"/></settings><typeAliases><typeAlias type="org.apache.ibatis.submitted.blocking_cache.Person" alias="Person" /> </typeAliases><environments default="development"><environment id="development"><transactionManager type="JDBC"><property name="" value="" /></transactionManager><dataSource type="UNPOOLED"><property name="driver" value="org.hsqldb.jdbcDriver" /><property name="url" value="jdbc:hsqldb:mem:cache" /><property name="username" value="sa" /></dataSource></environment></environments><mappers><mapper class="org.apache.ibatis.submitted.blocking_cache.PersonMapper"/></mappers></configuration>
解析過程
private void mapperElement(XNode parent) throws Exception { //如果configuration中配置了mapper節點 if (parent != null) { for (XNode child : parent.getChildren()) { //如果配置對是包路徑 if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { //擷取mapper元素中的resource屬性 String resource = child.getStringAttribute("resource"); //擷取mapper元素中的url屬性 String url = child.getStringAttribute("url"); //擷取mapper元素中的class屬性,如果基於註解的配置的mapper 配置的就是class String mapperClass = child.getStringAttribute("class"); //對於resource,url,mapperClass 優先使用resource,其次是url最後是class if (resource != null && url == null && mapperClass == null) { //建立異常上下文 ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); //建立XMLMapperBuilder XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //解析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) { //擷取mapper的介面 Class<?> mapperInterface = Resources.classForName(mapperClass); //將該介面註冊到已知mapper configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); }
//註冊Mapper public <T> void addMapper(Class<T> type) { //如果type是介面 if (type.isInterface()) { //判斷該介面是否已經註冊過相應的Mapper了,如果是則拋出異常,因為knownMappers的key為type if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { //對該介面建立MapperProxyFactory,並保註冊到knownMappers knownMappers.put(type, new MapperProxyFactory<T>(type)); //建立MapperAnnotationBuilder MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); //解析Annotation parser.parse(); //解析成功則表示載入完成 loadCompleted = true; } finally { //如果載入沒有完成則將其從knownMappers中刪除 if (!loadCompleted) { knownMappers.remove(type); } } } }
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace(‘.‘, ‘/‘) + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
//初始化註解類型,分為兩組
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}
public void parse() {
String resource = type.toString();
//判斷該資源是否已經註冊
if (!configuration.isResourceLoaded(resource)) {
//沒有註冊過則需要載入XML資源
loadXmlResource();
//將該資源名稱添加到已經註冊集合中
configuration.addLoadedResource(resource);
//設定nameSpace
assistant.setCurrentNamespace(type.getName());
//解析cache
parseCache();
//解析cacheRef
parseCacheRef();
//擷取
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
//如果不是bridge方法則解析statement
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
//解析待定方法
parsePendingMethods();
}
private void loadXmlResource() { //判斷資源是否有載入過 if (!configuration.isResourceLoaded("namespace:" + type.getName())) { //擷取資源的path String xmlResource = type.getName().replace(‘.‘, ‘/‘) + ".xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e) { // ignore, resource is not required } //如果資源存在 if (inputStream != null) { //構建XMLMapperBuilder XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); //解析XML xmlParser.parse(); } } }
private void parseCache() { //擷取介面上 @CacheNamespace註解 CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class); //如果註解存在 if (cacheDomain != null) { //擷取註解配置的緩衝大小 Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size(); //擷取緩衝重新整理頻率 Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval(); //解析CacheNamespace配置的屬性 Properties props = convertToProperties(cacheDomain.properties()); //使用註解配置的資料建立緩衝 assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props); } }
private void parseCacheRef() { //擷取介面上 @CacheNamespaceRef 註解 CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class); //如果配置了緩衝引用 if (cacheDomainRef != null) { //擷取引用的類型 Class<?> refType = cacheDomainRef.value(); String refName = cacheDomainRef.name(); //如果參考型別和引用名稱都為空白則拋出異常 if (refType == void.class && refName.isEmpty()) { throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef"); } //如果參考型別和引用名稱同時配置了有效資料則拋出異常,這兩個是互斥資料 if (refType != void.class && !refName.isEmpty()) { throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef"); } //擷取namespace String namespace = (refType != void.class) ? refType.getName() : refName; //使用緩衝 assistant.useCacheRef(namespace); } }
void parseStatement(Method method) { //擷取參數類型 Class<?> parameterTypeClass = getParameterType(method); //擷取LanguageDriver LanguageDriver languageDriver = getLanguageDriver(method); //從註解中擷取Sqlsource SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver); //如果sqlSource不為null if (sqlSource != null) { //擷取 @Options註解 Options options = method.getAnnotation(Options.class); //建立statementId 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; //是否需要重新整理緩衝,如果不是查詢預設值為true,查詢預設值為false boolean flushCache = !isSelect; //是否使用緩衝,查詢預設為true,不是查詢預設為false boolean useCache = isSelect; KeyGenerator keyGenerator; String keyProperty = "id"; String keyColumn = null; //如果是插入或更新 if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { //擷取SelectKey註解 SelectKey selectKey = method.getAnnotation(SelectKey.class); if (selectKey != null) { keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver); keyProperty = selectKey.keyProperty(); } else if (options == null) { keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } else { keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; keyProperty = options.keyProperty(); keyColumn = options.keyColumn(); } } else {//不是插入或更新 即查詢和刪除則keyGenerator為NoKeyGenerator執行個體 keyGenerator = NoKeyGenerator.INSTANCE; } //如果@Options註解不為null if (options != null) { //根據配置的值設定是否重新整理緩衝 if (FlushCachePolicy.TRUE.equals(options.flushCache())) { flushCache = true; } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) { flushCache = false; } //是否使用緩衝 useCache = options.useCache(); //fetchSize 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(); } String resultMapId = null; //擷取ResultMap註解 ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class); if (resultMapAnnotation != null) { String[] resultMaps = resultMapAnnotation.value(); StringBuilder sb = new StringBuilder(); for (String resultMap : resultMaps) { if (sb.length() > 0) { sb.append(","); } sb.append(resultMap); } resultMapId = sb.toString(); } else if (isSelect) { resultMapId = parseResultMap(method); } assistant.addMappedStatement( mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout, // ParameterMapID null, parameterTypeClass, resultMapId, getReturnType(method), resultSetType, flushCache, useCache, // TODO gcode issue #577 false, keyGenerator, keyProperty, keyColumn, // DatabaseID null, languageDriver, // ResultSets options != null ? nullOrEmpty(options.resultSets()) : null); } }
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) { try { //擷取@Select, @Insert, @Update, @Delete類型的註解 Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method); //擷取 @SelectProvider, @InsertProvider, @UpdateProvider @DeleteProvider註解 Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method); //如果SQL註解不為null if (sqlAnnotationType != null) { //同時sqlProvider註解也不為空白則拋出異常,兩者互斥 if (sqlProviderAnnotationType != null) { throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName()); } //擷取註解 Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType); //擷取註解配置的值 final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation); //根據配置的資料建立SqlSource return buildSqlSourceFromStrings(strings, parameterType, languageDriver); } else if (sqlProviderAnnotationType != null) {//如果SqlProvider註解不為空白 Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType); //建立一個ProviderSqlSource return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method); } //如果沒有配置Sql註解也沒有配置SqlProvider註解則返回null return null; } catch (Exception e) { throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e); } }
Mybatis 基於註解Mapper源碼分析