MyBatis中的@SelectProvider注解源碼分析
@SelectProvider注解用法
寫一個簡單的@SelectProvider的用法,新建class類,添加一個根據(jù)userId查詢user的方法。
SelectSqlProvider
public class SelectSqlProvider { public String selectByUserId(Long id) { StringBuffer buffer = new StringBuffer(); buffer.append("SELECT * FROM user where "); buffer.append("id = ").append(id).append(";"); return buffer.toString(); } }
SelectSqlProvider中提供了一個很簡單的查詢方法,根據(jù)userId返回user對象,里面就是用了一個StringBuffer對象來拼接一個SQL語句,我想更多的是想用MyBatis中的SQL Builder的寫法,SQL Builder寫法在官方網(wǎng)站地址為//www.mybatis.org/mybatis-3/zh/statement-builders.html,不得不說SQL Builder的寫法確實(shí)比較漂亮,很工整,不過也是看自己運(yùn)用的熟練程度吧。
UserMapper
@ResultMap("BaseResultMap") @SelectProvider(type = SelectSqlProvider.class, method = "selectByUserId") User getUserByUserId(long id);
mapper中的其他方法就不貼出來了,需要說的就是這一個,這一個方法在xml中沒有對應(yīng)的sql,在該方法上也沒有@Select注解修飾,只有@SelectProvider注解,@SelectProvider中兩個屬性,type為提供sql的class類,method為指定方法。
對應(yīng)Mapper的調(diào)用與結(jié)果在這就不再分析了,就是簡單的返回user對象,下文將是對@SelectProvider注解作用的詳解。
@SelectProvider源碼分析
說起Select查詢,基本就又是回到我們先前那幾篇文章說的了,@SelectProvider注解加載問題,之前的文章中說了如何在解析xml之后解析注解中的SQL,這一種無非換了種樣式,從由注解提供改為了從class類中單獨(dú)寫方法提供SQL,我們來看下相關(guān)源碼實(shí)現(xiàn)。
這里就還要回到mapper的解析處,回到開始的parseConfiguration方法中mapperElement。
mapperElement(root.evalNode("mappers"));
這一行在解析xml文件之后,最后進(jìn)行了addMapper操作。
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."); } } } } }
但是從前文中我們知addMapper操作不僅將mapper保存進(jìn)knownMappers中,并且還進(jìn)行了注解Mapper的解析,從而實(shí)現(xiàn)了對注解sql的加載,同時**@SelectProvider**也是在這里進(jìn)行加載的。
knownMappers.put(type, new MapperProxyFactory<T>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true;
進(jìn)入到parse方法中,parse方法最終轉(zhuǎn)到parseStatement方法,在parseStatement方法中,在獲取SqlSource對象時,對method方法進(jìn)行了進(jìn)一步的解析。
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver); private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) { try { Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method); Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method); if (sqlAnnotationType != null) { 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); return buildSqlSourceFromStrings(strings, parameterType, languageDriver); } else if (sqlProviderAnnotationType != null) { Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType); return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method); } return null; } catch (Exception e) { throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e); } }
這里可以加上斷點(diǎn),對我們上面寫的代碼調(diào)試一下
到這一步就是對@SelectProvider注解的解析,可以看到此時的method方法為getUserByUserId。type類型為UserMapper等等。我們繼續(xù)進(jìn)入到ProviderSqlSource中,看看是如何組裝sql的。
public ProviderSqlSource(Configuration configuration, Object provider, Class<?> mapperType, Method mapperMethod) { String providerMethodName; try { this.configuration = configuration; this.sqlSourceParser = new SqlSourceBuilder(configuration); this.providerType = (Class<?>) provider.getClass().getMethod("type").invoke(provider); providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider); for (Method m : this.providerType.getMethods()) { if (providerMethodName.equals(m.getName()) && CharSequence.class.isAssignableFrom(m.getReturnType())) { if (providerMethod != null){ throw new BuilderException("Error creating SqlSource for SqlProvider. Method '" + providerMethodName + "' is found multiple in SqlProvider '" + this.providerType.getName() + "'. Sql provider method can not overload."); } this.providerMethod = m; this.providerMethodArgumentNames = new ParamNameResolver(configuration, m).getNames(); this.providerMethodParameterTypes = m.getParameterTypes(); } } } catch (BuilderException e) { throw e; } catch (Exception e) { throw new BuilderException("Error creating SqlSource for SqlProvider. Cause: " + e, e); } if (this.providerMethod == null) { throw new BuilderException("Error creating SqlSource for SqlProvider. Method '" + providerMethodName + "' not found in SqlProvider '" + this.providerType.getName() + "'."); } for (int i = 0; i< this.providerMethodParameterTypes.length; i++) { Class<?> parameterType = this.providerMethodParameterTypes[i]; if (parameterType == ProviderContext.class) { if (this.providerContext != null){ throw new BuilderException("Error creating SqlSource for SqlProvider. ProviderContext found multiple in SqlProvider method (" + this.providerType.getName() + "." + providerMethod.getName() + "). ProviderContext can not define multiple in SqlProvider method argument."); } this.providerContext = new ProviderContext(mapperType, mapperMethod); this.providerContextIndex = i; } } }
此處對sqlSourceParser與providerType、providerMethodName等參數(shù)進(jìn)行了實(shí)例化與賦值,最后返回sqlSource對象。 此處得到的可以說還不是原有的sql,所以在Select查詢的時候,還要繼續(xù)追蹤看一下到底是如何執(zhí)行sql的,這就要繼續(xù)回到Select查詢方法了,在前面很多文章中知最后查詢調(diào)用基本都是調(diào)用的selectList方法,此處還是要從這里分析開始。
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
進(jìn)入到executor.query方法中,executor的實(shí)現(xiàn)有兩種,一種是BaseExecutor,一種是CacheingExecutor,而這種的初始化條件為openSession中的newExecutor方法。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
這里一般就是實(shí)例化為Simple類型,但是如果cacheEnable字段為true的話,返回CachingExecutor對象。
而cacheEnable字段算得上是之前漏說了的一個屬性,這個是在loadSettings時進(jìn)行初始化的,而如果沒有設(shè)置cacheEnable字段時,默認(rèn)設(shè)置為true,如下:
private void settingsElement(Properties props) throws Exception { configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE"))); configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))) }
在說完BaseExecutor和CacheingExecutor之后,此處繼續(xù)回到query方法。
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
在query方法中獲取到boundSql對象,此處可以調(diào)試一下代碼,看看boundSql中有什么參數(shù)。
此處已經(jīng)完成了sql的組裝,繼續(xù)getBoundSql看看進(jìn)行了什么操作。
public BoundSql getBoundSql(Object parameterObject) { BoundSql boundSql = sqlSource.getBoundSql(parameterObject); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.isEmpty()) { boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject); } // check for nested result maps in parameter mappings (issue #30) for (ParameterMapping pm : boundSql.getParameterMappings()) { String rmId = pm.getResultMapId(); if (rmId != null) { ResultMap rm = configuration.getResultMap(rmId); if (rm != null) { hasNestedResultMaps |= rm.hasNestedResultMaps(); } } } return boundSql; }
還需要繼續(xù)追溯sqlSource.getBoundSql(parameterObject),此處SqlSource毫無疑問為ProviderSqlSource類。
@Override public BoundSql getBoundSql(Object parameterObject) { SqlSource sqlSource = createSqlSource(parameterObject); return sqlSource.getBoundSql(parameterObject); } private SqlSource createSqlSource(Object parameterObject) { try { int bindParameterCount = providerMethodParameterTypes.length - (providerContext == null ? 0 : 1); String sql; if (providerMethodParameterTypes.length == 0) { sql = invokeProviderMethod(); } else if (bindParameterCount == 0) { sql = invokeProviderMethod(providerContext); } else if (bindParameterCount == 1 && (parameterObject == null || providerMethodParameterTypes[(providerContextIndex == null || providerContextIndex == 1) ? 0 : 1].isAssignableFrom(parameterObject.getClass()))) { sql = invokeProviderMethod(extractProviderMethodArguments(parameterObject)); } else if (parameterObject instanceof Map) { @SuppressWarnings("unchecked") Map<String, Object> params = (Map<String, Object>) parameterObject; sql = invokeProviderMethod(extractProviderMethodArguments(params, providerMethodArgumentNames)); } else { throw new BuilderException("Error invoking SqlProvider method (" + providerType.getName() + "." + providerMethod.getName() + "). Cannot invoke a method that holds " + (bindParameterCount == 1 ? "named argument(@Param)": "multiple arguments") + " using a specifying parameterObject. In this case, please specify a 'java.util.Map' object."); } Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); return sqlSourceParser.parse(replacePlaceholder(sql), parameterType, new HashMap<String, Object>()); } catch (BuilderException e) { throw e; } catch (Exception e) { throw new BuilderException("Error invoking SqlProvider method (" + providerType.getName() + "." + providerMethod.getName() + "). Cause: " + e, e); } }
createSqlSource方法有些意思,對參數(shù)個數(shù)進(jìn)行了校驗(yàn),如果是沒有參數(shù),直接執(zhí)行invokeProviderMethod()方法,如果是一個則進(jìn)行傳參,如果多個判斷當(dāng)前類型是否是Map類型,否則拋錯,等會倒是可以測試一下,這里看下invokeProviderMethod方法。
private String invokeProviderMethod(Object... args) throws Exception { Object targetObject = null; if (!Modifier.isStatic(providerMethod.getModifiers())) { targetObject = providerType.newInstance(); } CharSequence sql = (CharSequence) providerMethod.invoke(targetObject, args); return sql != null ? sql.toString() : null; }
invokeProviderMethod方法其實(shí)就沒多少可說的了,對當(dāng)前方法、對象進(jìn)行了一個反射獲取值的操作,從而拿到對應(yīng)sql。
在獲取到sql之后剩下的執(zhí)行就和常規(guī)的是一樣的了,這里就不再繼續(xù)說后面的東西了。
到此這篇關(guān)于MyBatis中的@SelectProvider注解源碼分析的文章就介紹到這了,更多相關(guān)@SelectProvider注解源碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在VSCode里使用Jupyter?Notebook調(diào)試Java代碼的詳細(xì)過程
Jupyter Notebook是以網(wǎng)頁的形式打開,可以在網(wǎng)頁頁面中直接編寫代碼和運(yùn)行代碼,代碼的運(yùn)行結(jié)果也會直接在代碼塊下顯示的程序,這篇文章主要介紹了在VSCode里使用Jupyter?Notebook,調(diào)試Java代碼,需要的朋友可以參考下2022-07-07spring security在分布式項(xiàng)目下的配置方法(案例詳解)
這篇文章主要介紹了spring security在分布式項(xiàng)目下的配置方法,本文通過一個項(xiàng)目案例給大家詳細(xì)介紹,通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10通過Java實(shí)現(xiàn)在Word中創(chuàng)建可填充表單
這篇文章主要為大家詳細(xì)介紹了如何通過Java代碼,以編程方式在Word中創(chuàng)建可填充表單,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-03-03Maven倉庫無用文件和文件夾清理的方法實(shí)現(xiàn)
這篇文章主要介紹了Maven倉庫無用文件和文件夾清理的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12springboot項(xiàng)目整合注冊功能模塊開發(fā)實(shí)戰(zhàn)
這篇文章主要介紹了springboot項(xiàng)目整合注冊功能模塊開發(fā)實(shí)戰(zhàn),在用戶的注冊是首先需要查詢當(dāng)前的用戶名是否存在,如果存在則不能進(jìn)行注冊,相當(dāng)于一個查詢語句,本文通過實(shí)例代碼詳細(xì)講解,需要的朋友可以參考下2022-11-11Spring boot進(jìn)行參數(shù)校驗(yàn)的方法實(shí)例詳解
這篇文章主要介紹了Spring boot進(jìn)行參數(shù)校驗(yàn)的方法實(shí)例詳解,非 常不錯,具有參考借鑒價值,需要的朋友參考下吧2018-05-05springboot項(xiàng)目打包并部署到Tomcat上及報錯處理方案
這篇文章主要介紹了springboot項(xiàng)目打包并部署到Tomcat上及報錯處理方案,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-08-08java利用pdfbox+poi往pdf插入數(shù)據(jù)
這篇文章主要給大家介紹了關(guān)于java利用pdfbox+poi如何往pdf插入數(shù)據(jù)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2022-02-02Java使用pdfbox實(shí)現(xiàn)給pdf文件加圖片水印
有時候需要給pdf加水印,市面上工具都是收費(fèi)的要會員,還是自食其力吧;嘗試過 spire.pdf.free 那個超過10頁就不行了!所以本文還是使用了pdfbox,感興趣的可以了解一下2022-11-11