├── .mvn ├── jvm.config ├── maven.config ├── wrapper │ ├── maven-wrapper.properties │ └── MavenWrapperDownloader.java ├── extensions.xml └── settings.xml ├── .gitattributes ├── renovate.json ├── NOTICE ├── .gitignore ├── LICENSE_HEADER ├── src ├── test │ ├── resources │ │ ├── mybatis-freemarker-empty.properties │ │ ├── mybatis-freemarker.properties │ │ ├── sql │ │ │ ├── getAllNames.ftl │ │ │ ├── TemplateFilePathProviderMapper-delete.ftl │ │ │ ├── TemplateFilePathProviderMapper-findById.ftl │ │ │ ├── findUsingCustomizedContext.ftl │ │ │ ├── TemplateFilePathProviderMapper-insert.ftl │ │ │ ├── findName.ftl │ │ │ ├── preparedIn.ftl │ │ │ ├── preparedDatabaseIdTest.ftl │ │ │ ├── TemplateFilePathProviderMapper-update.ftl │ │ │ └── prepared.ftl │ │ ├── org │ │ │ └── mybatis │ │ │ │ └── scripting │ │ │ │ └── freemarker │ │ │ │ ├── support │ │ │ │ ├── BaseMapper_selectOne.ftl │ │ │ │ ├── TestMapper │ │ │ │ │ ├── selectAllAsc.ftl │ │ │ │ │ ├── TestMapper-delete.ftl │ │ │ │ │ ├── TestMapper-update.ftl │ │ │ │ │ └── TestMapper-update-h2.ftl │ │ │ │ ├── BaseMapper │ │ │ │ │ ├── BaseMapper-count.ftl │ │ │ │ │ ├── BaseMapper-insert.ftl │ │ │ │ │ └── BaseMapper-insert-h2.ftl │ │ │ │ ├── sql │ │ │ │ │ └── TestMapper-selectAllDesc.ftl │ │ │ │ └── DefaultPackageNameMapper │ │ │ │ │ └── DefaultPackageNameMapper-selectAllDesc.ftl │ │ │ │ ├── create-db.sql │ │ │ │ ├── mapper-config.xml │ │ │ │ └── mapper.xml │ │ └── mybatis-freemarker-custom.properties │ └── java │ │ ├── DefaultPackageNameMapper.java │ │ └── org │ │ └── mybatis │ │ └── scripting │ │ └── freemarker │ │ ├── support │ │ ├── BaseMapper.java │ │ ├── TestMapper.java │ │ └── TemplateFilePathProviderTest.java │ │ ├── NameParam.java │ │ ├── CustomizedDataContextMapper.java │ │ ├── PreparedDatabaseIdParamsMapper.java │ │ ├── InvalidFreeMarkerSettingTest.java │ │ ├── Name.java │ │ ├── TemplateFilePathProviderMapper.java │ │ ├── PreparedParam.java │ │ ├── PreparedParamsMapper.java │ │ ├── NameMapper.java │ │ ├── PreparedParamsTest.java │ │ ├── PreparedDatabaseIdParamsTest.java │ │ ├── CustomizedDataContextTest.java │ │ ├── FreeMarkerInXmlTest.java │ │ ├── FreeMarkerInAnnotationsTest.java │ │ ├── TemplateFilePathProviderMapperTest.java │ │ └── FreeMarkerLanguageDriverConfigTest.java ├── site │ ├── site.xml │ └── xdoc │ │ └── index.xml.vm └── main │ └── java │ └── org │ └── mybatis │ └── scripting │ └── freemarker │ ├── GeneratedParamsTemplateModel.java │ ├── ParamObjectAdapter.java │ ├── MyBatisParamDirective.java │ ├── FreeMarkerSqlSource.java │ ├── FreeMarkerLanguageDriver.java │ ├── support │ └── TemplateFilePathProvider.java │ └── FreeMarkerLanguageDriverConfig.java ├── format.xml ├── .github └── workflows │ ├── sonatype.yaml │ ├── ci.yaml │ ├── site.yaml │ ├── codeql.yaml │ ├── sonar.yaml │ └── coveralls.yaml ├── README.md ├── pom.xml ├── mvnw.cmd ├── LICENSE └── mvnw /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | * text=auto -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Daether.checksums.algorithms=SHA-512,SHA-256,SHA-1,MD5 2 | -Daether.connector.smartChecksums=false 3 | --no-transfer-progress 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | MyBatis Freemarker 2 | Copyright 2015-2024 3 | 4 | This product includes software developed by 5 | The MyBatis Team (http://www.mybatis.org/). 6 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionType=source 2 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip 3 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar 4 | wrapperVersion=3.3.4 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.iml 2 | /*.ipr 3 | /*.iws 4 | /.classpath 5 | /.idea 6 | /.project 7 | /.settings 8 | /ibderby 9 | /nb* 10 | /release.properties 11 | /target 12 | /test.db.lck 13 | /test.db.log 14 | /test.db.properties 15 | /test.db.script 16 | /test.db.tmp 17 | /src/docbkx 18 | velocity.log 19 | /bin 20 | .mvn/wrapper/maven-wrapper.jar 21 | pom.xml.releaseBackup 22 | -------------------------------------------------------------------------------- /LICENSE_HEADER: -------------------------------------------------------------------------------- 1 | Copyright ${license.git.copyrightYears} the original author or authors. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /src/test/resources/mybatis-freemarker-empty.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-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 | # 18 | -------------------------------------------------------------------------------- /src/test/resources/mybatis-freemarker.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-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 | basePackage=sql 18 | -------------------------------------------------------------------------------- /src/test/resources/sql/getAllNames.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | SELECT * 19 | FROM names -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/support/BaseMapper_selectOne.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/support/TestMapper/selectAllAsc.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/support/BaseMapper/BaseMapper-count.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/support/BaseMapper/BaseMapper-insert.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/support/TestMapper/TestMapper-delete.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/support/TestMapper/TestMapper-update.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/support/sql/TestMapper-selectAllDesc.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/support/BaseMapper/BaseMapper-insert-h2.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/support/TestMapper/TestMapper-update-h2.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/support/DefaultPackageNameMapper/DefaultPackageNameMapper-selectAllDesc.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | -------------------------------------------------------------------------------- /src/test/resources/sql/TemplateFilePathProviderMapper-delete.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | DELETE FROM names 19 | WHERE id = <@p name="id"/> 20 | 21 | -------------------------------------------------------------------------------- /src/test/resources/sql/TemplateFilePathProviderMapper-findById.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | SELECT * FROM names 19 | WHERE id = <@p name="id"/> 20 | -------------------------------------------------------------------------------- /format.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/test/resources/sql/findUsingCustomizedContext.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | <#-- MY_NAME constant is pre-defined --> 19 | SELECT * 20 | FROM names 21 | where firstName = '${MY_NAME}' -------------------------------------------------------------------------------- /src/test/resources/sql/TemplateFilePathProviderMapper-insert.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | INSERT INTO names (firstName, lastName) 19 | VALUES (<@p name="firstName"/>, <@p name="lastName"/>) -------------------------------------------------------------------------------- /src/test/resources/sql/findName.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | <#-- @ftlvariable name="p" type="org.mybatis.scripting.freemarker.MyBatisParamDirective" --> 19 | SELECT * 20 | FROM names 21 | where firstName = <@p name="name"/> -------------------------------------------------------------------------------- /src/test/resources/sql/preparedIn.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | select * from names where firstName in ( 19 | <#list ids as id> 20 | <@p value=id/> 21 | <#if id_has_next>, 22 | 23 | ) -------------------------------------------------------------------------------- /src/test/resources/sql/preparedDatabaseIdTest.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2023 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | select 19 | * 20 | from 21 | names 22 | where 23 | <#if '${_databaseId}' == 'hsqldb'>firstName = 'Fred' and lastName = 'Flintstone' 24 | -------------------------------------------------------------------------------- /src/test/java/DefaultPackageNameMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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 | import java.util.List; 17 | 18 | import org.mybatis.scripting.freemarker.Name; 19 | 20 | public interface DefaultPackageNameMapper { 21 | List selectAllDesc(); 22 | } 23 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | fr.jcgay.maven 22 | maven-profiler 23 | 3.3 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/support/BaseMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker.support; 17 | 18 | public interface BaseMapper { 19 | void insert(T model); 20 | 21 | void update(T model); 22 | 23 | long count(); 24 | 25 | T selectOne(int id); 26 | } 27 | -------------------------------------------------------------------------------- /src/test/resources/sql/TemplateFilePathProviderMapper-update.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | UPDATE names 19 | SET id = id 20 | <#if firstName?has_content> 21 | ,firstName = <@p name="firstName"/> 22 | 23 | <#if lastName?has_content> 24 | ,lastName = <@p name="lastName"/> 25 | 26 | WHERE id = <@p name="id"/> 27 | -------------------------------------------------------------------------------- /.github/workflows/sonatype.yaml: -------------------------------------------------------------------------------- 1 | name: Sonatype 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: read-all 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | build: 16 | if: github.repository_owner == 'mybatis' && ! contains(toJSON(github.event.head_commit.message), '[maven-release-plugin]') 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 30 19 | steps: 20 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 21 | - name: Setup Java 22 | uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 23 | with: 24 | cache: maven 25 | distribution: temurin 26 | java-version: 25 27 | - name: Deploy to Sonatype 28 | run: ./mvnw deploy --batch-mode --no-transfer-progress --settings ./.mvn/settings.xml --show-version -Dlicense.skip=true -DskipTests 29 | env: 30 | CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} 31 | CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} 32 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/support/TestMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker.support; 17 | 18 | import java.util.List; 19 | 20 | import org.mybatis.scripting.freemarker.Name; 21 | 22 | interface TestMapper extends BaseMapper { 23 | void delete(int id); 24 | 25 | List selectAllDesc(); 26 | 27 | List selectAllAsc(); 28 | 29 | List selectAllByFirstName(String firstName); 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/NameParam.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | /** 19 | * Class to test queries using parameter objects. 20 | * 21 | * @author elwood 22 | */ 23 | public class NameParam { 24 | private int id; 25 | 26 | public NameParam(int id) { 27 | this.id = id; 28 | } 29 | 30 | public int getId() { 31 | return id; 32 | } 33 | 34 | public void setId(int id) { 35 | this.id = id; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [workflow_dispatch, push, pull_request] 4 | 5 | permissions: read-all 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | test: 13 | runs-on: ${{ matrix.os }} 14 | timeout-minutes: 30 15 | strategy: 16 | matrix: 17 | cache: [maven] 18 | distribution: [temurin] 19 | java: [21, 25, 26-ea] 20 | os: [macos-latest, ubuntu-latest, windows-latest] 21 | fail-fast: false 22 | max-parallel: 6 23 | name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} 24 | 25 | steps: 26 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 27 | - name: Setup Java ${{ matrix.java }} ${{ matrix.distribution }} 28 | uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 29 | with: 30 | cache: ${{ matrix.cache }} 31 | distribution: ${{ matrix.distribution }} 32 | java-version: ${{ matrix.java }} 33 | - name: Test with Maven 34 | run: ./mvnw test --batch-mode --no-transfer-progress --show-version -D"license.skip=true" 35 | -------------------------------------------------------------------------------- /src/test/resources/mybatis-freemarker-custom.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015-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 | template-file.base-dir=sqls 18 | 19 | template-file.path-provider.prefix = mappers 20 | template-file.path-provider.includes-package-path = false 21 | template-file.path-provider.separate-directory-per-mapper = false 22 | template-file.path-provider.includes-mapper-name-when-separate-directory = false 23 | template-file.path-provider.cache-enabled = false 24 | 25 | freemarker-settings.interpolation_syntax=dollar 26 | freemarker-settings.whitespace_stripping=yes -------------------------------------------------------------------------------- /src/test/resources/sql/prepared.ftl: -------------------------------------------------------------------------------- 1 | <#-- 2 | 3 | Copyright 2015-2022 the original author or authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --> 18 | <#assign strValue='Wilma' /> 19 | <#assign intValue=5/> 20 | <#assign doubleValue=10.5/> 21 | <#assign objectValue=innerObject/> 22 | <#assign innerStringProp=innerObject.strValue/> 23 | 24 | select * from names where firstName = <@p value='Wilma'/> 25 | and '${strValue}' = <@p value=strValue/> 26 | and ${intValue} = <@p value=intValue/> 27 | and ${doubleValue} = <@p value=doubleValue/> 28 | and '${innerStringProp}' = <@p value=innerObject.strValue/> 29 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/CustomizedDataContextMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import java.util.List; 19 | 20 | import org.apache.ibatis.annotations.Lang; 21 | import org.apache.ibatis.annotations.Select; 22 | 23 | /** 24 | * @author elwood 25 | */ 26 | public interface CustomizedDataContextMapper { 27 | @Lang(CustomizedDataContextTest.CustomFreeMarkerLanguageDriver.class) 28 | @Select("findUsingCustomizedContext.ftl") 29 | List find(); 30 | } 31 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/create-db.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright 2015-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 | create table names ( 18 | id int generated by default as identity, 19 | firstName varchar(20), 20 | lastName varchar(20) 21 | ); 22 | 23 | insert into names (id, firstName, lastName) values(1, 'Fred', 'Flintstone'); 24 | insert into names (id, firstName, lastName) values(2, 'Wilma', 'Flintstone'); 25 | insert into names (id, firstName, lastName) values(3, 'Pebbles', 'Flintstone'); 26 | insert into names (id, firstName, lastName) values(4, 'Barney', 'Rubble'); 27 | insert into names (id, firstName, lastName) values(5, 'Betty', 'Rubble'); -------------------------------------------------------------------------------- /.github/workflows/site.yaml: -------------------------------------------------------------------------------- 1 | name: Site 2 | 3 | on: 4 | push: 5 | branches: 6 | - site 7 | 8 | permissions: 9 | contents: write 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | build: 17 | if: github.repository_owner == 'mybatis' && ! contains(toJSON(github.event.head_commit.message), '[maven-release-plugin]') 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 60 20 | steps: 21 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 22 | - name: Setup Java 23 | uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 24 | with: 25 | cache: maven 26 | distribution: temurin 27 | java-version: 25 28 | - name: Build site 29 | run: ./mvnw site site:stage --batch-mode --no-transfer-progress --settings ./.mvn/settings.xml --show-version -Dlicense.skip=true -DskipTests 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | NVD_API_KEY: ${{ secrets.NVD_API_KEY }} 33 | - name: Deploy Site to gh-pages 34 | uses: JamesIves/github-pages-deploy-action@9d877eea73427180ae43cf98e8914934fe157a1a # v4 35 | with: 36 | branch: gh-pages 37 | folder: target/staging 38 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/PreparedDatabaseIdParamsMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2023 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.freemarker; 17 | 18 | import java.util.Optional; 19 | 20 | import org.apache.ibatis.annotations.Lang; 21 | import org.apache.ibatis.annotations.Select; 22 | 23 | public interface PreparedDatabaseIdParamsMapper { 24 | @Lang(FreeMarkerLanguageDriver.class) 25 | @Select("preparedDatabaseIdTest.ftl") 26 | Optional getDatabaseIdTest(); 27 | 28 | @Lang(FreeMarkerLanguageDriver.class) 29 | @Select("preparedDatabaseIdTest.ftl") 30 | Optional getDatabaseIdTestWithParam(PreparedParam param); 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: '43 10 * * 2' 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | analyze: 17 | name: Analyze 18 | runs-on: 'ubuntu-latest' 19 | timeout-minutes: 30 20 | permissions: 21 | actions: read 22 | contents: read 23 | security-events: write 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 28 | 29 | - name: Setup Java 30 | uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 31 | with: 32 | cache: maven 33 | distribution: 'temurin' 34 | java-version: 25 35 | 36 | - name: Initialize CodeQL 37 | uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 38 | with: 39 | queries: +security-and-quality 40 | 41 | - name: Autobuild 42 | uses: github/codeql-action/autobuild@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 43 | 44 | - name: Perform CodeQL Analysis 45 | uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 46 | -------------------------------------------------------------------------------- /src/site/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MyBatis FreeMarker Support 2 | ======================== 3 | 4 | [![Java CI](https://github.com/mybatis/freemarker-scripting/actions/workflows/ci.yaml/badge.svg)](https://github.com/mybatis/freemarker-scripting/actions/workflows/ci.yaml) 5 | [![Coverage Status](https://coveralls.io/repos/mybatis/freemarker-scripting/badge.svg?branch=master&service=github)](https://coveralls.io/github/mybatis/freemarker-scripting?branch=master) 6 | [![Maven central](https://maven-badges.herokuapp.com/maven-central/org.mybatis.scripting/mybatis-freemarker/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.mybatis.scripting/mybatis-freemarker) 7 | [![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/org.mybatis.scripting/mybatis-freemarker.svg)](https://oss.sonatype.org/content/repositories/snapshots/org/mybatis/scripting/mybatis-freemarker/) 8 | [![License](https://img.shields.io/:license-apache-brightgreen.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 9 | 10 | ![freemarker-scripting](https://mybatis.org/images/mybatis-logo.png) 11 | 12 | MyBatis FreeMarker Scripting Support. 13 | 14 | Requirements 15 | ---------- 16 | 17 | * master(1.2.x) : MyBatis 3.5+, FreeMarker 2.3.22+ and Java 8+ 18 | * 1.1.x : MyBatis 3.4+, FreeMarker 2.3.22+ and Java 7+ 19 | 20 | 21 | Essentials 22 | ---------- 23 | 24 | * [See the docs](https://mybatis.org/freemarker-scripting/) 25 | 26 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/InvalidFreeMarkerSettingTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import org.junit.jupiter.api.Assertions; 19 | import org.junit.jupiter.api.Test; 20 | 21 | class InvalidFreeMarkerSettingTest { 22 | 23 | @Test 24 | void unknownSetting() { 25 | 26 | String message = Assertions.assertThrows(IllegalStateException.class, () -> { 27 | new FreeMarkerLanguageDriver( 28 | FreeMarkerLanguageDriverConfig.newInstance(c -> c.getFreemarkerSettings().put("foo", "bar"))); 29 | }).getMessage(); 30 | Assertions.assertEquals("Fail to configure FreeMarker template setting. name[foo] value[bar]", message); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/freemarker/GeneratedParamsTemplateModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import java.util.List; 19 | 20 | import freemarker.template.TemplateModel; 21 | 22 | /** 23 | * Just a wrapper for list of generated params. Only to be able to return this object from 24 | * {@link freemarker.template.TemplateHashModel#get(java.lang.String)} method. 25 | * 26 | * @author elwood 27 | */ 28 | public class GeneratedParamsTemplateModel implements TemplateModel { 29 | private final List generatedParams; 30 | 31 | public GeneratedParamsTemplateModel(List generatedParams) { 32 | this.generatedParams = generatedParams; 33 | } 34 | 35 | public List getGeneratedParams() { 36 | return generatedParams; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/Name.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | /** 19 | * Entity to be selected. 20 | * 21 | * @author elwood 22 | */ 23 | public class Name { 24 | private int id; 25 | private String firstName; 26 | private String lastName; 27 | 28 | public String getFirstName() { 29 | return firstName; 30 | } 31 | 32 | public void setFirstName(String firstName) { 33 | this.firstName = firstName; 34 | } 35 | 36 | public String getLastName() { 37 | return lastName; 38 | } 39 | 40 | public void setLastName(String lastName) { 41 | this.lastName = lastName; 42 | } 43 | 44 | public int getId() { 45 | return id; 46 | } 47 | 48 | public void setId(int id) { 49 | this.id = id; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/sonar.yaml: -------------------------------------------------------------------------------- 1 | name: SonarCloud 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: read-all 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | env: 15 | SONAR_ORGANIZATION: mybatis 16 | SONAR_PROJECT_KEY: freemarker-scripting 17 | 18 | jobs: 19 | build: 20 | if: github.repository_owner == 'mybatis' 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 30 23 | steps: 24 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 25 | with: 26 | # Disabling shallow clone is recommended for improving relevancy of reporting 27 | fetch-depth: 0 28 | - name: Setup Java 29 | uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 30 | with: 31 | cache: maven 32 | distribution: temurin 33 | java-version: 25 34 | - name: Set SONAR_SCANNER_JAVA_OPTS 35 | run: echo "SONAR_SCANNER_JAVA_OPTS=-Xmx512m" >> ${GITHUB_ENV} 36 | - name: Analyze with SonarCloud 37 | run: ./mvnw verify jacoco:report sonar:sonar --batch-mode --no-transfer-progress --show-version -Dlicense.skip=true -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=${{ env.SONAR_ORGANIZATION }} -Dsonar.projectKey=${{ env.SONAR_ORGANIZATION }}_${{ env.SONAR_PROJECT_KEY }} -Dsonar.scanner.skipJreProvisioning=true -Dsonar.token=${{ env.SONAR_TOKEN }} 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 41 | -------------------------------------------------------------------------------- /.github/workflows/coveralls.yaml: -------------------------------------------------------------------------------- 1 | name: Coveralls 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: read-all 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | coveralls: 13 | if: github.repository_owner == 'mybatis' 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 30 16 | steps: 17 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 18 | - name: Setup Java 19 | uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 20 | with: 21 | cache: maven 22 | distribution: temurin 23 | java-version: 25 24 | - name: Run the build 25 | run: ./mvnw test --batch-mode --no-transfer-progress --quiet --show-version -Dlicense.skip=true 26 | - name: Report Coverage to Coveralls for Pull Requests 27 | if: github.event_name == 'pull_request' 28 | run: ./mvnw generate-sources jacoco:report coveralls:report --batch-mode --no-transfer-progress -DpullRequest=${{ env.PR_NUMBER }} -DrepoToken=${{ env.GITHUB_TOKEN }} -DserviceName=github 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | PR_NUMBER: ${{ github.event.number }} 32 | - name: Report Coverage to Coveralls for General Push 33 | if: github.event_name == 'push' 34 | run: ./mvnw generate-sources jacoco:report coveralls:report --batch-mode --no-transfer-progress -DrepoToken=${{ env.GITHUB_TOKEN }} -DserviceName=github 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/TemplateFilePathProviderMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import org.apache.ibatis.annotations.DeleteProvider; 19 | import org.apache.ibatis.annotations.InsertProvider; 20 | import org.apache.ibatis.annotations.Options; 21 | import org.apache.ibatis.annotations.SelectProvider; 22 | import org.apache.ibatis.annotations.UpdateProvider; 23 | import org.mybatis.scripting.freemarker.support.TemplateFilePathProvider; 24 | 25 | public interface TemplateFilePathProviderMapper { 26 | 27 | @Options(useGeneratedKeys = true, keyProperty = "id") 28 | @InsertProvider(type = TemplateFilePathProvider.class) 29 | void insert(Name name); 30 | 31 | @UpdateProvider(type = TemplateFilePathProvider.class) 32 | void update(Name name); 33 | 34 | @DeleteProvider(type = TemplateFilePathProvider.class) 35 | void delete(Name name); 36 | 37 | @SelectProvider(type = TemplateFilePathProvider.class) 38 | Name findById(Integer id); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/PreparedParam.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | /** 19 | * Class to test auto-generated prepared statement parameters. 20 | * 21 | * @author elwood 22 | */ 23 | public class PreparedParam { 24 | public static class InnerClass { 25 | private String strValue = "InnerString"; 26 | 27 | public String getStrValue() { 28 | return strValue; 29 | } 30 | 31 | public void setStrValue(String strValue) { 32 | this.strValue = strValue; 33 | } 34 | } 35 | 36 | private InnerClass innerObject = new InnerClass(); 37 | private Object nullValue = null; 38 | 39 | public InnerClass getInnerObject() { 40 | return innerObject; 41 | } 42 | 43 | public void setInnerObject(InnerClass innerObject) { 44 | this.innerObject = innerObject; 45 | } 46 | 47 | public Object getNullValue() { 48 | return nullValue; 49 | } 50 | 51 | public void setNullValue(Object nullValue) { 52 | this.nullValue = nullValue; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/PreparedParamsMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import java.util.List; 19 | 20 | import org.apache.ibatis.annotations.Lang; 21 | import org.apache.ibatis.annotations.Param; 22 | import org.apache.ibatis.annotations.Select; 23 | 24 | /** 25 | * This mapper demonstrates the usage of auto-generating prepared statement parameters instead of usual inline strategy. 26 | * 27 | * @author elwood 28 | */ 29 | public interface PreparedParamsMapper { 30 | @Lang(FreeMarkerLanguageDriver.class) 31 | @Select("preparedIn.ftl") 32 | List findByNames(@Param("ids") List ids); 33 | 34 | /** 35 | * This is doesn't work - because params objects are unsupported when using auto-generated prepared parameters (it is 36 | * impossible to add parameters to MyBatis engine). This call will throw exception. 37 | */ 38 | @Lang(FreeMarkerLanguageDriver.class) 39 | @Select("prepared.ftl") 40 | Name findUsingParamsObject(PreparedParam param); 41 | 42 | @Lang(FreeMarkerLanguageDriver.class) 43 | @Select("prepared.ftl") 44 | Name findUsingParams(@Param("innerObject") PreparedParam.InnerClass innerClass); 45 | } 46 | -------------------------------------------------------------------------------- /.mvn/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 22 | 23 | 24 | 25 | central 26 | ${env.CI_DEPLOY_USERNAME} 27 | ${env.CI_DEPLOY_PASSWORD} 28 | 29 | 30 | 31 | 32 | gh-pages-scm 33 | 34 | branch 35 | gh-pages 36 | 37 | 38 | 39 | 40 | 41 | github 42 | ${env.GITHUB_TOKEN} 43 | 44 | 45 | 46 | 47 | nvd 48 | ${env.NVD_API_KEY} 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/mapper-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/freemarker/mapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | id, ${r"firstName"}, lastName 28 | 29 | 32 | 35 | 36 | 39 | 40 | 43 | 44 | 47 | 50 | 51 | 56 | 57 | 62 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/freemarker/ParamObjectAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 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.freemarker; 17 | 18 | import java.util.HashMap; 19 | import java.util.List; 20 | 21 | import freemarker.ext.beans.BeanModel; 22 | import freemarker.ext.beans.BeansWrapperBuilder; 23 | import freemarker.template.TemplateHashModel; 24 | import freemarker.template.TemplateModel; 25 | import freemarker.template.TemplateModelException; 26 | import freemarker.template.Version; 27 | 28 | /** 29 | * Important: if you are using some object that already has property "p", then MyBatisParamDirective will be unavailable 30 | * from script. 31 | * 32 | * @author elwood 33 | */ 34 | public class ParamObjectAdapter implements TemplateHashModel { 35 | private final BeanModel beanModel; 36 | private final List generatedParams; 37 | private HashMap additionalParams; 38 | 39 | public ParamObjectAdapter(Object paramObject, List generatedParams, Version incompatibleImprovementsVersion) { 40 | beanModel = new BeanModel(paramObject, new BeansWrapperBuilder(incompatibleImprovementsVersion).build()); 41 | this.generatedParams = generatedParams; 42 | } 43 | 44 | /** 45 | * Puts the additional parameter into adapter, it will be available if no existing property with same key exists. For 46 | * example, it is suitable to add custom objects and directives into dataContext. 47 | */ 48 | public void putAdditionalParam(String key, TemplateModel value) { 49 | if (additionalParams == null) { 50 | additionalParams = new HashMap<>(); 51 | } 52 | additionalParams.put(key, value); 53 | } 54 | 55 | public List getGeneratedParams() { 56 | return generatedParams; 57 | } 58 | 59 | @Override 60 | public TemplateModel get(String key) throws TemplateModelException { 61 | // Trying to get bean property 62 | TemplateModel value = beanModel.get(key); 63 | 64 | // If no value retrieved, trying to find the key in additional params 65 | if (value == null && additionalParams != null && additionalParams.containsKey(key)) { 66 | return additionalParams.get(key); 67 | } 68 | 69 | // If it is GENERATED_PARAMS_KEY, returning wrapper of generated params list 70 | if (value == null && FreeMarkerSqlSource.GENERATED_PARAMS_KEY.equals(key)) { 71 | return new GeneratedParamsTemplateModel(generatedParams); 72 | } 73 | 74 | return value; 75 | } 76 | 77 | @Override 78 | public boolean isEmpty() throws TemplateModelException { 79 | return beanModel.isEmpty(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/NameMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import java.util.List; 19 | 20 | import org.apache.ibatis.annotations.Lang; 21 | import org.apache.ibatis.annotations.Param; 22 | import org.apache.ibatis.annotations.Select; 23 | 24 | /** 25 | * Annotations-driven mapper for {@link org.mybatis.scripting.freemarker.FreeMarkerInAnnotationsTest}. 26 | * 27 | * @author elwood 28 | */ 29 | public interface NameMapper { 30 | /** 31 | * Simple query that is loaded from template. 32 | */ 33 | @Lang(FreeMarkerLanguageDriver.class) 34 | @Select("getAllNames.ftl") 35 | List getAllNames(); 36 | 37 | /** 38 | * Simple query with prepared statement parameter. 39 | */ 40 | @Lang(FreeMarkerLanguageDriver.class) 41 | @Select("findName.ftl") 42 | Name findName(@Param("name") String name); 43 | 44 | /** 45 | * If any whitespace found inside @Select text, it is interpreted as inline script, not template name. It is 46 | * convenient to avoid creating templates when script is really small. 47 | */ 48 | @Lang(FreeMarkerLanguageDriver.class) 49 | @Select("select * from names where id in (${ids?join(',')})") 50 | List findNamesByIds(@Param("ids") List ids); 51 | 52 | /** 53 | * There are no @Param annotation on argument. This means NameParam instance will be passed into driver as is, not as 54 | * Map entry. So, we need to support this case. Because in driver we need to add some another properties into template 55 | * model, and NameParam is not Map, we are need to wrap passed parameter object into 56 | * {@link org.mybatis.scripting.freemarker.ParamObjectAdapter} before processing template. 57 | */ 58 | @Lang(FreeMarkerLanguageDriver.class) 59 | @Select("select * from names where id = <@p name='id'/> and id = ${id}") 60 | Name find(NameParam nameParam); 61 | 62 | /** 63 | * This query is to demonstrate MyBatis odd behaviour when using String as parameter and can use properties that not 64 | * exist. Both props will be use provided `name` parameter value. Goal is to write FreeMarker lang plugin to support 65 | * this behaviour too (although it is confusing one). 66 | */ 67 | @Select("select * from names" + " where firstName = #{noSuchPropertyOnString}" 68 | + " or firstName = #{oneMoreUnexistingProperty}") 69 | List getNamesOddBehaviourStdLang(String name); 70 | 71 | /** 72 | * This query is to demonstrate that FreeMarker does not break the compatibility with this behaviour. 73 | */ 74 | @Lang(FreeMarkerLanguageDriver.class) 75 | @Select("select * from names" + " where firstName = <@p name='noSuchPropertyOnString'/>" 76 | + " or firstName = <@p name='oneMoreUnexistingProperty'/>") 77 | List getNamesOddBehaviourFreeMarkerLang(String name); 78 | } 79 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.net.Authenticator; 23 | import java.net.PasswordAuthentication; 24 | import java.net.URI; 25 | import java.net.URL; 26 | import java.nio.file.Files; 27 | import java.nio.file.Path; 28 | import java.nio.file.StandardCopyOption; 29 | import java.util.concurrent.ThreadLocalRandom; 30 | 31 | public final class MavenWrapperDownloader { 32 | private static final String WRAPPER_VERSION = "3.3.4"; 33 | 34 | private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE")); 35 | 36 | public static void main(String[] args) { 37 | log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION); 38 | 39 | if (args.length != 2) { 40 | System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing"); 41 | System.exit(1); 42 | } 43 | 44 | try { 45 | log(" - Downloader started"); 46 | final URL wrapperUrl = URI.create(args[0]).toURL(); 47 | final Path baseDir = Path.of(".").toAbsolutePath().normalize(); 48 | final Path wrapperJarPath = baseDir.resolve(args[1]).normalize(); 49 | if (!wrapperJarPath.startsWith(baseDir)) { 50 | throw new IOException("Invalid path: outside of allowed directory"); 51 | } 52 | downloadFileFromURL(wrapperUrl, wrapperJarPath); 53 | log("Done"); 54 | } catch (IOException e) { 55 | System.err.println("- Error downloading: " + e.getMessage()); 56 | if (VERBOSE) { 57 | e.printStackTrace(); 58 | } 59 | System.exit(1); 60 | } 61 | } 62 | 63 | private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath) 64 | throws IOException { 65 | log(" - Downloading to: " + wrapperJarPath); 66 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 67 | final String username = System.getenv("MVNW_USERNAME"); 68 | final char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 69 | Authenticator.setDefault(new Authenticator() { 70 | @Override 71 | protected PasswordAuthentication getPasswordAuthentication() { 72 | return new PasswordAuthentication(username, password); 73 | } 74 | }); 75 | } 76 | Path temp = wrapperJarPath 77 | .getParent() 78 | .resolve(wrapperJarPath.getFileName() + "." 79 | + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp"); 80 | try (InputStream inStream = wrapperUrl.openStream()) { 81 | Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING); 82 | Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING); 83 | } finally { 84 | Files.deleteIfExists(temp); 85 | } 86 | log(" - Downloader complete"); 87 | } 88 | 89 | private static void log(String msg) { 90 | if (VERBOSE) { 91 | System.out.println(msg); 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/PreparedParamsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import java.io.Reader; 19 | import java.sql.Connection; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import org.apache.ibatis.exceptions.PersistenceException; 24 | import org.apache.ibatis.io.Resources; 25 | import org.apache.ibatis.jdbc.ScriptRunner; 26 | import org.apache.ibatis.mapping.Environment; 27 | import org.apache.ibatis.session.Configuration; 28 | import org.apache.ibatis.session.SqlSession; 29 | import org.apache.ibatis.session.SqlSessionFactory; 30 | import org.apache.ibatis.session.SqlSessionFactoryBuilder; 31 | import org.apache.ibatis.transaction.TransactionFactory; 32 | import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; 33 | import org.hsqldb.jdbc.JDBCDataSource; 34 | import org.junit.jupiter.api.Assertions; 35 | import org.junit.jupiter.api.BeforeAll; 36 | import org.junit.jupiter.api.Test; 37 | 38 | /** 39 | * Test of using FreeMarker to generate prepared statements parameters. 40 | * 41 | * @author elwood 42 | */ 43 | class PreparedParamsTest { 44 | private static SqlSessionFactory sqlSessionFactory; 45 | 46 | @BeforeAll 47 | static void setUp() throws Exception { 48 | Class.forName("org.hsqldb.jdbcDriver"); 49 | 50 | JDBCDataSource dataSource = new JDBCDataSource(); 51 | dataSource.setUrl("jdbc:hsqldb:mem:db3"); 52 | dataSource.setUser("sa"); 53 | dataSource.setPassword(""); 54 | 55 | try (Connection conn = dataSource.getConnection()) { 56 | try (Reader reader = Resources.getResourceAsReader("org/mybatis/scripting/freemarker/create-db.sql")) { 57 | ScriptRunner runner = new ScriptRunner(conn); 58 | runner.setLogWriter(null); 59 | runner.setErrorLogWriter(null); 60 | runner.runScript(reader); 61 | conn.commit(); 62 | } 63 | } 64 | 65 | TransactionFactory transactionFactory = new JdbcTransactionFactory(); 66 | Environment environment = new Environment("development", transactionFactory, dataSource); 67 | 68 | // You can call configuration.setDefaultScriptingLanguage(FreeMarkerLanguageDriver.class) 69 | // after this to use FreeMarker driver by default. 70 | Configuration configuration = new Configuration(environment); 71 | 72 | configuration.addMapper(PreparedParamsMapper.class); 73 | sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); 74 | } 75 | 76 | @Test 77 | void testInCall() { 78 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 79 | PreparedParamsMapper mapper = sqlSession.getMapper(PreparedParamsMapper.class); 80 | List names = mapper.findByNames(new ArrayList() { 81 | private static final long serialVersionUID = 1L; 82 | { 83 | add("Pebbles"); 84 | add("Barney"); 85 | add("Betty"); 86 | } 87 | }); 88 | Assertions.assertEquals(3, names.size()); 89 | } 90 | } 91 | 92 | /** 93 | * PersistenceException will be thrown with cause of UnsupportedOperationException 94 | */ 95 | @Test 96 | void testParamsObjectCall() { 97 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 98 | final PreparedParamsMapper mapper = sqlSession.getMapper(PreparedParamsMapper.class); 99 | Assertions.assertThrows(PersistenceException.class, () -> mapper.findUsingParamsObject(new PreparedParam())); 100 | } 101 | } 102 | 103 | @Test 104 | void testNoParamsCall() { 105 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 106 | PreparedParamsMapper mapper = sqlSession.getMapper(PreparedParamsMapper.class); 107 | Name name = mapper.findUsingParams(new PreparedParam.InnerClass()); 108 | Assertions.assertTrue(name != null && name.getFirstName().equals("Wilma")); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/PreparedDatabaseIdParamsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2024 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.freemarker; 17 | 18 | import java.io.Reader; 19 | import java.sql.Connection; 20 | import java.util.Optional; 21 | 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 | 36 | /** 37 | * Test of using FreeMarker to generate prepared statements parameters. 38 | * 39 | * @author s-nakao 40 | */ 41 | class PreparedDatabaseIdParamsTest { 42 | private static SqlSessionFactory sqlSessionFactory; 43 | 44 | @BeforeAll 45 | static void setUp() throws Exception { 46 | Class.forName("org.hsqldb.jdbcDriver"); 47 | 48 | JDBCDataSource dataSource = new JDBCDataSource(); 49 | dataSource.setUrl("jdbc:hsqldb:mem:db5"); 50 | dataSource.setUser("sa"); 51 | dataSource.setPassword(""); 52 | 53 | try (Connection conn = dataSource.getConnection()) { 54 | try (Reader reader = Resources.getResourceAsReader("org/mybatis/scripting/freemarker/create-db.sql")) { 55 | ScriptRunner runner = new ScriptRunner(conn); 56 | runner.setLogWriter(null); 57 | runner.setErrorLogWriter(null); 58 | runner.runScript(reader); 59 | conn.commit(); 60 | } 61 | } 62 | 63 | TransactionFactory transactionFactory = new JdbcTransactionFactory(); 64 | Environment environment = new Environment("development", transactionFactory, dataSource); 65 | 66 | // You can call configuration.setDefaultScriptingLanguage(FreeMarkerLanguageDriver.class) 67 | // after this to use FreeMarker driver by default. 68 | Configuration configuration = new Configuration(environment); 69 | 70 | // set databaseId. default null 71 | // If it is a property, please refer to the following 72 | // https://mybatis.org/mybatis-3/ja/configuration.html#databaseIdProvider. 73 | configuration.setDatabaseId("hsqldb"); 74 | 75 | configuration.addMapper(PreparedDatabaseIdParamsMapper.class); 76 | sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); 77 | } 78 | 79 | @Test 80 | void testReferDatabaseIdInTemplate() throws Exception { 81 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 82 | PreparedDatabaseIdParamsMapper mapper = sqlSession.getMapper(PreparedDatabaseIdParamsMapper.class); 83 | Optional nameList = mapper.getDatabaseIdTest(); 84 | Assertions.assertTrue(nameList.isPresent()); 85 | Assertions.assertEquals("Fred", nameList.orElseThrow(() -> new Exception()).getFirstName()); 86 | Assertions.assertEquals("Flintstone", nameList.orElseThrow(() -> new Exception()).getLastName()); 87 | } 88 | } 89 | 90 | @Test 91 | void testReferDatabaseIdInTemplateWithParam() throws Exception { 92 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 93 | PreparedDatabaseIdParamsMapper mapper = sqlSession.getMapper(PreparedDatabaseIdParamsMapper.class); 94 | Optional nameList = mapper.getDatabaseIdTestWithParam(new PreparedParam()); 95 | Assertions.assertTrue(nameList.isPresent()); 96 | Assertions.assertEquals("Fred", nameList.orElseThrow(() -> new Exception()).getFirstName()); 97 | Assertions.assertEquals("Flintstone", nameList.orElseThrow(() -> new Exception()).getLastName()); 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/CustomizedDataContextTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import java.io.Reader; 19 | import java.sql.Connection; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | import org.apache.ibatis.io.Resources; 24 | import org.apache.ibatis.jdbc.ScriptRunner; 25 | import org.apache.ibatis.mapping.Environment; 26 | import org.apache.ibatis.mapping.SqlSource; 27 | import org.apache.ibatis.session.Configuration; 28 | import org.apache.ibatis.session.SqlSession; 29 | import org.apache.ibatis.session.SqlSessionFactory; 30 | import org.apache.ibatis.session.SqlSessionFactoryBuilder; 31 | import org.apache.ibatis.transaction.TransactionFactory; 32 | import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; 33 | import org.hsqldb.jdbc.JDBCDataSource; 34 | import org.junit.jupiter.api.Assertions; 35 | import org.junit.jupiter.api.BeforeAll; 36 | import org.junit.jupiter.api.Test; 37 | 38 | import freemarker.template.SimpleScalar; 39 | import freemarker.template.Template; 40 | import freemarker.template.Version; 41 | 42 | /** 43 | * @author elwood 44 | */ 45 | class CustomizedDataContextTest { 46 | private static SqlSessionFactory sqlSessionFactory; 47 | 48 | @BeforeAll 49 | static void setUp() throws Exception { 50 | Class.forName("org.hsqldb.jdbcDriver"); 51 | 52 | JDBCDataSource dataSource = new JDBCDataSource(); 53 | dataSource.setUrl("jdbc:hsqldb:mem:db4"); 54 | dataSource.setUser("sa"); 55 | dataSource.setPassword(""); 56 | 57 | try (Connection conn = dataSource.getConnection()) { 58 | try (Reader reader = Resources.getResourceAsReader("org/mybatis/scripting/freemarker/create-db.sql")) { 59 | ScriptRunner runner = new ScriptRunner(conn); 60 | runner.setLogWriter(null); 61 | runner.setErrorLogWriter(null); 62 | runner.runScript(reader); 63 | conn.commit(); 64 | } 65 | } 66 | 67 | TransactionFactory transactionFactory = new JdbcTransactionFactory(); 68 | Environment environment = new Environment("development", transactionFactory, dataSource); 69 | 70 | // You can call configuration.setDefaultScriptingLanguage(FreeMarkerLanguageDriver.class) 71 | // after this to use FreeMarker driver by default. 72 | Configuration configuration = new Configuration(environment); 73 | 74 | configuration.addMapper(CustomizedDataContextMapper.class); 75 | sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); 76 | } 77 | 78 | public static class CustomSqlSource extends FreeMarkerSqlSource { 79 | CustomSqlSource(Template template, Configuration configuration, Version version) { 80 | super(template, configuration, version); 81 | } 82 | 83 | @Override 84 | @SuppressWarnings("unchecked") 85 | protected Object preProcessDataContext(Object dataContext, boolean isMap) { 86 | dataContext = super.preProcessDataContext(dataContext, isMap); 87 | if (isMap) { 88 | ((Map) dataContext).put("MY_NAME", new SimpleScalar("Barney")); 89 | } else { 90 | ((ParamObjectAdapter) dataContext).putAdditionalParam("MY_NAME", new SimpleScalar("Barney")); 91 | } 92 | return dataContext; 93 | } 94 | } 95 | 96 | public static class CustomFreeMarkerLanguageDriver extends FreeMarkerLanguageDriver { 97 | @Override 98 | protected SqlSource createSqlSource(Template template, Configuration configuration) { 99 | return new CustomSqlSource(template, configuration, freemarkerCfg.getIncompatibleImprovements()); 100 | } 101 | } 102 | 103 | @Test 104 | void test() { 105 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 106 | CustomizedDataContextMapper mapper = sqlSession.getMapper(CustomizedDataContextMapper.class); 107 | List names = mapper.find(); 108 | Assertions.assertEquals(1, names.size()); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/FreeMarkerInXmlTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import java.io.Reader; 19 | import java.sql.Connection; 20 | import java.sql.DriverManager; 21 | import java.util.ArrayList; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | 25 | import org.apache.ibatis.io.Resources; 26 | import org.apache.ibatis.jdbc.ScriptRunner; 27 | import org.apache.ibatis.session.SqlSession; 28 | import org.apache.ibatis.session.SqlSessionFactory; 29 | import org.apache.ibatis.session.SqlSessionFactoryBuilder; 30 | import org.junit.jupiter.api.Assertions; 31 | import org.junit.jupiter.api.BeforeAll; 32 | import org.junit.jupiter.api.Test; 33 | 34 | /** 35 | * Test of using FreeMarker inside XML mapper config. 36 | * 37 | * @author elwood 38 | */ 39 | class FreeMarkerInXmlTest { 40 | private static SqlSessionFactory sqlSessionFactory; 41 | 42 | @BeforeAll 43 | static void setUp() throws Exception { 44 | Connection conn = null; 45 | 46 | try { 47 | Class.forName("org.hsqldb.jdbcDriver"); 48 | conn = DriverManager.getConnection("jdbc:hsqldb:mem:db2", "sa", ""); 49 | 50 | Reader reader = Resources.getResourceAsReader("org/mybatis/scripting/freemarker/create-db.sql"); 51 | 52 | ScriptRunner runner = new ScriptRunner(conn); 53 | runner.setLogWriter(null); 54 | runner.setErrorLogWriter(null); 55 | runner.runScript(reader); 56 | conn.commit(); 57 | reader.close(); 58 | 59 | reader = Resources.getResourceAsReader("org/mybatis/scripting/freemarker/mapper-config.xml"); 60 | sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); 61 | reader.close(); 62 | } finally { 63 | if (conn != null) { 64 | conn.close(); 65 | } 66 | } 67 | } 68 | 69 | @Test 70 | void testNoParamsCall() { 71 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 72 | List allNames = sqlSession.selectList("getAllNames"); 73 | Assertions.assertEquals(5, allNames.size()); 74 | } 75 | } 76 | 77 | @Test 78 | void testMyBatisParamCall() { 79 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 80 | HashMap paramsMap = new HashMap<>(); 81 | paramsMap.put("name", "Pebbles"); 82 | Name pebble = sqlSession.selectOne("findName", paramsMap); 83 | Assertions.assertTrue(pebble != null && pebble.getFirstName().equals("Pebbles")); 84 | } 85 | } 86 | 87 | @Test 88 | void testInQuery() { 89 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 90 | HashMap paramsMap = new HashMap<>(); 91 | paramsMap.put("ids", new ArrayList() { 92 | private static final long serialVersionUID = 1L; 93 | { 94 | add(1); 95 | add(3); 96 | add(4); 97 | } 98 | }); 99 | List namesByIds = sqlSession.selectList("findNamesByIds", paramsMap); 100 | Assertions.assertEquals(3, namesByIds.size()); 101 | } 102 | } 103 | 104 | @Test 105 | void testParamObject() { 106 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 107 | Name name = sqlSession.selectOne("find", new NameParam(4)); 108 | Assertions.assertTrue(name != null && name.getId() == 4); 109 | } 110 | } 111 | 112 | @Test 113 | void testStringParam() { 114 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 115 | List stdLangResult = sqlSession.selectList("getNamesOddBehaviourStdLang", "Pebbles"); 116 | List freeMarkerLangResult = sqlSession.selectList("getNamesOddBehaviourFreeMarkerLang", "Pebbles"); 117 | Assertions.assertEquals(1, stdLangResult.size()); 118 | Assertions.assertEquals("Pebbles", stdLangResult.get(0).getFirstName()); 119 | Assertions.assertEquals(1, freeMarkerLangResult.size()); 120 | Assertions.assertEquals("Pebbles", freeMarkerLangResult.get(0).getFirstName()); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/freemarker/MyBatisParamDirective.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2023 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.freemarker; 17 | 18 | import java.io.IOException; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | import freemarker.core.Environment; 23 | import freemarker.ext.util.WrapperTemplateModel; 24 | import freemarker.template.DefaultListAdapter; 25 | import freemarker.template.SimpleScalar; 26 | import freemarker.template.TemplateBooleanModel; 27 | import freemarker.template.TemplateDateModel; 28 | import freemarker.template.TemplateDirectiveBody; 29 | import freemarker.template.TemplateDirectiveModel; 30 | import freemarker.template.TemplateException; 31 | import freemarker.template.TemplateModel; 32 | import freemarker.template.TemplateNumberModel; 33 | import freemarker.template.TemplateScalarModel; 34 | 35 | /** 36 | * Custom FreeMarker directive for generating "#{paramName}" declarations in convenient way. Problem is FreeMarker 37 | * supports this syntax natively and there are no chance to disable this (although it is deprecated). And to get 38 | * "#{paramName}" we should write ${r"#{paramName}"}. With this directive you can write more simple: 39 | *

40 | *

41 | * 42 | *
 43 |  *     <@p name="paramName"/>
 44 |  * 
45 | * 46 | *
47 | *

48 | * Also directive supports `value` attribute. If it is specified, param will take passed value and create the 49 | * corresponding #{}-parameter. This is useful in loops: 50 | *

51 | *
52 | * 53 | *
 54 |  *     <#list ids as id>
 55 |  *       <@p value=id/>
 56 |  *       <#if id_has_next>,</#if>
 57 |  *     </#list>
 58 |  * 
59 | * 60 | *
61 | *

62 | * will be translated into 63 | *

64 | *
65 | * 66 | *
 67 |  *     #{_p0},#{_p1},#{_p2}
 68 |  * 
69 | * 70 | *
71 | *

72 | * And MyBatis engine will convert it to `?`-params finally. 73 | *

74 | * 75 | * @author elwood 76 | */ 77 | public class MyBatisParamDirective implements TemplateDirectiveModel { 78 | public static String DEFAULT_KEY = "p"; 79 | public static String DATABASE_ID_KEY = "_databaseId"; 80 | 81 | @Override 82 | public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) 83 | throws TemplateException, IOException { 84 | SimpleScalar name = (SimpleScalar) params.get("name"); 85 | if (params.containsKey("value")) { 86 | Object valueObject = params.get("value"); 87 | Object value; 88 | if (valueObject == null) { 89 | value = null; 90 | } else if (valueObject instanceof WrapperTemplateModel) { 91 | value = ((WrapperTemplateModel) valueObject).getWrappedObject(); 92 | } else if (valueObject instanceof TemplateScalarModel) { 93 | value = ((TemplateScalarModel) valueObject).getAsString(); 94 | } else if (valueObject instanceof TemplateNumberModel) { 95 | value = ((TemplateNumberModel) valueObject).getAsNumber(); 96 | } else if (valueObject instanceof TemplateDateModel) { 97 | value = ((TemplateDateModel) valueObject).getAsDate(); 98 | } else if (valueObject instanceof TemplateBooleanModel) { 99 | value = ((TemplateBooleanModel) valueObject).getAsBoolean(); 100 | } else { 101 | throw new UnsupportedOperationException( 102 | String.format("Type %s is not supported yet in this context.", valueObject.getClass().getSimpleName())); 103 | } 104 | 105 | TemplateModel generatedParamsObject = env.getGlobalVariables().get(FreeMarkerSqlSource.GENERATED_PARAMS_KEY); 106 | List generatedParams; 107 | if (generatedParamsObject instanceof DefaultListAdapter) { 108 | generatedParams = (List) ((DefaultListAdapter) generatedParamsObject).getWrappedObject(); 109 | } else { 110 | generatedParams = ((GeneratedParamsTemplateModel) generatedParamsObject).getGeneratedParams(); 111 | } 112 | String generatedParamName = "_p" + generatedParams.size(); 113 | env.getOut().write(String.format("#{%s}", generatedParamName)); 114 | generatedParams.add(value); 115 | } else { 116 | env.getOut().write(String.format("#{%s}", name)); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 4.0.0 22 | 23 | 24 | org.mybatis 25 | mybatis-parent 26 | 51 27 | 28 | 29 | 30 | org.mybatis.scripting 31 | mybatis-freemarker 32 | 1.3.2-SNAPSHOT 33 | 34 | MyBatis FreeMarker 35 | FreeMarker support for MyBatis 36 | https://www.mybatis.org/freemarker-scripting/ 37 | 38 | 2015 39 | 40 | 41 | 42 | Igor Kostromin 43 | elwood.su@gmail.com 44 | 45 | 46 | Eduardo Macarron 47 | eduardo.macarron@gmail.com 48 | 49 | 50 | Frank Martinez 51 | mnesarco@gmail.com 52 | 53 | 54 | 55 | 56 | scm:git:ssh://git@github.com/mybatis/freemarker-scripting.git 57 | scm:git:ssh://git@github.com/mybatis/freemarker-scripting.git 58 | HEAD 59 | https://github.com/mybatis/freemarker-scripting/ 60 | 61 | 62 | GitHub Issue Management 63 | https://github.com/mybatis/freemarker-scripting/issues 64 | 65 | 66 | GitHub Actions 67 | https://github.com/mybatis/freemarker-scripting/actions 68 | 69 | 70 | 71 | gh-pages-scm 72 | Mybatis GitHub Pages 73 | scm:git:ssh://git@github.com/mybatis/freemarker-scripting.git 74 | 75 | 76 | 77 | 78 | 79 | 11 80 | 11 81 | 82 | 1.1.2 83 | org.mybatis.scripting.freemarker 84 | 85 | 86 | 1763920575 87 | 88 | 89 | 90 | 91 | org.mybatis 92 | mybatis 93 | 3.5.19 94 | 95 | 96 | org.freemarker 97 | freemarker 98 | 2.3.34 99 | 100 | 101 | org.apache.commons 102 | commons-text 103 | 1.15.0 104 | 105 | 106 | 107 | 108 | org.junit.jupiter 109 | junit-jupiter-engine 110 | 6.0.1 111 | test 112 | 113 | 114 | org.hsqldb 115 | hsqldb 116 | 2.7.4 117 | test 118 | 119 | 120 | 121 | 122 | 123 | 124 | org.apache.maven.plugins 125 | maven-enforcer-plugin 126 | 127 | 128 | 129 | 130 | 131 | org.freemarker:freemarker 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/FreeMarkerInAnnotationsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import java.io.Reader; 19 | import java.sql.Connection; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import org.apache.ibatis.io.Resources; 24 | import org.apache.ibatis.jdbc.ScriptRunner; 25 | import org.apache.ibatis.mapping.Environment; 26 | import org.apache.ibatis.session.Configuration; 27 | import org.apache.ibatis.session.SqlSession; 28 | import org.apache.ibatis.session.SqlSessionFactory; 29 | import org.apache.ibatis.session.SqlSessionFactoryBuilder; 30 | import org.apache.ibatis.transaction.TransactionFactory; 31 | import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; 32 | import org.hsqldb.jdbc.JDBCDataSource; 33 | import org.junit.jupiter.api.Assertions; 34 | import org.junit.jupiter.api.BeforeAll; 35 | import org.junit.jupiter.api.Test; 36 | 37 | /** 38 | * Test of using FreeMarker in annotations-driven mapper. 39 | * 40 | * @author elwood 41 | */ 42 | class FreeMarkerInAnnotationsTest { 43 | private static SqlSessionFactory sqlSessionFactory; 44 | 45 | @BeforeAll 46 | static void setUp() throws Exception { 47 | Class.forName("org.hsqldb.jdbcDriver"); 48 | 49 | JDBCDataSource dataSource = new JDBCDataSource(); 50 | dataSource.setUrl("jdbc:hsqldb:mem:db1"); 51 | dataSource.setUser("sa"); 52 | dataSource.setPassword(""); 53 | 54 | try (Connection conn = dataSource.getConnection()) { 55 | try (Reader reader = Resources.getResourceAsReader("org/mybatis/scripting/freemarker/create-db.sql")) { 56 | ScriptRunner runner = new ScriptRunner(conn); 57 | runner.setLogWriter(null); 58 | runner.setErrorLogWriter(null); 59 | runner.runScript(reader); 60 | conn.commit(); 61 | } 62 | } 63 | 64 | TransactionFactory transactionFactory = new JdbcTransactionFactory(); 65 | Environment environment = new Environment("development", transactionFactory, dataSource); 66 | 67 | // You can call configuration.setDefaultScriptingLanguage(FreeMarkerLanguageDriver.class) 68 | // after this to use FreeMarker driver by default. 69 | Configuration configuration = new Configuration(environment); 70 | 71 | configuration.addMapper(NameMapper.class); 72 | sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); 73 | } 74 | 75 | @Test 76 | void testNoParamsCall() { 77 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 78 | NameMapper mapper = sqlSession.getMapper(NameMapper.class); 79 | List allNames = mapper.getAllNames(); 80 | Assertions.assertEquals(5, allNames.size()); 81 | } 82 | } 83 | 84 | @Test 85 | void testMyBatisParamCall() { 86 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 87 | NameMapper mapper = sqlSession.getMapper(NameMapper.class); 88 | Name pebble = mapper.findName("Pebbles"); 89 | Assertions.assertTrue(pebble != null && pebble.getFirstName().equals("Pebbles")); 90 | } 91 | } 92 | 93 | @Test 94 | void testInQuery() { 95 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 96 | NameMapper mapper = sqlSession.getMapper(NameMapper.class); 97 | List namesByIds = mapper.findNamesByIds(new ArrayList() { 98 | private static final long serialVersionUID = 1L; 99 | { 100 | add(1); 101 | add(3); 102 | add(4); 103 | } 104 | }); 105 | Assertions.assertEquals(3, namesByIds.size()); 106 | } 107 | } 108 | 109 | @Test 110 | void testParamObject() { 111 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 112 | NameMapper mapper = sqlSession.getMapper(NameMapper.class); 113 | Name name = mapper.find(new NameParam(4)); 114 | Assertions.assertTrue(name != null && name.getId() == 4); 115 | } 116 | } 117 | 118 | @Test 119 | void testStringParam() { 120 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 121 | NameMapper mapper = sqlSession.getMapper(NameMapper.class); 122 | List stdLangResult = mapper.getNamesOddBehaviourStdLang("Pebbles"); 123 | List freeMarkerLangResult = mapper.getNamesOddBehaviourFreeMarkerLang("Pebbles"); 124 | Assertions.assertEquals(1, stdLangResult.size()); 125 | Assertions.assertEquals("Pebbles", stdLangResult.get(0).getFirstName()); 126 | Assertions.assertEquals(1, freeMarkerLangResult.size()); 127 | Assertions.assertEquals("Pebbles", freeMarkerLangResult.get(0).getFirstName()); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/freemarker/FreeMarkerSqlSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 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.freemarker; 17 | 18 | import java.io.CharArrayWriter; 19 | import java.io.IOException; 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | import org.apache.ibatis.builder.SqlSourceBuilder; 26 | import org.apache.ibatis.mapping.BoundSql; 27 | import org.apache.ibatis.mapping.SqlSource; 28 | import org.apache.ibatis.session.Configuration; 29 | 30 | import freemarker.template.SimpleScalar; 31 | import freemarker.template.Template; 32 | import freemarker.template.TemplateException; 33 | import freemarker.template.Version; 34 | 35 | /** 36 | * Applies provided parameter(s) to FreeMarker template. Then passes the result into default MyBatis engine (and it 37 | * finally replaces #{}-params to '?'-params). So, FreeMarker is used as preprocessor for MyBatis engine. 38 | * 39 | * @author elwood 40 | */ 41 | public class FreeMarkerSqlSource implements SqlSource { 42 | private final Template template; 43 | private final Configuration configuration; 44 | private final Version incompatibleImprovementsVersion; 45 | private final String databaseId; 46 | 47 | public static final String GENERATED_PARAMS_KEY = "__GENERATED__"; 48 | 49 | public FreeMarkerSqlSource(Template template, Configuration configuration, Version incompatibleImprovementsVersion) { 50 | this.template = template; 51 | this.configuration = configuration; 52 | this.incompatibleImprovementsVersion = incompatibleImprovementsVersion; 53 | this.databaseId = configuration.getDatabaseId(); 54 | } 55 | 56 | /** 57 | * Populates additional parameters to data context. Data context can be {@link java.util.Map} or 58 | * {@link org.mybatis.scripting.freemarker.ParamObjectAdapter} instance. 59 | */ 60 | protected Object preProcessDataContext(Object dataContext, boolean isMap) { 61 | if (isMap) { 62 | ((Map) dataContext).put(MyBatisParamDirective.DEFAULT_KEY, new MyBatisParamDirective()); 63 | ((Map) dataContext).put(MyBatisParamDirective.DATABASE_ID_KEY, new SimpleScalar(this.databaseId)); 64 | } else { 65 | ((ParamObjectAdapter) dataContext).putAdditionalParam(MyBatisParamDirective.DEFAULT_KEY, 66 | new MyBatisParamDirective()); 67 | ((ParamObjectAdapter) dataContext).putAdditionalParam(MyBatisParamDirective.DATABASE_ID_KEY, 68 | new SimpleScalar(this.databaseId)); 69 | } 70 | return dataContext; 71 | } 72 | 73 | @Override 74 | public BoundSql getBoundSql(Object parameterObject) { 75 | // Add to passed parameterObject our predefined directive - MyBatisParamDirective 76 | // It will be available as "p" inside templates 77 | Object dataContext; 78 | List generatedParams = new ArrayList<>(); 79 | if (parameterObject != null) { 80 | if (parameterObject instanceof Map) { 81 | HashMap map = new HashMap<>((Map) parameterObject); 82 | map.put(GENERATED_PARAMS_KEY, generatedParams); 83 | dataContext = preProcessDataContext(map, true); 84 | } else { 85 | ParamObjectAdapter adapter = new ParamObjectAdapter(parameterObject, generatedParams, 86 | incompatibleImprovementsVersion); 87 | dataContext = preProcessDataContext(adapter, false); 88 | } 89 | } else { 90 | HashMap map = new HashMap<>(); 91 | map.put(GENERATED_PARAMS_KEY, generatedParams); 92 | dataContext = preProcessDataContext(map, true); 93 | } 94 | 95 | CharArrayWriter writer = new CharArrayWriter(); 96 | try { 97 | template.process(dataContext, writer); 98 | } catch (TemplateException | IOException e) { 99 | throw new RuntimeException(e); 100 | } 101 | 102 | // We got SQL ready for MyBatis here. This SQL contains 103 | // params declarations like "#{param}", 104 | // they will be replaced to '?' by MyBatis engine further 105 | String sql = writer.toString(); 106 | 107 | if (!generatedParams.isEmpty()) { 108 | if (!(parameterObject instanceof Map)) { 109 | throw new UnsupportedOperationException("Auto-generated prepared statements parameters" 110 | + " are not available if using parameters object. Use @Param-annotated parameters" + " instead."); 111 | } 112 | 113 | Map parametersMap = (Map) parameterObject; 114 | for (int i = 0; i < generatedParams.size(); i++) { 115 | parametersMap.put("_p" + i, generatedParams.get(i)); 116 | } 117 | } 118 | 119 | // Pass retrieved SQL into MyBatis engine, it will substitute prepared-statements parameters 120 | SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); 121 | Class parameterType1 = parameterObject == null ? Object.class : parameterObject.getClass(); 122 | SqlSource sqlSource = sqlSourceParser.parse(sql, parameterType1, new HashMap<>()); 123 | return sqlSource.getBoundSql(parameterObject); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/TemplateFilePathProviderMapperTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import java.io.Reader; 19 | import java.sql.Connection; 20 | 21 | import org.apache.ibatis.io.Resources; 22 | import org.apache.ibatis.jdbc.ScriptRunner; 23 | import org.apache.ibatis.mapping.Environment; 24 | import org.apache.ibatis.session.Configuration; 25 | import org.apache.ibatis.session.SqlSession; 26 | import org.apache.ibatis.session.SqlSessionFactory; 27 | import org.apache.ibatis.session.SqlSessionFactoryBuilder; 28 | import org.apache.ibatis.transaction.TransactionFactory; 29 | import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; 30 | import org.hsqldb.jdbc.JDBCDataSource; 31 | import org.junit.jupiter.api.AfterAll; 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.freemarker.support.TemplateFilePathProvider; 36 | 37 | class TemplateFilePathProviderMapperTest { 38 | private static SqlSessionFactory sqlSessionFactory; 39 | private static FreeMarkerLanguageDriverConfig driverConfig; 40 | 41 | @BeforeAll 42 | @AfterAll 43 | static void cleanup() { 44 | TemplateFilePathProvider.clearCache(); 45 | } 46 | 47 | @BeforeAll 48 | static void setUp() throws Exception { 49 | Class.forName("org.hsqldb.jdbcDriver"); 50 | JDBCDataSource dataSource = new JDBCDataSource(); 51 | dataSource.setUrl("jdbc:hsqldb:mem:template-file-path-provider"); 52 | dataSource.setUser("sa"); 53 | dataSource.setPassword(""); 54 | 55 | try (Connection conn = dataSource.getConnection()) { 56 | try (Reader reader = Resources.getResourceAsReader("org/mybatis/scripting/freemarker/create-db.sql")) { 57 | ScriptRunner runner = new ScriptRunner(conn); 58 | runner.setLogWriter(null); 59 | runner.setErrorLogWriter(null); 60 | runner.runScript(reader); 61 | conn.commit(); 62 | } 63 | } 64 | 65 | TransactionFactory transactionFactory = new JdbcTransactionFactory(); 66 | Environment environment = new Environment("development", transactionFactory, dataSource); 67 | 68 | Configuration configuration = new Configuration(environment); 69 | configuration.setMapUnderscoreToCamelCase(true); 70 | driverConfig = FreeMarkerLanguageDriverConfig.newInstance(c -> { 71 | c.getTemplateFile().getPathProvider().setIncludesPackagePath(false); 72 | c.getTemplateFile().getPathProvider().setSeparateDirectoryPerMapper(false); 73 | c.getFreemarkerSettings().put("interpolation_syntax", "dollar"); 74 | }); 75 | configuration.getLanguageRegistry().register(new FreeMarkerLanguageDriver(driverConfig)); 76 | configuration.setDefaultScriptingLanguage(FreeMarkerLanguageDriver.class); 77 | 78 | configuration.addMapper(TemplateFilePathProviderMapper.class); 79 | sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); 80 | } 81 | 82 | @Test 83 | void testInsert() { 84 | driverConfig.getTemplateFile().getPathProvider().setCacheEnabled(true); 85 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 86 | TemplateFilePathProviderMapper mapper = sqlSession.getMapper(TemplateFilePathProviderMapper.class); 87 | Name name = new Name(); 88 | name.setFirstName("Thymeleaf"); 89 | name.setLastName("MyBatis"); 90 | mapper.insert(name); 91 | 92 | Name loadedName = mapper.findById(name.getId()); 93 | Assertions.assertEquals(name.getFirstName(), loadedName.getFirstName()); 94 | Assertions.assertEquals(name.getLastName(), loadedName.getLastName()); 95 | } 96 | } 97 | 98 | @Test 99 | void testUpdate() { 100 | driverConfig.getTemplateFile().getPathProvider().setCacheEnabled(false); 101 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 102 | TemplateFilePathProviderMapper mapper = sqlSession.getMapper(TemplateFilePathProviderMapper.class); 103 | Name name = new Name(); 104 | name.setFirstName("Thymeleaf"); 105 | name.setLastName("MyBatis"); 106 | mapper.insert(name); 107 | 108 | Name updatingName = new Name(); 109 | updatingName.setId(name.getId()); 110 | updatingName.setFirstName("Thymeleaf3"); 111 | mapper.update(updatingName); 112 | 113 | Name loadedName = mapper.findById(name.getId()); 114 | Assertions.assertEquals(updatingName.getFirstName(), loadedName.getFirstName()); 115 | Assertions.assertEquals(name.getLastName(), loadedName.getLastName()); 116 | } 117 | } 118 | 119 | @Test 120 | void testDelete() { 121 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 122 | TemplateFilePathProviderMapper mapper = sqlSession.getMapper(TemplateFilePathProviderMapper.class); 123 | Name name = new Name(); 124 | name.setFirstName("Thymeleaf"); 125 | name.setLastName("MyBatis"); 126 | mapper.insert(name); 127 | 128 | mapper.delete(name); 129 | 130 | Name loadedName = mapper.findById(name.getId()); 131 | Assertions.assertNull(loadedName); 132 | } 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriverConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import java.util.Properties; 19 | 20 | import org.junit.jupiter.api.AfterEach; 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.BeforeEach; 23 | import org.junit.jupiter.api.Test; 24 | 25 | class FreeMarkerLanguageDriverConfigTest { 26 | 27 | private String currentConfigFile; 28 | private String currentConfigEncoding; 29 | 30 | @BeforeEach 31 | void saveCurrentConfig() { 32 | currentConfigFile = System.getProperty("mybatis-freemarker.config"); 33 | currentConfigEncoding = System.getProperty("mybatis-freemarker.config.encoding"); 34 | } 35 | 36 | @AfterEach 37 | void restoreConfig() { 38 | if (currentConfigFile == null) { 39 | System.clearProperty("mybatis-freemarker.config.file"); 40 | } else { 41 | System.setProperty("mybatis-freemarker.config.file", currentConfigFile); 42 | } 43 | if (currentConfigEncoding == null) { 44 | System.clearProperty("mybatis-freemarker.config.encoding"); 45 | } else { 46 | System.setProperty("mybatis-freemarker.config.encoding", currentConfigEncoding); 47 | } 48 | } 49 | 50 | @Test 51 | void newInstanceWithEmptyPropertiesFile() { 52 | System.setProperty("mybatis-freemarker.config.file", "mybatis-freemarker-empty.properties"); 53 | FreeMarkerLanguageDriverConfig config = FreeMarkerLanguageDriverConfig.newInstance(); 54 | @SuppressWarnings("deprecation") 55 | String basePackage = config.getBasePackage(); 56 | Assertions.assertEquals("", basePackage); 57 | Assertions.assertEquals("", config.getTemplateFile().getBaseDir()); 58 | Assertions.assertEquals("", config.getTemplateFile().getPathProvider().getPrefix()); 59 | Assertions.assertTrue(config.getTemplateFile().getPathProvider().isIncludesPackagePath()); 60 | Assertions.assertTrue(config.getTemplateFile().getPathProvider().isSeparateDirectoryPerMapper()); 61 | Assertions.assertTrue(config.getTemplateFile().getPathProvider().isIncludesMapperNameWhenSeparateDirectory()); 62 | Assertions.assertTrue(config.getTemplateFile().getPathProvider().isCacheEnabled()); 63 | } 64 | 65 | @Test 66 | void newInstanceWithPropertiesFileNotFound() { 67 | System.setProperty("mybatis-freemarker.config.file", "mybatis-freemarker-notfound.properties"); 68 | FreeMarkerLanguageDriverConfig config = FreeMarkerLanguageDriverConfig.newInstance(); 69 | @SuppressWarnings("deprecation") 70 | String basePackage = config.getBasePackage(); 71 | Assertions.assertEquals("", basePackage); 72 | Assertions.assertEquals("", config.getTemplateFile().getBaseDir()); 73 | Assertions.assertEquals("", config.getTemplateFile().getPathProvider().getPrefix()); 74 | Assertions.assertTrue(config.getTemplateFile().getPathProvider().isIncludesPackagePath()); 75 | Assertions.assertTrue(config.getTemplateFile().getPathProvider().isSeparateDirectoryPerMapper()); 76 | Assertions.assertTrue(config.getTemplateFile().getPathProvider().isIncludesMapperNameWhenSeparateDirectory()); 77 | Assertions.assertTrue(config.getTemplateFile().getPathProvider().isCacheEnabled()); 78 | } 79 | 80 | @Test 81 | void newInstanceWithCustomPropertiesFile() { 82 | System.setProperty("mybatis-freemarker.config.file", "mybatis-freemarker-custom.properties"); 83 | FreeMarkerLanguageDriverConfig config = FreeMarkerLanguageDriverConfig.newInstance(); 84 | @SuppressWarnings("deprecation") 85 | String basePackage = config.getBasePackage(); 86 | Assertions.assertEquals("sqls", basePackage); 87 | Assertions.assertEquals("sqls", config.getTemplateFile().getBaseDir()); 88 | Assertions.assertEquals("mappers", config.getTemplateFile().getPathProvider().getPrefix()); 89 | Assertions.assertFalse(config.getTemplateFile().getPathProvider().isIncludesPackagePath()); 90 | Assertions.assertFalse(config.getTemplateFile().getPathProvider().isSeparateDirectoryPerMapper()); 91 | Assertions.assertFalse(config.getTemplateFile().getPathProvider().isIncludesMapperNameWhenSeparateDirectory()); 92 | Assertions.assertFalse(config.getTemplateFile().getPathProvider().isCacheEnabled()); 93 | Assertions.assertEquals(2, config.getFreemarkerSettings().size()); 94 | Assertions.assertEquals("dollar", config.getFreemarkerSettings().get("interpolation_syntax")); 95 | Assertions.assertEquals("yes", config.getFreemarkerSettings().get("whitespace_stripping")); 96 | } 97 | 98 | @Test 99 | void newInstanceWithCustomProperties() { 100 | Properties properties = new Properties(); 101 | properties.setProperty("templateFile.pathProvider.prefix", "mapper"); 102 | properties.setProperty("templateFile.pathProvider.includesPackagePath", "false"); 103 | properties.setProperty("templateFile.pathProvider.separateDirectoryPerMapper", "false"); 104 | properties.setProperty("templateFile.pathProvider.includesMapperNameWhenSeparateDirectory", "false"); 105 | properties.setProperty("templateFile.pathProvider.cacheEnabled", "false"); 106 | properties.setProperty("freemarkerSettings.interpolation_syntax", "dollar"); 107 | properties.setProperty("freemarkerSettings.whitespace_stripping", "yes"); 108 | 109 | FreeMarkerLanguageDriverConfig config = FreeMarkerLanguageDriverConfig.newInstance(properties); 110 | @SuppressWarnings("deprecation") 111 | String basePackage = config.getBasePackage(); 112 | Assertions.assertEquals("sql", basePackage); 113 | Assertions.assertEquals("sql", config.getTemplateFile().getBaseDir()); 114 | Assertions.assertEquals("mapper", config.getTemplateFile().getPathProvider().getPrefix()); 115 | Assertions.assertFalse(config.getTemplateFile().getPathProvider().isIncludesPackagePath()); 116 | Assertions.assertFalse(config.getTemplateFile().getPathProvider().isSeparateDirectoryPerMapper()); 117 | Assertions.assertFalse(config.getTemplateFile().getPathProvider().isIncludesMapperNameWhenSeparateDirectory()); 118 | Assertions.assertFalse(config.getTemplateFile().getPathProvider().isCacheEnabled()); 119 | Assertions.assertEquals("dollar", config.getFreemarkerSettings().get("interpolation_syntax")); 120 | Assertions.assertEquals("yes", config.getFreemarkerSettings().get("whitespace_stripping")); 121 | } 122 | 123 | @Test 124 | @SuppressWarnings("deprecation") 125 | void newInstanceWithConsumer() { 126 | FreeMarkerLanguageDriverConfig config = FreeMarkerLanguageDriverConfig.newInstance(c -> { 127 | c.setBasePackage("sqls"); 128 | }); 129 | Assertions.assertEquals("sql", config.getBasePackage()); 130 | Assertions.assertEquals("sql", config.getTemplateFile().getBaseDir()); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker; 17 | 18 | import java.io.IOException; 19 | import java.io.StringReader; 20 | import java.nio.charset.StandardCharsets; 21 | 22 | import org.apache.ibatis.executor.parameter.ParameterHandler; 23 | import org.apache.ibatis.mapping.BoundSql; 24 | import org.apache.ibatis.mapping.MappedStatement; 25 | import org.apache.ibatis.mapping.SqlSource; 26 | import org.apache.ibatis.parsing.XNode; 27 | import org.apache.ibatis.scripting.LanguageDriver; 28 | import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; 29 | import org.apache.ibatis.session.Configuration; 30 | import org.mybatis.scripting.freemarker.support.TemplateFilePathProvider; 31 | 32 | import freemarker.cache.ClassTemplateLoader; 33 | import freemarker.cache.TemplateLoader; 34 | import freemarker.template.Template; 35 | import freemarker.template.TemplateException; 36 | 37 | /** 38 | * Adds FreeMarker templates support to scripting in MyBatis. If you want to change or extend template loader 39 | * configuration, use can inherit from this class and override {@link #createFreeMarkerConfiguration()} method. 40 | * 41 | * @author elwood 42 | * @author Kazuki Shimizu 43 | */ 44 | public class FreeMarkerLanguageDriver implements LanguageDriver { 45 | 46 | protected final FreeMarkerLanguageDriverConfig driverConfig; 47 | protected final freemarker.template.Configuration freemarkerCfg; 48 | 49 | /** 50 | * Constructor. 51 | * 52 | * @see FreeMarkerLanguageDriverConfig#newInstance() 53 | */ 54 | public FreeMarkerLanguageDriver() { 55 | this(FreeMarkerLanguageDriverConfig.newInstance()); 56 | } 57 | 58 | /** 59 | * Constructor. 60 | * 61 | * @param driverConfig 62 | * a language driver configuration 63 | * 64 | * @since 1.2.0 65 | */ 66 | public FreeMarkerLanguageDriver(FreeMarkerLanguageDriverConfig driverConfig) { 67 | this.driverConfig = driverConfig; 68 | this.freemarkerCfg = createFreeMarkerConfiguration(); 69 | TemplateFilePathProvider.setLanguageDriverConfig(driverConfig); 70 | } 71 | 72 | /** 73 | * Creates the {@link freemarker.template.Configuration} instance and sets it up. If you want to change it (set 74 | * another props, for example), you can override it in inherited class and use your own class in @Lang directive. 75 | */ 76 | protected freemarker.template.Configuration createFreeMarkerConfiguration() { 77 | freemarker.template.Configuration cfg = new freemarker.template.Configuration( 78 | freemarker.template.Configuration.VERSION_2_3_22); 79 | 80 | TemplateLoader templateLoader = new ClassTemplateLoader(this.getClass().getClassLoader(), 81 | driverConfig.getTemplateFile().getBaseDir()); 82 | cfg.setTemplateLoader(templateLoader); 83 | 84 | // To avoid formatting numbers using spaces and commas in SQL 85 | cfg.setNumberFormat("computer"); 86 | 87 | // Because it defaults to default system encoding, we should set it always explicitly 88 | cfg.setDefaultEncoding(StandardCharsets.UTF_8.name()); 89 | 90 | driverConfig.getFreemarkerSettings().forEach((name, value) -> { 91 | try { 92 | cfg.setSetting(name, value); 93 | } catch (TemplateException e) { 94 | throw new IllegalStateException( 95 | String.format("Fail to configure FreeMarker template setting. name[%s] value[%s]", name, value), e); 96 | } 97 | }); 98 | 99 | return cfg; 100 | } 101 | 102 | /** 103 | * Creates a {@link ParameterHandler} that passes the actual parameters to the the JDBC statement. 104 | * 105 | * @see DefaultParameterHandler 106 | * 107 | * @param mappedStatement 108 | * The mapped statement that is being executed 109 | * @param parameterObject 110 | * The input parameter object (can be null) 111 | * @param boundSql 112 | * The resulting SQL once the dynamic language has been executed. 113 | */ 114 | @Override 115 | public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, 116 | BoundSql boundSql) { 117 | // As default XMLLanguageDriver 118 | return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); 119 | } 120 | 121 | /** 122 | * Creates an {@link SqlSource} that will hold the statement read from a mapper xml file. It is called during startup, 123 | * when the mapped statement is read from a class or an xml file. 124 | * 125 | * @param configuration 126 | * The MyBatis configuration 127 | * @param script 128 | * XNode parsed from a XML file 129 | * @param parameterType 130 | * input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be 131 | * null. 132 | */ 133 | @Override 134 | public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) { 135 | return createSqlSource(configuration, script.getNode().getTextContent()); 136 | } 137 | 138 | /** 139 | * Creates an {@link SqlSource} that will hold the statement read from an annotation. It is called during startup, 140 | * when the mapped statement is read from a class or an xml file. 141 | * 142 | * @param configuration 143 | * The MyBatis configuration 144 | * @param script 145 | * The content of the annotation 146 | * @param parameterType 147 | * input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be 148 | * null. 149 | */ 150 | @Override 151 | public SqlSource createSqlSource(Configuration configuration, String script, Class parameterType) { 152 | return createSqlSource(configuration, script); 153 | } 154 | 155 | protected SqlSource createSqlSource(Template template, Configuration configuration) { 156 | return new FreeMarkerSqlSource(template, configuration, freemarkerCfg.getIncompatibleImprovements()); 157 | } 158 | 159 | private SqlSource createSqlSource(Configuration configuration, String scriptText) { 160 | Template template; 161 | if (scriptText.trim().contains(" ")) { 162 | // Consider that script is inline script 163 | try { 164 | template = new Template(null, new StringReader(scriptText), freemarkerCfg); 165 | } catch (IOException e) { 166 | throw new RuntimeException(e); 167 | } 168 | } else { 169 | // Consider that script is template name, trying to find the template in classpath 170 | try { 171 | template = freemarkerCfg.getTemplate(scriptText.trim()); 172 | } catch (IOException e) { 173 | throw new RuntimeException(e); 174 | } 175 | } 176 | 177 | return createSqlSource(template, configuration); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.3.4 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. >&2 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. >&2 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. >&2 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. >&2 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | 121 | @REM Maven main class is here to fix maven 4.0.0-beta-5 through 4.0.0-rc-4 122 | set MAVEN_MAIN_CLASS=org.apache.maven.cling.MavenCling 123 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 124 | 125 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" 126 | 127 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 128 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 129 | ) 130 | 131 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 132 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 133 | if exist %WRAPPER_JAR% ( 134 | if "%MVNW_VERBOSE%" == "true" ( 135 | echo Found %WRAPPER_JAR% 136 | ) 137 | ) else ( 138 | if not "%MVNW_REPOURL%" == "" ( 139 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" 140 | ) 141 | if "%MVNW_VERBOSE%" == "true" ( 142 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 143 | echo Downloading from: %WRAPPER_URL% 144 | ) 145 | 146 | powershell -Command "&{"^ 147 | "$webclient = new-object System.Net.WebClient;"^ 148 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 149 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 150 | "}"^ 151 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 152 | "}" 153 | if "%MVNW_VERBOSE%" == "true" ( 154 | echo Finished downloading %WRAPPER_JAR% 155 | ) 156 | ) 157 | @REM End of extension 158 | 159 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 160 | SET WRAPPER_SHA_256_SUM="" 161 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 162 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 163 | ) 164 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 165 | powershell -Command "&{"^ 166 | "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ 167 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 168 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 169 | " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 170 | " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 171 | " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 172 | " exit 1;"^ 173 | "}"^ 174 | "}" 175 | if ERRORLEVEL 1 goto error 176 | ) 177 | 178 | @REM Provide a "standardized" way to retrieve the CLI args that will 179 | @REM work with both Windows and non-Windows executions. 180 | set MAVEN_CMD_LINE_ARGS=%* 181 | 182 | %MAVEN_JAVA_EXE% ^ 183 | %JVM_CONFIG_MAVEN_PROPS% ^ 184 | %MAVEN_OPTS% ^ 185 | %MAVEN_DEBUG_OPTS% ^ 186 | -classpath %WRAPPER_JAR% ^ 187 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 188 | "-Dmaven.mainClass=%MAVEN_MAIN_CLASS%" ^ 189 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 190 | if ERRORLEVEL 1 goto error 191 | goto end 192 | 193 | :error 194 | set ERROR_CODE=1 195 | 196 | :end 197 | @endlocal & set ERROR_CODE=%ERROR_CODE% 198 | 199 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 200 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 201 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 202 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 203 | :skipRcPost 204 | 205 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 206 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 207 | 208 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 209 | 210 | cmd /C exit /B %ERROR_CODE% 211 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/freemarker/support/TemplateFilePathProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker.support; 17 | 18 | import java.io.IOException; 19 | import java.lang.reflect.Method; 20 | import java.util.Optional; 21 | import java.util.concurrent.ConcurrentHashMap; 22 | import java.util.concurrent.ConcurrentMap; 23 | 24 | import org.apache.ibatis.builder.annotation.ProviderContext; 25 | import org.apache.ibatis.io.Resources; 26 | import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver; 27 | import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig; 28 | import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig.TemplateFileConfig.PathProviderConfig; 29 | 30 | /** 31 | * The SQL provider class that return the SQL template file path. 32 | *

33 | * IMPORTANT: This class required to use with mybatis 3.5.1+ and need to use with SQL provider annotation (such 34 | * as {@link org.apache.ibatis.annotations.SelectProvider} as follow: 35 | *

36 | * 37 | *

 38 |  * package com.example.mapper;
 39 |  *
 40 |  * public interface BaseMapper<T> {
 41 |  *
 42 |  *   @Options(useGeneratedKeys = true, keyProperty = "id")
 43 |  *   @InsertProvider(type = TemplateFilePathProvider.class)
 44 |  *   void insert(T entity);
 45 |  *
 46 |  *   @UpdateProvider(type = TemplateFilePathProvider.class)
 47 |  *   void update(T entity);
 48 |  *
 49 |  *   @DeleteProvider(type = TemplateFilePathProvider.class)
 50 |  *   void delete(T entity);
 51 |  *
 52 |  *   @SelectProvider(type = TemplateFilePathProvider.class)
 53 |  *   T findById(Integer id);
 54 |  *
 55 |  * }
 56 |  * 
57 | * 58 | *
 59 |  * package com.example.mapper;
 60 |  *
 61 |  * public interface NameMapper extends BaseMapper {
 62 |  *
 63 |  *   @SelectProvider(type = TemplateFilePathProvider.class)
 64 |  *   List<Name> findByConditions(NameConditions conditions);
 65 |  *
 66 |  * }
 67 |  * 
68 | * 69 | * @author Kazuki Shimizu 70 | * 71 | * @version 1.2.0 72 | */ 73 | public class TemplateFilePathProvider { 74 | 75 | private static final PathGenerator DEFAULT_PATH_GENERATOR = TemplateFilePathProvider::generateTemplatePath; 76 | private static final FreeMarkerLanguageDriverConfig DEFAULT_LANGUAGE_DRIVER_CONFIG = FreeMarkerLanguageDriverConfig 77 | .newInstance(); 78 | 79 | private static PathGenerator pathGenerator = DEFAULT_PATH_GENERATOR; 80 | private static FreeMarkerLanguageDriverConfig languageDriverConfig = DEFAULT_LANGUAGE_DRIVER_CONFIG; 81 | 82 | private static ConcurrentMap cache = new ConcurrentHashMap<>(); 83 | 84 | private TemplateFilePathProvider() { 85 | // NOP 86 | } 87 | 88 | /** 89 | * Set custom implementation for {@link PathGenerator}. 90 | * 91 | * @param pathGenerator 92 | * a instance for generating a template file path 93 | */ 94 | public static void setCustomTemplateFilePathGenerator(PathGenerator pathGenerator) { 95 | TemplateFilePathProvider.pathGenerator = Optional.ofNullable(pathGenerator).orElse(DEFAULT_PATH_GENERATOR); 96 | } 97 | 98 | /** 99 | * Set a configuration instance for {@link FreeMarkerLanguageDriver}. 100 | *

101 | * By default, {@link FreeMarkerLanguageDriverConfig#newInstance()} will used. 102 | *

103 | * If you applied an user define {@link FreeMarkerLanguageDriverConfig} for {@link FreeMarkerLanguageDriver}, please 104 | * same instance to the this class. 105 | * 106 | * @param languageDriverConfig 107 | * A user defined {@link FreeMarkerLanguageDriverConfig} 108 | */ 109 | public static void setLanguageDriverConfig(FreeMarkerLanguageDriverConfig languageDriverConfig) { 110 | TemplateFilePathProvider.languageDriverConfig = Optional.ofNullable(languageDriverConfig) 111 | .orElse(DEFAULT_LANGUAGE_DRIVER_CONFIG); 112 | } 113 | 114 | /** 115 | * Provide an SQL scripting string(template file path). 116 | *

117 | * By default implementation, a template file path resolve following format and priority order. If does not match all, 118 | * it throw an exception that indicate not found a template file. 119 | *

    120 | *
  • com/example/mapper/NameMapper/NameMapper-{methodName}-{databaseId}.ftl
  • 121 | *
  • com/example/mapper/NameMapper/NameMapper-{methodName}.ftl (fallback using default database)
  • 122 | *
  • com/example/mapper/BaseMapper/BaseMapper-{methodName}-{databaseId}.ftl (fallback using declaring class of 123 | * method)
  • 124 | *
  • com/example/mapper/BaseMapper/BaseMapper-{methodName}.ftl (fallback using declaring class of method and default 125 | * database)
  • 126 | *
127 | * 128 | * @param context 129 | * a context of SQL provider 130 | * 131 | * @return an SQL scripting string(template file path) 132 | */ 133 | @SuppressWarnings("unused") 134 | public static String provideSql(ProviderContext context) { 135 | return languageDriverConfig.getTemplateFile().getPathProvider().isCacheEnabled() 136 | ? cache.computeIfAbsent(context, c -> providePath(c.getMapperType(), c.getMapperMethod(), c.getDatabaseId())) 137 | : providePath(context.getMapperType(), context.getMapperMethod(), context.getDatabaseId()); 138 | } 139 | 140 | /** 141 | * Clear cache. 142 | */ 143 | public static void clearCache() { 144 | cache.clear(); 145 | } 146 | 147 | static String providePath(Class mapperType, Method mapperMethod, String databaseId) { 148 | boolean fallbackDeclaringClass = mapperType != mapperMethod.getDeclaringClass(); 149 | boolean fallbackDatabase = databaseId != null; 150 | String path = pathGenerator.generatePath(mapperType, mapperMethod, databaseId); 151 | if (exists(path)) { 152 | return path; 153 | } 154 | if (fallbackDatabase) { 155 | path = pathGenerator.generatePath(mapperType, mapperMethod, null); 156 | if (exists(path)) { 157 | return path; 158 | } 159 | } 160 | if (fallbackDeclaringClass) { 161 | path = pathGenerator.generatePath(mapperMethod.getDeclaringClass(), mapperMethod, databaseId); 162 | if (exists(path)) { 163 | return path; 164 | } 165 | if (fallbackDatabase) { 166 | path = pathGenerator.generatePath(mapperMethod.getDeclaringClass(), mapperMethod, null); 167 | if (exists(path)) { 168 | return path; 169 | } 170 | } 171 | } 172 | throw new IllegalStateException("The SQL template file not found. mapperType:[" + mapperType + "] mapperMethod:[" 173 | + mapperMethod + "] databaseId:[" + databaseId + "]"); 174 | } 175 | 176 | private static String generateTemplatePath(Class type, Method method, String databaseId) { 177 | Package pkg = type.getPackage(); 178 | String packageName = pkg == null ? "" : pkg.getName(); 179 | String className = type.getName().substring(packageName.length() + (packageName.isEmpty() ? 0 : 1)); 180 | 181 | PathProviderConfig pathProviderConfig = languageDriverConfig.getTemplateFile().getPathProvider(); 182 | StringBuilder path = new StringBuilder(); 183 | if (!pathProviderConfig.getPrefix().isEmpty()) { 184 | path.append(pathProviderConfig.getPrefix()); 185 | } 186 | if (pathProviderConfig.isIncludesPackagePath() && !packageName.isEmpty()) { 187 | path.append(packageName.replace('.', '/')).append('/'); 188 | } 189 | path.append(className); 190 | if (pathProviderConfig.isSeparateDirectoryPerMapper()) { 191 | path.append('/'); 192 | if (pathProviderConfig.isIncludesMapperNameWhenSeparateDirectory()) { 193 | path.append(className).append('-'); 194 | } 195 | } else { 196 | path.append('-'); 197 | } 198 | path.append(method.getName()); 199 | if (databaseId != null) { 200 | path.append('-').append(databaseId); 201 | } 202 | path.append(".ftl"); 203 | return path.toString(); 204 | } 205 | 206 | private static boolean exists(String path) { 207 | String basePath = languageDriverConfig.getTemplateFile().getBaseDir(); 208 | String actualPath = basePath.isEmpty() ? path : basePath + (basePath.endsWith("/") ? "" : "/") + path; 209 | try { 210 | Resources.getResourceURL(actualPath); 211 | return true; 212 | } catch (IOException e) { 213 | return false; 214 | } 215 | } 216 | 217 | /** 218 | * The interface that implements a function for generating template file path. 219 | */ 220 | @FunctionalInterface 221 | public interface PathGenerator { 222 | 223 | /** 224 | * Generate a template file path. 225 | * 226 | * @param type 227 | * mapper interface type that specified provider (or declaring interface type of mapper method) 228 | * @param method 229 | * a mapper method that specified provider 230 | * @param databaseId 231 | * a database id that provided from {@link org.apache.ibatis.mapping.DatabaseIdProvider} 232 | * 233 | * @return a template file path 234 | */ 235 | String generatePath(Class type, Method method, String databaseId); 236 | 237 | } 238 | 239 | } 240 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/freemarker/support/TemplateFilePathProviderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-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.freemarker.support; 17 | 18 | import java.lang.reflect.Method; 19 | import java.util.Arrays; 20 | 21 | import org.junit.jupiter.api.AfterAll; 22 | import org.junit.jupiter.api.AfterEach; 23 | import org.junit.jupiter.api.Assertions; 24 | import org.junit.jupiter.api.BeforeAll; 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.Test; 27 | import org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig; 28 | 29 | class TemplateFilePathProviderTest { 30 | 31 | @BeforeAll 32 | static void setup() { 33 | System.setProperty("mybatis-freemarker.config.file", "mybatis-freemarker-empty.properties"); 34 | } 35 | 36 | @AfterAll 37 | static void restore() { 38 | System.clearProperty("mybatis-freemarker.config.file"); 39 | } 40 | 41 | @BeforeEach 42 | @AfterEach 43 | void clean() { 44 | TemplateFilePathProvider.setCustomTemplateFilePathGenerator(null); 45 | TemplateFilePathProvider.setLanguageDriverConfig(FreeMarkerLanguageDriverConfig.newInstance()); 46 | } 47 | 48 | @Test 49 | void withoutDatabaseId() { 50 | String path = TemplateFilePathProvider.providePath(TestMapper.class, extractMethod(TestMapper.class, "update"), 51 | null); 52 | Assertions.assertEquals("org/mybatis/scripting/freemarker/support/TestMapper/TestMapper-update.ftl", path); 53 | } 54 | 55 | @Test 56 | void withDatabaseId() { 57 | String path = TemplateFilePathProvider.providePath(TestMapper.class, extractMethod(TestMapper.class, "update"), 58 | "h2"); 59 | Assertions.assertEquals("org/mybatis/scripting/freemarker/support/TestMapper/TestMapper-update-h2.ftl", path); 60 | } 61 | 62 | @Test 63 | void fallbackWithDefaultDatabase() { 64 | String path = TemplateFilePathProvider.providePath(TestMapper.class, extractMethod(TestMapper.class, "delete"), 65 | "h2"); 66 | Assertions.assertEquals("org/mybatis/scripting/freemarker/support/TestMapper/TestMapper-delete.ftl", path); 67 | } 68 | 69 | @Test 70 | void fallbackDeclaringClassWithoutDatabaseId() { 71 | String path = TemplateFilePathProvider.providePath(TestMapper.class, extractMethod(TestMapper.class, "insert"), 72 | null); 73 | Assertions.assertEquals("org/mybatis/scripting/freemarker/support/BaseMapper/BaseMapper-insert.ftl", path); 74 | } 75 | 76 | @Test 77 | void fallbackDeclaringClassWithDatabaseId() { 78 | String path = TemplateFilePathProvider.providePath(TestMapper.class, extractMethod(TestMapper.class, "insert"), 79 | "h2"); 80 | Assertions.assertEquals("org/mybatis/scripting/freemarker/support/BaseMapper/BaseMapper-insert-h2.ftl", path); 81 | } 82 | 83 | @Test 84 | void fallbackDeclaringClassAndDefaultDatabase() { 85 | String path = TemplateFilePathProvider.providePath(TestMapper.class, extractMethod(TestMapper.class, "count"), 86 | "h2"); 87 | Assertions.assertEquals("org/mybatis/scripting/freemarker/support/BaseMapper/BaseMapper-count.ftl", path); 88 | } 89 | 90 | @Test 91 | void notFoundSqlFile() { 92 | IllegalStateException e = Assertions.assertThrows(IllegalStateException.class, () -> TemplateFilePathProvider 93 | .providePath(TestMapper.class, extractMethod(TestMapper.class, "selectOne"), "h2")); 94 | Assertions.assertEquals( 95 | "The SQL template file not found. mapperType:[interface org.mybatis.scripting.freemarker.support.TestMapper] mapperMethod:[public abstract java.lang.Object org.mybatis.scripting.freemarker.support.BaseMapper.selectOne(int)] databaseId:[h2]", 96 | e.getMessage()); 97 | } 98 | 99 | @Test 100 | void notFoundSqlFileWithoutDatabaseId() { 101 | IllegalStateException e = Assertions.assertThrows(IllegalStateException.class, () -> TemplateFilePathProvider 102 | .providePath(TestMapper.class, extractMethod(TestMapper.class, "selectOne"), null)); 103 | Assertions.assertEquals( 104 | "The SQL template file not found. mapperType:[interface org.mybatis.scripting.freemarker.support.TestMapper] mapperMethod:[public abstract java.lang.Object org.mybatis.scripting.freemarker.support.BaseMapper.selectOne(int)] databaseId:[null]", 105 | e.getMessage()); 106 | } 107 | 108 | @Test 109 | void notFoundSqlFileWithoutFallbackDeclaringClass() { 110 | IllegalStateException e = Assertions.assertThrows(IllegalStateException.class, () -> TemplateFilePathProvider 111 | .providePath(TestMapper.class, extractMethod(TestMapper.class, "selectAllByFirstName"), null)); 112 | Assertions.assertEquals( 113 | "The SQL template file not found. mapperType:[interface org.mybatis.scripting.freemarker.support.TestMapper] mapperMethod:[public abstract java.util.List org.mybatis.scripting.freemarker.support.TestMapper.selectAllByFirstName(java.lang.String)] databaseId:[null]", 114 | e.getMessage()); 115 | } 116 | 117 | @Test 118 | void includesPackagePathAndSeparatesDirectoryPerMapperIsFalse() { 119 | TemplateFilePathProvider.setLanguageDriverConfig(FreeMarkerLanguageDriverConfig.newInstance(c -> { 120 | c.getTemplateFile().setBaseDir("org/mybatis/scripting/freemarker/support/sql"); 121 | c.getTemplateFile().getPathProvider().setIncludesPackagePath(false); 122 | c.getTemplateFile().getPathProvider().setSeparateDirectoryPerMapper(false); 123 | })); 124 | String path = TemplateFilePathProvider.providePath(TestMapper.class, 125 | extractMethod(TestMapper.class, "selectAllDesc"), null); 126 | Assertions.assertEquals("TestMapper-selectAllDesc.ftl", path); 127 | } 128 | 129 | @Test 130 | void baseDirEndWithSlash() { 131 | TemplateFilePathProvider.setLanguageDriverConfig(FreeMarkerLanguageDriverConfig.newInstance(c -> { 132 | c.getTemplateFile().setBaseDir("org/mybatis/scripting/freemarker/support/sql/"); 133 | c.getTemplateFile().getPathProvider().setIncludesPackagePath(false); 134 | c.getTemplateFile().getPathProvider().setSeparateDirectoryPerMapper(false); 135 | })); 136 | String path = TemplateFilePathProvider.providePath(TestMapper.class, 137 | extractMethod(TestMapper.class, "selectAllDesc"), null); 138 | Assertions.assertEquals("TestMapper-selectAllDesc.ftl", path); 139 | } 140 | 141 | @Test 142 | void includesMapperNameWhenSeparateDirectoryIsFalse() { 143 | TemplateFilePathProvider.setLanguageDriverConfig(FreeMarkerLanguageDriverConfig 144 | .newInstance(c -> c.getTemplateFile().getPathProvider().setIncludesMapperNameWhenSeparateDirectory(false))); 145 | String path = TemplateFilePathProvider.providePath(TestMapper.class, 146 | extractMethod(TestMapper.class, "selectAllAsc"), null); 147 | Assertions.assertEquals("org/mybatis/scripting/freemarker/support/TestMapper/selectAllAsc.ftl", path); 148 | } 149 | 150 | @Test 151 | void prefix() { 152 | TemplateFilePathProvider.setLanguageDriverConfig(FreeMarkerLanguageDriverConfig.newInstance(c -> { 153 | c.getTemplateFile().getPathProvider().setPrefix("org/mybatis/scripting/freemarker/support/sql/"); 154 | c.getTemplateFile().getPathProvider().setIncludesPackagePath(false); 155 | c.getTemplateFile().getPathProvider().setSeparateDirectoryPerMapper(false); 156 | })); 157 | String path = TemplateFilePathProvider.providePath(TestMapper.class, 158 | extractMethod(TestMapper.class, "selectAllDesc"), null); 159 | Assertions.assertEquals("org/mybatis/scripting/freemarker/support/sql/TestMapper-selectAllDesc.ftl", path); 160 | } 161 | 162 | @Test 163 | void defaultPackageMapper() throws ClassNotFoundException { 164 | TemplateFilePathProvider.setLanguageDriverConfig(FreeMarkerLanguageDriverConfig 165 | .newInstance(c -> c.getTemplateFile().setBaseDir("org/mybatis/scripting/freemarker/support/"))); 166 | Class mapperType = Class.forName("DefaultPackageNameMapper"); 167 | String path = TemplateFilePathProvider.providePath(mapperType, extractMethod(mapperType, "selectAllDesc"), null); 168 | Assertions.assertEquals("DefaultPackageNameMapper/DefaultPackageNameMapper-selectAllDesc.ftl", path); 169 | } 170 | 171 | @Test 172 | void defaultPackageMapperWithIncludesPackagePathIsFalse() throws ClassNotFoundException { 173 | TemplateFilePathProvider.setLanguageDriverConfig(FreeMarkerLanguageDriverConfig.newInstance(c -> { 174 | c.getTemplateFile().setBaseDir("org/mybatis/scripting/freemarker/support/"); 175 | c.getTemplateFile().getPathProvider().setIncludesPackagePath(false); 176 | })); 177 | Class mapperType = Class.forName("DefaultPackageNameMapper"); 178 | String path = TemplateFilePathProvider.providePath(mapperType, extractMethod(mapperType, "selectAllDesc"), null); 179 | Assertions.assertEquals("DefaultPackageNameMapper/DefaultPackageNameMapper-selectAllDesc.ftl", path); 180 | } 181 | 182 | @Test 183 | void customTemplateFileGenerator() { 184 | TemplateFilePathProvider.setCustomTemplateFilePathGenerator( 185 | (type, method, databaseId) -> type.getName().replace('.', '/') + "_" + method.getName() + ".ftl"); 186 | String path = TemplateFilePathProvider.providePath(TestMapper.class, extractMethod(TestMapper.class, "selectOne"), 187 | null); 188 | Assertions.assertEquals("org/mybatis/scripting/freemarker/support/BaseMapper_selectOne.ftl", path); 189 | 190 | } 191 | 192 | private Method extractMethod(Class type, String methodName) { 193 | return Arrays.stream(type.getMethods()).filter(m -> m.getName().equals(methodName)).findFirst().orElseThrow( 194 | () -> new IllegalArgumentException("The method not found. type:" + type + " methodName:" + methodName)); 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | https://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.4 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ]; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ]; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ]; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ]; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false 54 | darwin=false 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true ;; 59 | Darwin*) 60 | darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | JAVA_HOME="$(/usr/libexec/java_home)" 66 | export JAVA_HOME 67 | else 68 | JAVA_HOME="/Library/Java/Home" 69 | export JAVA_HOME 70 | fi 71 | fi 72 | ;; 73 | esac 74 | 75 | if [ -z "$JAVA_HOME" ]; then 76 | if [ -r /etc/gentoo-release ]; then 77 | JAVA_HOME=$(java-config --jre-home) 78 | fi 79 | fi 80 | 81 | # For Cygwin, ensure paths are in UNIX format before anything is touched 82 | if $cygwin; then 83 | [ -n "$JAVA_HOME" ] \ 84 | && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 85 | [ -n "$CLASSPATH" ] \ 86 | && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 87 | fi 88 | 89 | # For Mingw, ensure paths are in UNIX format before anything is touched 90 | if $mingw; then 91 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ 92 | && JAVA_HOME="$( 93 | cd "$JAVA_HOME" || ( 94 | echo "cannot cd into $JAVA_HOME." >&2 95 | exit 1 96 | ) 97 | pwd 98 | )" 99 | fi 100 | 101 | if [ -z "$JAVA_HOME" ]; then 102 | javaExecutable="$(which javac)" 103 | if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then 104 | # readlink(1) is not available as standard on Solaris 10. 105 | readLink=$(which readlink) 106 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 107 | if $darwin; then 108 | javaHome="$(dirname "$javaExecutable")" 109 | javaExecutable="$(cd "$javaHome" && pwd -P)/javac" 110 | else 111 | javaExecutable="$(readlink -f "$javaExecutable")" 112 | fi 113 | javaHome="$(dirname "$javaExecutable")" 114 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 115 | JAVA_HOME="$javaHome" 116 | export JAVA_HOME 117 | fi 118 | fi 119 | fi 120 | 121 | if [ -z "$JAVACMD" ]; then 122 | if [ -n "$JAVA_HOME" ]; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD="$JAVA_HOME/jre/sh/java" 126 | else 127 | JAVACMD="$JAVA_HOME/bin/java" 128 | fi 129 | else 130 | JAVACMD="$( 131 | \unset -f command 2>/dev/null 132 | \command -v java 133 | )" 134 | fi 135 | fi 136 | 137 | if [ ! -x "$JAVACMD" ]; then 138 | echo "Error: JAVA_HOME is not defined correctly." >&2 139 | echo " We cannot execute $JAVACMD" >&2 140 | exit 1 141 | fi 142 | 143 | if [ -z "$JAVA_HOME" ]; then 144 | echo "Warning: JAVA_HOME environment variable is not set." >&2 145 | fi 146 | 147 | # traverses directory structure from process work directory to filesystem root 148 | # first directory with .mvn subdirectory is considered project base directory 149 | find_maven_basedir() { 150 | if [ -z "$1" ]; then 151 | echo "Path not specified to find_maven_basedir" >&2 152 | return 1 153 | fi 154 | 155 | basedir="$1" 156 | wdir="$1" 157 | while [ "$wdir" != '/' ]; do 158 | if [ -d "$wdir"/.mvn ]; then 159 | basedir=$wdir 160 | break 161 | fi 162 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 163 | if [ -d "${wdir}" ]; then 164 | wdir=$( 165 | cd "$wdir/.." || exit 1 166 | pwd 167 | ) 168 | fi 169 | # end of workaround 170 | done 171 | printf '%s' "$( 172 | cd "$basedir" || exit 1 173 | pwd 174 | )" 175 | } 176 | 177 | # concatenates all lines of a file 178 | concat_lines() { 179 | if [ -f "$1" ]; then 180 | # Remove \r in case we run on Windows within Git Bash 181 | # and check out the repository with auto CRLF management 182 | # enabled. Otherwise, we may read lines that are delimited with 183 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 184 | # splitting rules. 185 | tr -s '\r\n' ' ' <"$1" 186 | fi 187 | } 188 | 189 | log() { 190 | if [ "$MVNW_VERBOSE" = true ]; then 191 | printf '%s\n' "$1" 192 | fi 193 | } 194 | 195 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 196 | if [ -z "$BASE_DIR" ]; then 197 | exit 1 198 | fi 199 | 200 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 201 | export MAVEN_PROJECTBASEDIR 202 | log "$MAVEN_PROJECTBASEDIR" 203 | 204 | trim() { 205 | # MWRAPPER-139: 206 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 207 | # Needed for removing poorly interpreted newline sequences when running in more 208 | # exotic environments such as mingw bash on Windows. 209 | printf "%s" "${1}" | tr -d '[:space:]' 210 | } 211 | 212 | ########################################################################################## 213 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 214 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 215 | ########################################################################################## 216 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 217 | if [ -r "$wrapperJarPath" ]; then 218 | log "Found $wrapperJarPath" 219 | else 220 | log "Couldn't find $wrapperJarPath, downloading it ..." 221 | 222 | if [ -n "$MVNW_REPOURL" ]; then 223 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" 224 | else 225 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" 226 | fi 227 | while IFS="=" read -r key value; do 228 | case "$key" in wrapperUrl) 229 | wrapperUrl=$(trim "${value-}") 230 | break 231 | ;; 232 | esac 233 | done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 234 | log "Downloading from: $wrapperUrl" 235 | 236 | if $cygwin; then 237 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 238 | fi 239 | 240 | if command -v wget >/dev/null; then 241 | log "Found wget ... using wget" 242 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 243 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 244 | wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 245 | else 246 | wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 247 | fi 248 | elif command -v curl >/dev/null; then 249 | log "Found curl ... using curl" 250 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 251 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 252 | curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 253 | else 254 | curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 255 | fi 256 | else 257 | log "Falling back to using Java to download" 258 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 260 | # For Cygwin, switch paths to Windows format before running javac 261 | if $cygwin; then 262 | javaSource=$(cygpath --path --windows "$javaSource") 263 | javaClass=$(cygpath --path --windows "$javaClass") 264 | fi 265 | if [ -e "$javaSource" ]; then 266 | if [ ! -e "$javaClass" ]; then 267 | log " - Compiling MavenWrapperDownloader.java ..." 268 | ("$JAVA_HOME/bin/javac" "$javaSource") 269 | fi 270 | if [ -e "$javaClass" ]; then 271 | log " - Running MavenWrapperDownloader.java ..." 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 282 | wrapperSha256Sum="" 283 | while IFS="=" read -r key value; do 284 | case "$key" in wrapperSha256Sum) 285 | wrapperSha256Sum=$(trim "${value-}") 286 | break 287 | ;; 288 | esac 289 | done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 290 | if [ -n "$wrapperSha256Sum" ]; then 291 | wrapperSha256Result=false 292 | if command -v sha256sum >/dev/null; then 293 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then 294 | wrapperSha256Result=true 295 | fi 296 | elif command -v shasum >/dev/null; then 297 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then 298 | wrapperSha256Result=true 299 | fi 300 | else 301 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 302 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 303 | exit 1 304 | fi 305 | if [ $wrapperSha256Result = false ]; then 306 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 307 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 308 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 309 | exit 1 310 | fi 311 | fi 312 | 313 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 314 | 315 | # For Cygwin, switch paths to Windows format before running java 316 | if $cygwin; then 317 | [ -n "$JAVA_HOME" ] \ 318 | && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 319 | [ -n "$CLASSPATH" ] \ 320 | && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 321 | [ -n "$MAVEN_PROJECTBASEDIR" ] \ 322 | && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 323 | fi 324 | 325 | # Provide a "standardized" way to retrieve the CLI args that will 326 | # work with both Windows and non-Windows executions. 327 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 328 | export MAVEN_CMD_LINE_ARGS 329 | 330 | # Maven main class is here to fix maven 4.0.0-beta-5 through 4.0.0-rc-4 331 | MAVEN_MAIN_CLASS=org.apache.maven.cling.MavenCling 332 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 333 | 334 | # shellcheck disable=SC2086 # safe args 335 | exec "$JAVACMD" \ 336 | $MAVEN_OPTS \ 337 | $MAVEN_DEBUG_OPTS \ 338 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 339 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 340 | "-Dmaven.mainClass=${MAVEN_MAIN_CLASS}" \ 341 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 342 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/freemarker/FreeMarkerLanguageDriverConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 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.freemarker; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.io.InputStreamReader; 22 | import java.nio.charset.Charset; 23 | import java.nio.charset.StandardCharsets; 24 | import java.util.Collections; 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | import java.util.Objects; 28 | import java.util.Optional; 29 | import java.util.Properties; 30 | import java.util.function.Consumer; 31 | import java.util.function.Function; 32 | 33 | import org.apache.commons.text.WordUtils; 34 | import org.apache.ibatis.io.Resources; 35 | import org.apache.ibatis.logging.Log; 36 | import org.apache.ibatis.logging.LogFactory; 37 | import org.apache.ibatis.reflection.DefaultReflectorFactory; 38 | import org.apache.ibatis.reflection.MetaObject; 39 | import org.apache.ibatis.reflection.factory.DefaultObjectFactory; 40 | import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory; 41 | 42 | /** 43 | * Configuration class for {@link FreeMarkerLanguageDriver}. 44 | * 45 | * @author Kazuki Shimizu 46 | * 47 | * @since 1.2.0 48 | */ 49 | public class FreeMarkerLanguageDriverConfig { 50 | private static final String PROPERTY_KEY_CONFIG_FILE = "mybatis-freemarker.config.file"; 51 | private static final String PROPERTY_KEY_CONFIG_ENCODING = "mybatis-freemarker.config.encoding"; 52 | private static final String DEFAULT_PROPERTIES_FILE = "mybatis-freemarker.properties"; 53 | private static final Map, Function> TYPE_CONVERTERS; 54 | 55 | static { 56 | Map, Function> converters = new HashMap<>(); 57 | converters.put(String.class, String::trim); 58 | converters.put(boolean.class, v -> Boolean.valueOf(v.trim())); 59 | converters.put(Object.class, v -> v); 60 | TYPE_CONVERTERS = Collections.unmodifiableMap(converters); 61 | } 62 | 63 | private static final Log log = LogFactory.getLog(FreeMarkerLanguageDriverConfig.class); 64 | 65 | /** 66 | * The configuration properties. 67 | */ 68 | private final Map freemarkerSettings = new HashMap<>(); 69 | 70 | /** 71 | * Template file configuration. 72 | */ 73 | private final TemplateFileConfig templateFile = new TemplateFileConfig(); 74 | 75 | /** 76 | * Get FreeMarker settings. 77 | * 78 | * @return FreeMarker settings 79 | */ 80 | public Map getFreemarkerSettings() { 81 | return freemarkerSettings; 82 | } 83 | 84 | /** 85 | * Get a base directory for reading template resources. 86 | *

87 | * Default is none (just under classpath). 88 | *

89 | * 90 | * @return a base directory for reading template resources 91 | * 92 | * @deprecated Recommend to use the {@link TemplateFileConfig#getBaseDir()}} because this method defined for keeping 93 | * backward compatibility (There is possibility that this method removed at a future version) 94 | */ 95 | @Deprecated 96 | public String getBasePackage() { 97 | return templateFile.getBaseDir(); 98 | } 99 | 100 | /** 101 | * Set a base directory for reading template resources. 102 | * 103 | * @param basePackage 104 | * a base directory for reading template resources 105 | * 106 | * @deprecated Recommend to use the {@link TemplateFileConfig#setBaseDir(String)} because this method defined for 107 | * keeping backward compatibility (There is possibility that this method removed at a future version) 108 | */ 109 | @Deprecated 110 | public void setBasePackage(String basePackage) { 111 | log.warn("The 'basePackage' has been deprecated since 1.2.0. Please use the 'templateFile.baseDir'."); 112 | templateFile.setBaseDir(basePackage); 113 | } 114 | 115 | /** 116 | * Get a template file configuration. 117 | * 118 | * @return a template file configuration 119 | */ 120 | public TemplateFileConfig getTemplateFile() { 121 | return templateFile; 122 | } 123 | 124 | /** 125 | * Template file configuration. 126 | */ 127 | public static class TemplateFileConfig { 128 | 129 | /** 130 | * The base directory for reading template resources. 131 | */ 132 | private String baseDir = ""; 133 | 134 | /** 135 | * The template file path provider configuration. 136 | */ 137 | private final PathProviderConfig pathProvider = new PathProviderConfig(); 138 | 139 | /** 140 | * Get the base directory for reading template resource file. 141 | *

142 | * Default is {@code ""}(none). 143 | *

144 | * 145 | * @return the base directory for reading template resource file 146 | */ 147 | public String getBaseDir() { 148 | return baseDir; 149 | } 150 | 151 | /** 152 | * Set the base directory for reading template resource file. 153 | * 154 | * @param baseDir 155 | * the base directory for reading template resource file 156 | */ 157 | public void setBaseDir(String baseDir) { 158 | this.baseDir = baseDir; 159 | } 160 | 161 | /** 162 | * Get the template file path provider configuration. 163 | * 164 | * @return the template file path provider configuration 165 | */ 166 | public PathProviderConfig getPathProvider() { 167 | return pathProvider; 168 | } 169 | 170 | /** 171 | * The template file path provider configuration. 172 | */ 173 | public static class PathProviderConfig { 174 | 175 | /** 176 | * The prefix for adding to template file path. 177 | */ 178 | private String prefix = ""; 179 | 180 | /** 181 | * Whether includes package path part. 182 | */ 183 | private boolean includesPackagePath = true; 184 | 185 | /** 186 | * Whether separate directory per mapper. 187 | */ 188 | private boolean separateDirectoryPerMapper = true; 189 | 190 | /** 191 | * Whether includes mapper name into file name when separate directory per mapper. 192 | */ 193 | private boolean includesMapperNameWhenSeparateDirectory = true; 194 | 195 | /** 196 | * Whether cache a resolved template file path. 197 | */ 198 | private boolean cacheEnabled = true; 199 | 200 | /** 201 | * Get a prefix for adding to template file path. 202 | *

203 | * Default is {@code ""}. 204 | *

205 | * 206 | * @return a prefix for adding to template file path 207 | */ 208 | public String getPrefix() { 209 | return prefix; 210 | } 211 | 212 | /** 213 | * Set the prefix for adding to template file path. 214 | * 215 | * @param prefix 216 | * The prefix for adding to template file path 217 | */ 218 | public void setPrefix(String prefix) { 219 | this.prefix = prefix; 220 | } 221 | 222 | /** 223 | * Get whether includes package path part. 224 | *

225 | * Default is {@code true}. 226 | *

227 | * 228 | * @return If includes package path, return {@code true} 229 | */ 230 | public boolean isIncludesPackagePath() { 231 | return includesPackagePath; 232 | } 233 | 234 | /** 235 | * Set whether includes package path part. 236 | * 237 | * @param includesPackagePath 238 | * If want to includes, set {@code true} 239 | */ 240 | public void setIncludesPackagePath(boolean includesPackagePath) { 241 | this.includesPackagePath = includesPackagePath; 242 | } 243 | 244 | /** 245 | * Get whether separate directory per mapper. 246 | * 247 | * @return If separate directory per mapper, return {@code true} 248 | */ 249 | public boolean isSeparateDirectoryPerMapper() { 250 | return separateDirectoryPerMapper; 251 | } 252 | 253 | /** 254 | * Set whether separate directory per mapper. 255 | *

256 | * Default is {@code true}. 257 | *

258 | * 259 | * @param separateDirectoryPerMapper 260 | * If want to separate directory, set {@code true} 261 | */ 262 | public void setSeparateDirectoryPerMapper(boolean separateDirectoryPerMapper) { 263 | this.separateDirectoryPerMapper = separateDirectoryPerMapper; 264 | } 265 | 266 | /** 267 | * Get whether includes mapper name into file name when separate directory per mapper. 268 | *

269 | * Default is {@code true}. 270 | *

271 | * 272 | * @return If includes mapper name, return {@code true} 273 | */ 274 | public boolean isIncludesMapperNameWhenSeparateDirectory() { 275 | return includesMapperNameWhenSeparateDirectory; 276 | } 277 | 278 | /** 279 | * Set whether includes mapper name into file name when separate directory per mapper. 280 | *

281 | * Default is {@code true}. 282 | *

283 | * 284 | * @param includesMapperNameWhenSeparateDirectory 285 | * If want to includes, set {@code true} 286 | */ 287 | public void setIncludesMapperNameWhenSeparateDirectory(boolean includesMapperNameWhenSeparateDirectory) { 288 | this.includesMapperNameWhenSeparateDirectory = includesMapperNameWhenSeparateDirectory; 289 | } 290 | 291 | /** 292 | * Get whether cache a resolved template file path. 293 | *

294 | * Default is {@code true}. 295 | *

296 | * 297 | * @return If cache a resolved template file path, return {@code true} 298 | */ 299 | public boolean isCacheEnabled() { 300 | return cacheEnabled; 301 | } 302 | 303 | /** 304 | * Set whether cache a resolved template file path. 305 | * 306 | * @param cacheEnabled 307 | * If want to cache, set {@code true} 308 | */ 309 | public void setCacheEnabled(boolean cacheEnabled) { 310 | this.cacheEnabled = cacheEnabled; 311 | } 312 | 313 | } 314 | 315 | } 316 | 317 | /** 318 | * Create an instance from default properties file.
319 | * If you want to customize a default {@code TemplateEngine}, you can configure some property using 320 | * mybatis-freemarker.properties that encoded by UTF-8. Also, you can change the properties file that will read using 321 | * system property (-Dmybatis-freemarker.config.file=... -Dmybatis-freemarker.config.encoding=...).
322 | * Supported properties are as follows: 323 | * 324 | * 325 | * 326 | * 327 | * 328 | * 329 | * 330 | * 331 | * 332 | * 333 | * 334 | * 335 | * 336 | * 337 | * 338 | * 339 | * 340 | * 341 | * 342 | * 343 | *
Supported properties
Property KeyDescriptionDefault
General configuration
base-packageThe base directory for reading template resourcesNone(just under classpath)
freemarker-settings.*The settings of freemarker {@link freemarker.core.Configurable#setSetting(String, String)}).-
344 | * 345 | * @return a configuration instance 346 | */ 347 | public static FreeMarkerLanguageDriverConfig newInstance() { 348 | return newInstance(loadDefaultProperties()); 349 | } 350 | 351 | /** 352 | * Create an instance from specified properties. 353 | * 354 | * @param customProperties 355 | * custom configuration properties 356 | * 357 | * @return a configuration instance 358 | * 359 | * @see #newInstance() 360 | */ 361 | public static FreeMarkerLanguageDriverConfig newInstance(Properties customProperties) { 362 | FreeMarkerLanguageDriverConfig config = new FreeMarkerLanguageDriverConfig(); 363 | Properties properties = loadDefaultProperties(); 364 | Optional.ofNullable(customProperties).ifPresent(properties::putAll); 365 | override(config, properties); 366 | return config; 367 | } 368 | 369 | /** 370 | * Create an instance using specified customizer and override using a default properties file. 371 | * 372 | * @param customizer 373 | * baseline customizer 374 | * 375 | * @return a configuration instance 376 | * 377 | * @see #newInstance() 378 | */ 379 | public static FreeMarkerLanguageDriverConfig newInstance(Consumer customizer) { 380 | FreeMarkerLanguageDriverConfig config = new FreeMarkerLanguageDriverConfig(); 381 | customizer.accept(config); 382 | override(config, loadDefaultProperties()); 383 | return config; 384 | } 385 | 386 | private static void override(FreeMarkerLanguageDriverConfig config, Properties properties) { 387 | MetaObject metaObject = MetaObject.forObject(config, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), 388 | new DefaultReflectorFactory()); 389 | properties.forEach((key, value) -> { 390 | String propertyPath = WordUtils.uncapitalize(WordUtils.capitalize(Objects.toString(key), '-').replace("-", "")); 391 | Optional.ofNullable(value).ifPresent(v -> { 392 | Object convertedValue = TYPE_CONVERTERS.get(metaObject.getSetterType(propertyPath)).apply(value.toString()); 393 | metaObject.setValue(propertyPath, convertedValue); 394 | }); 395 | }); 396 | } 397 | 398 | private static Properties loadDefaultProperties() { 399 | return loadProperties(System.getProperty(PROPERTY_KEY_CONFIG_FILE, DEFAULT_PROPERTIES_FILE)); 400 | } 401 | 402 | private static Properties loadProperties(String resourcePath) { 403 | Properties properties = new Properties(); 404 | InputStream in; 405 | try { 406 | in = Resources.getResourceAsStream(resourcePath); 407 | } catch (IOException e) { 408 | in = null; 409 | } 410 | if (in != null) { 411 | Charset encoding = Optional.ofNullable(System.getProperty(PROPERTY_KEY_CONFIG_ENCODING)).map(Charset::forName) 412 | .orElse(StandardCharsets.UTF_8); 413 | try (InputStreamReader inReader = new InputStreamReader(in, encoding); 414 | BufferedReader bufReader = new BufferedReader(inReader)) { 415 | properties.load(bufReader); 416 | } catch (IOException e) { 417 | throw new IllegalStateException(e); 418 | } 419 | } 420 | return properties; 421 | } 422 | 423 | } 424 | -------------------------------------------------------------------------------- /src/site/xdoc/index.xml.vm: -------------------------------------------------------------------------------- 1 | 2 | 19 | #set( $D = '$' ) 20 | #set( $N = '#if' ) 21 | 23 | 24 | MyBatis FreeMarker 25 | The MyBatis Team 26 | 27 | 28 | 29 |
30 |

31 | The mybatis-freemarker is a plugin that helps creating big dynamic SQL queries. You can use it selectively, to only queries that need if statmenets or foreach-loops, for example. But it is possible to use this syntax by default too. 32 |

33 |

34 | If you are not familiar with FreeMarker syntax, you can view 35 |

36 | 41 |
42 | 43 |
44 |

45 | If you are using maven, you can add this: 46 |

47 | 49 | org.mybatis.scripting 50 | mybatis-freemarker 51 | ${project.version} 52 | 53 | ]]> 54 | 55 |

If you are using gradle, you can use this snippet:

56 | 57 | 61 | 62 |
63 | 64 |
65 |
    66 |
  • Checkout the source code
  • 67 |
  • Run mvn install to build and to automatically install it to your local maven repo
  • 68 |
  • Add maven dependency to your project 69 | 71 | org.mybatis.scripting 72 | mybatis-freemarker 73 | ${project.version} 74 | ]]> 75 |
  • 76 |
77 |
78 | 79 |
80 | 81 |

82 | By default, the mybatis-freemarker.properties file create in your classpath. 83 |

84 |
    85 |
  • 86 | You can define the base directory to search FreeMarker templates. 87 | By default it is empty string, so you will need to provide full path to template every time. 88 | 92 | 93 | The basePackage property supported for keeping backward compatibility with old versions(1.1.x or under). 94 | 98 |
  • 99 |
  • 100 | You can customize the path provider of SQL provider class that return the SQL template file path. 101 | For details see the "TemplateFilePathProvider" 102 | 107 |
  • 108 |
  • 109 | Since 1.2.0, you configure the FreeMarker configuration. 110 | About available setting name, please refer to the API documentation of FreeMarker. 111 | 116 |
  • 117 |
  • 118 | Since 1.2.0, you can use an any properties file or encoding as follow: 119 | 122 |
  • 123 |
124 | 125 |
126 | 127 |

128 | You may need to do next steps: 129 |

130 |
    131 |
  • Register the language driver alias in your mybatis configuration file: 132 | 134 | ... 135 | 136 | 137 | 138 | ... 139 | ]]> 140 |
  • 141 |
  • (Optional) Set the freemarker as your default scripting language: 142 | 144 | ... 145 | 146 | 147 | 148 | ... 149 | ]]> 150 |
  • 151 |
152 |
153 |
154 | 155 |
156 |

Just write your queries using FreeMarker syntax:

157 | findNamesByIds(@Param("ids") List ids);]]> 161 |

If any whitespace found inside @Select text, it is interpreted as inline script, not template name. It is convenient to avoid creating templates when script is really small. If you have a large SQL script, you can place it in distinct template and write next code:

162 | 166 |

Template will be searched in classpath using basePackage property that has already been described above.

167 |

findName.ftl content can be:

168 | ]]> 172 |

]]> is a custom directive to generate #{n} markup. 173 | This markup further will be passed into MyBatis engine, and it will replace this to ?-parameter. 174 | You can't write #{paramName} directly, because FreeMarker supports this syntax natively 175 | (alghough it is deprecated). So, to get ?-parameters to prepared statements works, you need 176 | to use ${D}{r"#{paramName}"} verbose syntax, or this directive. By the way, in XML 177 | files ${D}{r"#{paramName}"} is more preferrable because you don't need wrap it using 178 | CDATA statements. In annotations and in external templates <@p/> 179 | directive is more neat.

180 |
181 | 182 |
183 |

As in annotations, you can write inline scripts or template names.

184 | 186 | id, ${D}{r"firstName"}, lastName 187 | 188 | 191 | 192 | 195 | 196 | 198 |