diff --git a/pom.xml b/pom.xml index d0e8532d..a279bcf3 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,7 @@ - 3.5.19 + 3.6.0-SNAPSHOT 3.1.3.RELEASE @@ -214,4 +214,12 @@ + + + sonatype-oss-snapshots + Sonatype OSS Snapshots Repository + https://central.sonatype.com/repository/maven-snapshots/ + + + diff --git a/src/main/java/org/mybatis/scripting/thymeleaf/ThymeleafLanguageDriver.java b/src/main/java/org/mybatis/scripting/thymeleaf/ThymeleafLanguageDriver.java index 38d1caa6..9ec40de7 100644 --- a/src/main/java/org/mybatis/scripting/thymeleaf/ThymeleafLanguageDriver.java +++ b/src/main/java/org/mybatis/scripting/thymeleaf/ThymeleafLanguageDriver.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2022 the original author or authors. + * Copyright 2018-2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.parsing.XNode; +import org.apache.ibatis.reflection.ParamNameResolver; import org.apache.ibatis.scripting.LanguageDriver; import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; import org.apache.ibatis.session.Configuration; @@ -84,7 +85,7 @@ public ParameterHandler createParameterHandler(MappedStatement mappedStatement, */ @Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) { - return createSqlSource(configuration, script.getNode().getTextContent(), parameterType); + return createSqlSource(configuration, script.getNode().getTextContent(), parameterType, null); } /** @@ -92,7 +93,19 @@ public SqlSource createSqlSource(Configuration configuration, XNode script, Clas */ @Override public SqlSource createSqlSource(Configuration configuration, String script, Class parameterType) { - return new ThymeleafSqlSource(configuration, sqlGenerator, script.trim(), parameterType); + return createSqlSource(configuration, script, parameterType, null); + } + + @Override + public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType, + ParamNameResolver paramNameResolver) { + return createSqlSource(configuration, script.getNode().getTextContent(), parameterType, paramNameResolver); + } + + @Override + public SqlSource createSqlSource(Configuration configuration, String script, Class parameterType, + ParamNameResolver paramNameResolver) { + return new ThymeleafSqlSource(configuration, sqlGenerator, script.trim(), parameterType, paramNameResolver); } } diff --git a/src/main/java/org/mybatis/scripting/thymeleaf/ThymeleafSqlSource.java b/src/main/java/org/mybatis/scripting/thymeleaf/ThymeleafSqlSource.java index 3d26f4ea..4c24a03e 100644 --- a/src/main/java/org/mybatis/scripting/thymeleaf/ThymeleafSqlSource.java +++ b/src/main/java/org/mybatis/scripting/thymeleaf/ThymeleafSqlSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2022 the original author or authors. + * Copyright 2018-2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,12 @@ package org.mybatis.scripting.thymeleaf; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -26,10 +29,14 @@ import java.util.Set; import java.util.function.BiFunction; +import org.apache.ibatis.builder.ParameterMappingTokenHandler; import org.apache.ibatis.builder.SqlSourceBuilder; import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.parsing.GenericTokenParser; import org.apache.ibatis.reflection.MetaClass; +import org.apache.ibatis.reflection.ParamNameResolver; import org.apache.ibatis.scripting.xmltags.DynamicContext; import org.apache.ibatis.session.Configuration; import org.thymeleaf.context.IContext; @@ -53,9 +60,9 @@ private static class TemporaryTakeoverKeys { private final Configuration configuration; private final SqlGenerator sqlGenerator; - private final SqlSourceBuilder sqlSourceBuilder; private final String sqlTemplate; private final Class parameterType; + private final ParamNameResolver paramNameResolver; /** * Constructor for for integrating with template engine provide by Thymeleaf. @@ -69,13 +76,23 @@ private static class TemporaryTakeoverKeys { * @param parameterType * A parameter type that specified at mapper method argument or xml element */ + ThymeleafSqlSource(Configuration configuration, SqlGenerator sqlGenerator, String sqlTemplate, Class parameterType, + ParamNameResolver paramNameResolver) { + this.configuration = configuration; + this.sqlGenerator = sqlGenerator; + this.sqlTemplate = sqlTemplate; + this.parameterType = parameterType; + this.paramNameResolver = paramNameResolver; + } + + @Deprecated ThymeleafSqlSource(Configuration configuration, SqlGenerator sqlGenerator, String sqlTemplate, Class parameterType) { this.configuration = configuration; this.sqlGenerator = sqlGenerator; this.sqlTemplate = sqlTemplate; this.parameterType = parameterType; - this.sqlSourceBuilder = new SqlSourceBuilder(configuration); + this.paramNameResolver = null; } /** @@ -90,20 +107,33 @@ public BoundSql getBoundSql(Object parameterObject) { processingParameterType = parameterType; } - DynamicContext dynamicContext = new DynamicContext(configuration, parameterObject); - Map customVariables = dynamicContext.getBindings(); + Map bindings = new HashMap<>(); + bindings.put(DynamicContext.PARAMETER_OBJECT_KEY, parameterObject); + bindings.put(DynamicContext.DATABASE_ID_KEY, configuration.getDatabaseId()); + + Map customVariables = bindings; customVariables.put(TemporaryTakeoverKeys.CONFIGURATION, configuration); - customVariables.put(TemporaryTakeoverKeys.DYNAMIC_CONTEXT, dynamicContext); + customVariables.put(TemporaryTakeoverKeys.DYNAMIC_CONTEXT, bindings); customVariables.put(TemporaryTakeoverKeys.PROCESSING_PARAMETER_TYPE, processingParameterType); - String sql = sqlGenerator.generate(sqlTemplate, parameterObject, dynamicContext::bind, customVariables); + String sql = sqlGenerator.generate(sqlTemplate, parameterObject, bindings::put, customVariables); - SqlSource sqlSource = sqlSourceBuilder.parse(sql, processingParameterType, dynamicContext.getBindings()); + SqlSource sqlSource = parse(configuration, sql, parameterObject, bindings, paramNameResolver); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); - dynamicContext.getBindings().forEach(boundSql::setAdditionalParameter); + bindings.forEach(boundSql::setAdditionalParameter); return boundSql; } + private static SqlSource parse(Configuration configuration, String originalSql, Object parameterObject, + Map additionalParameters, ParamNameResolver pnResolver) { + Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); + List parameterMappings = new ArrayList<>(); + ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(parameterMappings, configuration, + parameterObject, parameterType, additionalParameters, pnResolver, false); + GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); + return SqlSourceBuilder.buildSqlSource(configuration, parser.parse(originalSql), parameterMappings); + } + /** * The factory class for Thymeleaf's context. * @@ -116,20 +146,20 @@ static class ContextFactory implements BiFunction, I @Override public IContext apply(Object parameter, Map customVariable) { Configuration configuration = (Configuration) customVariable.remove(TemporaryTakeoverKeys.CONFIGURATION); - DynamicContext dynamicContext = (DynamicContext) customVariable.remove(TemporaryTakeoverKeys.DYNAMIC_CONTEXT); + Map bindings = (Map) customVariable.remove(TemporaryTakeoverKeys.DYNAMIC_CONTEXT); Class processingParameterType = (Class) customVariable .remove(TemporaryTakeoverKeys.PROCESSING_PARAMETER_TYPE); MyBatisBindingContext bindingContext = new MyBatisBindingContext( parameter != null && configuration.getTypeHandlerRegistry().hasTypeHandler(processingParameterType)); - dynamicContext.bind(MyBatisBindingContext.CONTEXT_VARIABLE_NAME, bindingContext); + bindings.put(MyBatisBindingContext.CONTEXT_VARIABLE_NAME, bindingContext); IContext context; if (parameter instanceof Map) { @SuppressWarnings(value = "unchecked") Map map = (Map) parameter; - context = new MapBasedContext(map, dynamicContext, configuration.getVariables()); + context = new MapBasedContext(map, bindings, configuration.getVariables()); } else { MetaClass metaClass = MetaClass.forClass(processingParameterType, configuration.getReflectorFactory()); - context = new MetaClassBasedContext(parameter, metaClass, processingParameterType, dynamicContext, + context = new MetaClassBasedContext(parameter, metaClass, processingParameterType, bindings, configuration.getVariables()); } return context; @@ -138,15 +168,15 @@ public IContext apply(Object parameter, Map customVariable) { private abstract static class AbstractContext implements IContext { - private final DynamicContext dynamicContext; + private final Map dynamicContext; private final Properties configurationProperties; private final Set variableNames; - private AbstractContext(DynamicContext dynamicContext, Properties configurationProperties) { + private AbstractContext(Map dynamicContext, Properties configurationProperties) { this.dynamicContext = dynamicContext; this.configurationProperties = configurationProperties; this.variableNames = new HashSet<>(); - addVariableNames(dynamicContext.getBindings().keySet()); + addVariableNames(dynamicContext.keySet()); Optional.ofNullable(configurationProperties).ifPresent(v -> addVariableNames(v.stringPropertyNames())); } @@ -183,8 +213,8 @@ public Set getVariableNames() { */ @Override public Object getVariable(String name) { - if (dynamicContext.getBindings().containsKey(name)) { - return dynamicContext.getBindings().get(name); + if (dynamicContext.containsKey(name)) { + return dynamicContext.get(name); } if (configurationProperties != null && configurationProperties.containsKey(name)) { return configurationProperties.getProperty(name); @@ -200,7 +230,7 @@ private static class MapBasedContext extends AbstractContext { private final Map variables; - private MapBasedContext(Map parameterMap, DynamicContext dynamicContext, + private MapBasedContext(Map parameterMap, Map dynamicContext, Properties configurationProperties) { super(dynamicContext, configurationProperties); this.variables = parameterMap; @@ -224,7 +254,7 @@ private static class MetaClassBasedContext extends AbstractContext { private final Class parameterType; private MetaClassBasedContext(Object parameterObject, MetaClass parameterMetaClass, Class parameterType, - DynamicContext dynamicContext, Properties configurationProperties) { + Map dynamicContext, Properties configurationProperties) { super(dynamicContext, configurationProperties); this.parameterObject = parameterObject; this.parameterMetaClass = parameterMetaClass;