在mybatis中sql的来源一般分位两种方式:
详细总结图
1.写在xml文件中,如:
2.通过注解写入sql,如:
在此文中我们将研究mybatis是如何获取他们,并把他们放在哪。
从上一篇博客(maybatis源码分析(二)——mybatis mapper的注册流程_zhaoliubao1的博客-CSDN博客)我们可以知道,mybatis首先会读取配置文件,并最终构建出mapper接口的代理工厂类,此时我们的mapper接口的相关信息已经被获取,那么接下来我们就要具体去寻找mapper接口对应的方法的sql。有两种情况,一种是写在xml文件中,另一种写通过注解的方式去记录将要执行的sql。
我们先研究mybatis是如何解析注解中携带的sql语句:
源码如下:
public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { //注入该接口对应的代理工厂类 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); //------解析sql----// parser.parse(); //------解析sql----// loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
其实我们可以看到,解析sql的时候我们需要传入该类的信息(文件路径)其实就是mapper注册的key值。
接下来便是真正解析sql了:
public void parse() { String resource = type.toString(); //防止重复解析 if (!configuration.isResourceLoaded(resource)) { //通过xml去解析sql loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); //获取改接口的所有方法 遍历解析sql 组装ResultMap for (Method method : type.getMethods()) { if (!canHaveStatement(method)) { continue; } if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent() && method.getAnnotation(ResultMap.class) == null) { parseResultMap(method); } try { //解析sql的入口 parseStatement(method); } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); }
先看注解解析入口代码:
//注解解析sql的入口 parseStatement(method);
拨开云雾我们终于看到了SqlSource 这便是sql源了
void parseStatement(Method method) { //获取该方法的返回值 final Class<?> parameterTypeClass = getParameterType(method); final LanguageDriver languageDriver = getLanguageDriver(method); getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> { //这里便是最终得到的sql源代码了 final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method); final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType(); final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options)x.getAnnotation()).orElse(null); final String mappedStatementId = type.getName() + "." + method.getName(); final KeyGenerator keyGenerator; String keyProperty = null; String keyColumn = null; ..... }
首先我们需要先了解一下这个方法:
private Optional<AnnotationWrapper> getAnnotationWrapper(Method method, boolean errorIfNoMatch, Collection<Class<? extends Annotation>> targetTypes) { String databaseId = configuration.getDatabaseId(); Map<String, AnnotationWrapper> statementAnnotations = targetTypes.stream() .flatMap(x -> Arrays.stream(method.getAnnotationsByType(x))).map(AnnotationWrapper::new) .collect(Collectors.toMap(AnnotationWrapper::getDatabaseId, x -> x, (existing, duplicate) -> { throw new BuilderException(String.format("Detected conflicting annotations '%s' and '%s' on '%s'.", existing.getAnnotation(), duplicate.getAnnotation(), method.getDeclaringClass().getName() + "." + method.getName())); })); ...... }
这段代码的意思就是:将某个方法的注解何预定的注解进行匹配,并返回。
接下来我们将研究他是如何获取并赋值给 SqlSource对象的,也就是:
final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
我们继续深入可以发现:
private SqlSource buildSqlSource(Annotation annotation, Class<?> parameterType, LanguageDriver languageDriver, Method method) { if (annotation instanceof Select) { return buildSqlSourceFromStrings(((Select) annotation).value(), parameterType, languageDriver); } else if (annotation instanceof Update) { return buildSqlSourceFromStrings(((Update) annotation).value(), parameterType, languageDriver); } else if (annotation instanceof Insert) { return buildSqlSourceFromStrings(((Insert) annotation).value(), parameterType, languageDriver); } else if (annotation instanceof Delete) { return buildSqlSourceFromStrings(((Delete) annotation).value(), parameterType, languageDriver); } else if (annotation instanceof SelectKey) { return buildSqlSourceFromStrings(((SelectKey) annotation).statement(), parameterType, languageDriver); } return new ProviderSqlSource(assistant.getConfiguration(), annotation, type, method); }
通过先前getAnnotationWrapper我们已经得到了某个方法对应的注解,那在此处我们就可以知道这个注解的具体信息
我们看其中一段代码:
private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) { return languageDriver.createSqlSource(configuration, String.join(" ", strings).trim(), parameterTypeClass); }
继续进入:
@Override public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) { // issue #3 if (script.startsWith("<script>")) { XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver()); return createSqlSource(configuration, parser.evalNode("/script"), parameterType); } else { // issue #127 script = PropertyParser.parse(script, configuration.getVariables()); TextSqlNode textSqlNode = new TextSqlNode(script); if (textSqlNode.isDynamic()) { return new DynamicSqlSource(configuration, textSqlNode); } else { return new RawSqlSource(configuration, script, parameterType); } } }
通过我们前面的讲解 我们已经知道了 sql来源 ,所以如果我么的来源是注解并且没有<script>标签那么他将会走else逻辑,比如我们现在拿到了一条这样的sql:
他究竟会怎么做呢?
我们进入他的解析器一探究竟:
public static String parse(String string, Properties variables) { VariableTokenHandler handler = new VariableTokenHandler(variables); GenericTokenParser parser = new GenericTokenParser("${", "}", handler); return parser.parse(string); }
我们可以看到 这个解析器主要是用于处理含有$符号的sql,含有#号的sql是通RawSqlSource
类来处理,所以我们将会看到:
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) { SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> clazz = parameterType == null ? Object.class : parameterType; sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>()); }
继续进入将会看到:
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); String sql; if (configuration.isShrinkWhitespacesInSql()) { sql = parser.parse(removeExtraWhitespaces(originalSql)); } else { sql = parser.parse(originalSql); } return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); }
通过我们的通用解析器,最终我们将会得到:
自此我们将得到了每个statement的目标sql