21 |
22 | Copyright ©2018-{localyear} MyBatis.org. All rights reserved.
21 |19 | * The mybatis-thymeleaf provides utility expression object. 20 | *
19 | * The mybatis-thymeleaf is a plugin that helps applying the 2-way SQL/dynamic SQL feature to the MyBatis 3 using the 20 | * template(or natural template) mechanism provided by Thymeleaf 3. 21 | */ 22 | package org.mybatis.scripting.thymeleaf; -------------------------------------------------------------------------------- /src/test/resources/sql/NameMapper/findByIdsWithoutParamAnnotation.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright 2018-2022 the original author or authors. 3 | -- 4 | -- Licensed under the Apache License, Version 2.0 (the "License"); 5 | -- you may not use this file except in compliance with the License. 6 | -- You may obtain a copy of the License at 7 | -- 8 | -- https://www.apache.org/licenses/LICENSE-2.0 9 | -- 10 | -- Unless required by applicable law or agreed to in writing, software 11 | -- distributed under the License is distributed on an "AS IS" BASIS, 12 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | -- See the License for the specific language governing permissions and 14 | -- limitations under the License. 15 | -- 16 | 17 | SELECT * FROM names 18 | WHERE 1 = 1 19 | /*[# th:if="${not #lists.isEmpty(list)}"]*/ 20 | AND id IN ( 21 | /*[# th:each="id : ${list}"]*/ 22 | /*[(${idStat.first ? '' : ','})]*/ 23 | /*[# mb:p="id"]*/ 1 /*[/]*/ 24 | /*[/]*/ 25 | ) 26 | /*[/]*/ 27 | ORDER BY id 28 | -------------------------------------------------------------------------------- /src/test/resources/sql/NameMapper/update.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright 2018-2022 the original author or authors. 3 | -- 4 | -- Licensed under the Apache License, Version 2.0 (the "License"); 5 | -- you may not use this file except in compliance with the License. 6 | -- You may obtain a copy of the License at 7 | -- 8 | -- https://www.apache.org/licenses/LICENSE-2.0 9 | -- 10 | -- Unless required by applicable law or agreed to in writing, software 11 | -- distributed under the License is distributed on an "AS IS" BASIS, 12 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | -- See the License for the specific language governing permissions and 14 | -- limitations under the License. 15 | -- 16 | 17 | UPDATE names 18 | SET id = id 19 | /*[# th:if="${firstName} != null"]*/ 20 | ,firstName = /*[# mb:p="firstName"]*/ 'Taro' /*[/]*/ 21 | /*[/]*/ 22 | /*[# th:if="${lastName} != null"]*/ 23 | ,lastName = /*[# mb:p="lastName"]*/ 'Yamada' /*[/]*/ 24 | /*[/]*/ 25 | WHERE id = /*[# mb:p="id"]*/ 1 /*[/]*/ 26 | -------------------------------------------------------------------------------- /.github/workflows/sonar.yaml: -------------------------------------------------------------------------------- 1 | name: SonarCloud 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: read-all 9 | 10 | jobs: 11 | build: 12 | if: github.repository_owner == 'mybatis' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v6 16 | with: 17 | # Disabling shallow clone is recommended for improving relevancy of reporting 18 | fetch-depth: 0 19 | - name: Set up JDK 20 | uses: actions/setup-java@v5 21 | with: 22 | cache: maven 23 | distribution: temurin 24 | java-version: 21 25 | - name: Analyze with SonarCloud 26 | run: ./mvnw verify jacoco:report sonar:sonar -B -V -Dlog.level.thymeleaf.config=info -Dsonar.projectKey=mybatis_thymeleaf-scripting -Dsonar.organization=mybatis -Dsonar.host.url=https://sonarcloud.io -Dsonar.token=$SONAR_TOKEN -Dlicense.skip=true --no-transfer-progress 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 30 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/thymeleaf/processor/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * The package that holds classes for mybatis-thymeleaf's processor. 18 | *
19 | * The mybatis-thymeleaf provides processors for following custom attribute tag. 20 | *
30 | * package com.example;
31 | *
32 | * // ...
33 | * public class R2dbcMySQLBindVariableRender extends EnclosingBasedBindVariableRender {
34 | * public R2dbcMySQLBindVariableRender() {
35 | * super("?", ""); // Render '?...' (e.g. ?id)
36 | * }
37 | * }
38 | *
39 | *
40 | * 44 | * dialect.bind-variable-render = com.example.MyBindVariableRender 45 | *46 | * 47 | * @author Kazuki Shimizu 48 | * 49 | * @version 1.0.2 50 | */ 51 | @FunctionalInterface 52 | public interface BindVariableRender extends UnaryOperator
82 | * This is default. 83 | *
84 | */ 85 | MYBATIS(name -> "#{" + name + "}"), 86 | /** 87 | * The render for Spring JDBC named parameter format(.e.g {@literal :id}). 88 | */ 89 | SPRING_NAMED_PARAMETER(new SpringNamedParameterBindVariableRender()); 90 | 91 | private final BindVariableRender delegate; 92 | 93 | BuiltIn(BindVariableRender delegate) { 94 | this.delegate = delegate; 95 | } 96 | 97 | /** 98 | * {@inheritDoc} 99 | */ 100 | @Override 101 | public String render(String name) { 102 | return delegate.render(name); 103 | } 104 | 105 | /** 106 | * Get a type of the actual {@link BindVariableRender}. 107 | * 108 | * @return a type of delegating {@link BindVariableRender} 109 | */ 110 | public Class extends BindVariableRender> getType() { 111 | return delegate.getClass(); 112 | } 113 | 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/thymeleaf/ThymeleafLanguageDriver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.thymeleaf; 17 | 18 | import org.apache.ibatis.executor.parameter.ParameterHandler; 19 | import org.apache.ibatis.mapping.BoundSql; 20 | import org.apache.ibatis.mapping.MappedStatement; 21 | import org.apache.ibatis.mapping.SqlSource; 22 | import org.apache.ibatis.parsing.XNode; 23 | import org.apache.ibatis.scripting.LanguageDriver; 24 | import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; 25 | import org.apache.ibatis.session.Configuration; 26 | import org.mybatis.scripting.thymeleaf.support.TemplateFilePathProvider; 27 | import org.thymeleaf.ITemplateEngine; 28 | 29 | /** 30 | * The {@code LanguageDriver} for integrating with Thymeleaf. 31 | * 32 | * @author Kazuki Shimizu 33 | * 34 | * @version 1.0.0 35 | */ 36 | public class ThymeleafLanguageDriver implements LanguageDriver { 37 | 38 | private final SqlGenerator sqlGenerator; 39 | 40 | /** 41 | * Constructor for creating instance with default {@code TemplateEngine}. 42 | */ 43 | public ThymeleafLanguageDriver() { 44 | this.sqlGenerator = configure(new SqlGenerator(ThymeleafLanguageDriverConfig.newInstance())); 45 | } 46 | 47 | /** 48 | * Constructor for creating instance with user specified {@code Properties}. 49 | * 50 | * @param config 51 | * A user defined {@code ITemplateEngine} instance 52 | */ 53 | public ThymeleafLanguageDriver(ThymeleafLanguageDriverConfig config) { 54 | this.sqlGenerator = configure(new SqlGenerator(config)); 55 | TemplateFilePathProvider.setLanguageDriverConfig(config); 56 | } 57 | 58 | /** 59 | * Constructor for creating instance with user defined {@code ITemplateEngine}. 60 | * 61 | * @param templateEngine 62 | * A user defined {@code ITemplateEngine} instance 63 | */ 64 | public ThymeleafLanguageDriver(ITemplateEngine templateEngine) { 65 | this.sqlGenerator = configure(new SqlGenerator(templateEngine)); 66 | } 67 | 68 | private SqlGenerator configure(SqlGenerator sqlGenerator) { 69 | sqlGenerator.setContextFactory(new ThymeleafSqlSource.ContextFactory()); 70 | return sqlGenerator; 71 | } 72 | 73 | /** 74 | * {@inheritDoc} 75 | */ 76 | @Override 77 | public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, 78 | BoundSql boundSql) { 79 | return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); 80 | } 81 | 82 | /** 83 | * {@inheritDoc} 84 | */ 85 | @Override 86 | public SqlSource createSqlSource(Configuration configuration, XNode script, Class> parameterType) { 87 | return createSqlSource(configuration, script.getNode().getTextContent(), parameterType); 88 | } 89 | 90 | /** 91 | * {@inheritDoc} 92 | */ 93 | @Override 94 | public SqlSource createSqlSource(Configuration configuration, String script, Class> parameterType) { 95 | return new ThymeleafSqlSource(configuration, sqlGenerator, script.trim(), parameterType); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/thymeleaf/MyBatisDialectTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.thymeleaf; 17 | 18 | import java.io.Reader; 19 | import java.sql.Connection; 20 | 21 | import org.apache.ibatis.annotations.Select; 22 | import org.apache.ibatis.io.Resources; 23 | import org.apache.ibatis.jdbc.ScriptRunner; 24 | import org.apache.ibatis.mapping.Environment; 25 | import org.apache.ibatis.session.Configuration; 26 | import org.apache.ibatis.session.SqlSession; 27 | import org.apache.ibatis.session.SqlSessionFactory; 28 | import org.apache.ibatis.session.SqlSessionFactoryBuilder; 29 | import org.apache.ibatis.transaction.TransactionFactory; 30 | import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; 31 | import org.hsqldb.jdbc.JDBCDataSource; 32 | import org.junit.jupiter.api.Assertions; 33 | import org.junit.jupiter.api.BeforeAll; 34 | import org.junit.jupiter.api.Test; 35 | import org.mybatis.scripting.thymeleaf.integrationtest.domain.Name; 36 | 37 | class MyBatisDialectTest { 38 | 39 | private static SqlSessionFactory sqlSessionFactory; 40 | 41 | @BeforeAll 42 | static void setUp() throws Exception { 43 | Class.forName("org.hsqldb.jdbcDriver"); 44 | JDBCDataSource dataSource = new JDBCDataSource(); 45 | dataSource.setUrl("jdbc:hsqldb:mem:dialect-db"); 46 | dataSource.setUser("sa"); 47 | dataSource.setPassword(""); 48 | 49 | TransactionFactory transactionFactory = new JdbcTransactionFactory(); 50 | Environment environment = new Environment("development", transactionFactory, dataSource); 51 | 52 | Configuration configuration = new Configuration(environment); 53 | configuration.getLanguageRegistry().register(new ThymeleafLanguageDriver( 54 | ThymeleafLanguageDriverConfig.newInstance(c -> c.getDialect().setPrefix("mybatis")))); 55 | configuration.setDefaultScriptingLanguage(ThymeleafLanguageDriver.class); 56 | configuration.getMapperRegistry().addMapper(Mapper.class); 57 | sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); 58 | 59 | try (Connection conn = sqlSessionFactory.getConfiguration().getEnvironment().getDataSource().getConnection()) { 60 | try (Reader reader = Resources.getResourceAsReader("create-db.sql")) { 61 | ScriptRunner runner = new ScriptRunner(conn); 62 | runner.setLogWriter(null); 63 | runner.setErrorLogWriter(null); 64 | runner.runScript(reader); 65 | conn.commit(); 66 | } 67 | } 68 | } 69 | 70 | @Test 71 | void testCustomDialectPrefix() { 72 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 73 | Mapper mapper = sqlSession.getMapper(Mapper.class); 74 | Name name = mapper.select(1); 75 | Assertions.assertEquals("Fred", name.getFirstName()); 76 | } 77 | } 78 | 79 | @Test 80 | void testDefaultConstructor() { 81 | MyBatisDialect dialect = new MyBatisDialect(); 82 | Assertions.assertEquals("mb", dialect.getPrefix()); 83 | } 84 | 85 | interface Mapper { 86 | @Select("SELECT * FROM names WHERE id = /*[# mybatis:p='id']*/ 1000 /*[/]*/") 87 | Name select(int id); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/thymeleaf/TemplateEngineCustomizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.thymeleaf; 17 | 18 | import java.util.Optional; 19 | import java.util.function.Consumer; 20 | 21 | import org.thymeleaf.TemplateEngine; 22 | import org.thymeleaf.templateresolver.ITemplateResolver; 23 | 24 | /** 25 | * The interface for customizing a default {@code TemplateEngine} instanced by the MyBatis Thymeleaf.
32 | * package com.example;
33 | *
34 | * // ...
35 | * public class MyTemplateEngineCustomizer implements TemplateEngineCustomizer {
36 | * public void customize(TemplateEngine defaultTemplateEngine) {
37 | * // ...
38 | * }
39 | * }
40 | *
41 | *
42 | * 46 | * customizer = com.example.MyTemplateEngineCustomizer 47 | *48 | * 49 | * @author Kazuki Shimizu 50 | * 51 | * @version 1.0.0 52 | */ 53 | @FunctionalInterface 54 | public interface TemplateEngineCustomizer extends Consumer
101 | * This customizer instance do nothing. 102 | *
103 | */ 104 | DO_NOTHING { 105 | @Override 106 | public void customize(TemplateEngine defaultTemplateEngine) { 107 | // NOP 108 | } 109 | } 110 | 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/thymeleaf/processor/MyBatisBindTagProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.thymeleaf.processor; 17 | 18 | import java.util.List; 19 | import java.util.Objects; 20 | 21 | import org.mybatis.scripting.thymeleaf.MyBatisBindingContext; 22 | import org.thymeleaf.context.ITemplateContext; 23 | import org.thymeleaf.engine.AttributeName; 24 | import org.thymeleaf.exceptions.TemplateProcessingException; 25 | import org.thymeleaf.model.IProcessableElementTag; 26 | import org.thymeleaf.processor.element.AbstractAttributeTagProcessor; 27 | import org.thymeleaf.processor.element.IElementTagStructureHandler; 28 | import org.thymeleaf.standard.expression.Assignation; 29 | import org.thymeleaf.standard.expression.AssignationSequence; 30 | import org.thymeleaf.standard.expression.AssignationUtils; 31 | import org.thymeleaf.standard.expression.IStandardExpression; 32 | import org.thymeleaf.templatemode.TemplateMode; 33 | import org.thymeleaf.util.StringUtils; 34 | 35 | /** 36 | * The processor class for handling the {@code mybatis:bind} tag.195 | * This method use by internal processing. 196 | *
197 | */ 198 | static void clearCache() { 199 | cache.clear(); 200 | } 201 | 202 | private static Map