@select注解_MyBatis@SelectProvider注解源码分析

260c2af5-e122-48ae-aeec-535006b7178f

@SelectProvider注解用法

写一个简单的@SelectProvider的用法,新建class类,添加一个根据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中提供了一个很简单的查询方法,根据userId返回user对象,里面就是用了一个StringBuffer对象来拼接一个SQL语句,我想更多的是想用MyBatis中的SQL Builder的写法,SQL Builder写法在官方网站地址为http://www.mybatis.org/mybatis-3/zh/statement-builders.html,不得不说SQL Builder的写法确实比较漂亮,很工整,不过也是看自己运用的熟练程度吧。

UserMapper:

@ResultMap("BaseResultMap")@SelectProvider(type = SelectSqlProvider.class, method = "selectByUserId")User getUserByUserId(long id);

mapper中的其他方法就不贴出来了,需要说的就是这一个,这一个方法在xml中没有对应的sql,在该方法上也没有@Select注解修饰,只有@SelectProvider注解,@SelectProvider中两个属性,type为提供sql的class类,method为指定方法。

对应Mapper的调用与结果在这就不再分析了,就是简单的返回user对象,下文将是对@SelectProvider注解作用的详解。

2. @SelectProvider源码分析

说起Select查询,基本就又是回到我们先前那几篇文章说的了,@SelectProvider注解加载问题,之前的文章中说了如何在解析xml之后解析注解中的SQL,这一种无非换了种样式,从由注解提供改为了从class类中单独写方法提供SQL,我们来看下相关源码实现。

这里就还要回到mapper的解析处,回到开始的parseConfiguration方法中mapperElement。

mapperElement(root.evalNode("mappers"));

这一行在解析xml文件之后,最后进行了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保存进knownMappers中,并且还进行了注解Mapper的解析,从而实现了对注解sql的加载,同时**@SelectProvider**也是在这里进行加载的。

knownMappers.put(type, new MapperProxyFactory(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;

进入到parse方法中,parse方法最终转到parseStatement方法,在parseStatement方法中,在获取SqlSource对象时,对method方法进行了进一步的解析。

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); } }

这里可以加上断点,对我们上面写的代码调试一下,如下图。

![image-20190108071518992](/Users/xiaxuan/Library/Application Support/typora-user-images/image-20190108071518992.png)

到这一步就是对@SelectProvider注解的解析,可以看到此时的method方法为getUserByUserId。type类型为UserMapper等等。我们继续进入到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等参数进行了实例化与赋值,最后返回sqlSource对象。

此处得到的可以说还不是原有的sql,所以在Select查询的时候,还要继续追踪看一下到底是如何执行sql的,这就要继续回到Select查询方法了,在前面很多文章中知最后查询调用基本都是调用的selectList方法,此处还是要从这里分析开始。

@Override public  List 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(); } }

进入到executor.query方法中,executor的实现有两种,一种是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; }

这里一般就是实例化为Simple类型,但是如果cacheEnable字段为true的话,返回CachingExecutor对象。而cacheEnable字段算得上是之前漏说了的一个属性,这个是在loadSettings时进行初始化的,而如果没有设置cacheEnable字段时,默认设置为true,如下:

private void settingsElement(Properties props) throws Exception { configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值