diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..8e7bc9ae72 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# +# Copyright 2010-2022 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +root = true + +[*.{java, xml, sql}] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000000..e145b28d15 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,34 @@ +name: Java CI + +on: [workflow_dispatch, push, pull_request] + +permissions: read-all + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + matrix: + cache: [maven] + distribution: [temurin] + java: [21, 25, 26-ea] + os: [macos-latest, ubuntu-latest, windows-latest] + fail-fast: false + max-parallel: 6 + name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} + + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - name: Setup Java ${{ matrix.java }} ${{ matrix.distribution }} + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + with: + cache: ${{ matrix.cache }} + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.java }} + - name: Test with Maven + run: ./mvnw test --batch-mode --no-transfer-progress --show-version -D"license.skip=true" diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 0000000000..86a1ce6d93 --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,45 @@ +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: '43 10 * * 2' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze + runs-on: 'ubuntu-latest' + timeout-minutes: 30 + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + + - name: Setup Java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + with: + cache: maven + distribution: 'temurin' + java-version: 25 + + - name: Initialize CodeQL + uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4 + with: + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4 diff --git a/.github/workflows/coveralls.yaml b/.github/workflows/coveralls.yaml new file mode 100644 index 0000000000..62f1da6a52 --- /dev/null +++ b/.github/workflows/coveralls.yaml @@ -0,0 +1,36 @@ +name: Coveralls + +on: [push, pull_request] + +permissions: read-all + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + coveralls: + if: github.repository_owner == 'mybatis' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - name: Setup Java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + with: + cache: maven + distribution: temurin + java-version: 25 + - name: Run the build + run: ./mvnw test --batch-mode --no-transfer-progress --quiet --show-version -Dlicense.skip=true + - name: Report Coverage to Coveralls for Pull Requests + if: github.event_name == 'pull_request' + run: ./mvnw generate-sources jacoco:report coveralls:report --batch-mode --no-transfer-progress -DpullRequest=${{ env.PR_NUMBER }} -DrepoToken=${{ env.GITHUB_TOKEN }} -DserviceName=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.number }} + - name: Report Coverage to Coveralls for General Push + if: github.event_name == 'push' + run: ./mvnw generate-sources jacoco:report coveralls:report --batch-mode --no-transfer-progress -DrepoToken=${{ env.GITHUB_TOKEN }} -DserviceName=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/site.yaml b/.github/workflows/site.yaml new file mode 100644 index 0000000000..908cd1eea2 --- /dev/null +++ b/.github/workflows/site.yaml @@ -0,0 +1,37 @@ +name: Site + +on: + push: + branches: + - site + +permissions: + contents: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + if: github.repository_owner == 'mybatis' && ! contains(toJSON(github.event.head_commit.message), '[maven-release-plugin]') + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - name: Setup Java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + with: + cache: maven + distribution: temurin + java-version: 25 + - name: Build site + run: ./mvnw site site:stage --batch-mode --no-transfer-progress --settings ./.mvn/settings.xml --show-version -Dlicense.skip=true -DskipTests + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NVD_API_KEY: ${{ secrets.NVD_API_KEY }} + - name: Deploy Site to gh-pages + uses: JamesIves/github-pages-deploy-action@9d877eea73427180ae43cf98e8914934fe157a1a # v4 + with: + branch: gh-pages + folder: target/staging diff --git a/.github/workflows/sonar.yaml b/.github/workflows/sonar.yaml new file mode 100644 index 0000000000..cbb8088e04 --- /dev/null +++ b/.github/workflows/sonar.yaml @@ -0,0 +1,40 @@ +name: SonarCloud + +on: + push: + branches: + - master + +permissions: read-all + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + SONAR_ORGANIZATION: mybatis + SONAR_PROJECT_KEY: spring + +jobs: + build: + if: github.repository_owner == 'mybatis' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + with: + # Disabling shallow clone is recommended for improving relevancy of reporting + fetch-depth: 0 + - name: Setup Java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + with: + cache: maven + distribution: temurin + java-version: 25 + - name: Set SONAR_SCANNER_JAVA_OPTS + run: echo "SONAR_SCANNER_JAVA_OPTS=-Xmx512m" >> ${GITHUB_ENV} + - name: Analyze with SonarCloud + 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 }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/sonatype.yaml b/.github/workflows/sonatype.yaml new file mode 100644 index 0000000000..da1b3cb0a9 --- /dev/null +++ b/.github/workflows/sonatype.yaml @@ -0,0 +1,31 @@ +name: Sonatype + +on: + push: + branches: + - master + +permissions: read-all + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + if: github.repository_owner == 'mybatis' && ! contains(toJSON(github.event.head_commit.message), '[maven-release-plugin]') + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + - name: Setup Java + uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5 + with: + cache: maven + distribution: temurin + java-version: 25 + - name: Deploy to Sonatype + run: ./mvnw deploy --batch-mode --no-transfer-progress --settings ./.mvn/settings.xml --show-version -Dlicense.skip=true -DskipTests + env: + CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} + CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} diff --git a/.gitignore b/.gitignore index 8a1aa32238..908d8660e8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ *.iml /.idea .mvn/wrapper/maven-wrapper.jar +release.properties +*.releaseBackup +.github/keys/ diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 0000000000..b6c32576a7 --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,25 @@ + + + + + fr.jcgay.maven + maven-profiler + 3.3 + + diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 0000000000..f996a0db0b --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1,3 @@ +-Daether.checksums.algorithms=SHA-512,SHA-256,SHA-1,MD5 +-Daether.connector.smartChecksums=false +--no-transfer-progress diff --git a/.mvn/settings.xml b/.mvn/settings.xml new file mode 100644 index 0000000000..b8e9a63f79 --- /dev/null +++ b/.mvn/settings.xml @@ -0,0 +1,52 @@ + + + + + + + + central + ${env.CI_DEPLOY_USERNAME} + ${env.CI_DEPLOY_PASSWORD} + + + + + gh-pages-scm + + branch + gh-pages + + + + + + github + ${env.GITHUB_TOKEN} + + + + + nvd + ${env.NVD_API_KEY} + + + + diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index d475a89ce1..b79c3b637f 100644 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -1,110 +1,95 @@ /* -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ - http://www.apache.org/licenses/LICENSE-2.0 +import java.io.IOException; +import java.io.InputStream; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.ThreadLocalRandom; -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. -*/ +public final class MavenWrapperDownloader { + private static final String WRAPPER_VERSION = "3.3.4"; -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; + private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE")); -public class MavenWrapperDownloader { + public static void main(String[] args) { + log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION); - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = - "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + if (args.length != 2) { + System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing"); + System.exit(1); + } - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } + try { + log(" - Downloader started"); + final URL wrapperUrl = URI.create(args[0]).toURL(); + final Path baseDir = Path.of(".").toAbsolutePath().normalize(); + final Path wrapperJarPath = baseDir.resolve(args[1]).normalize(); + if (!wrapperJarPath.startsWith(baseDir)) { + throw new IOException("Invalid path: outside of allowed directory"); } + downloadFileFromURL(wrapperUrl, wrapperJarPath); + log("Done"); + } catch (IOException e) { + System.err.println("- Error downloading: " + e.getMessage()); + if (VERBOSE) { + e.printStackTrace(); + } + System.exit(1); } - System.out.println("- Downloading from: : " + url); + } - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } + private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath) + throws IOException { + log(" - Downloading to: " + wrapperJarPath); + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + final String username = System.getenv("MVNW_USERNAME"); + final char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); + Path temp = wrapperJarPath + .getParent() + .resolve(wrapperJarPath.getFileName() + "." + + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp"); + try (InputStream inStream = wrapperUrl.openStream()) { + Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING); + Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING); + } finally { + Files.deleteIfExists(temp); } + log(" - Downloader complete"); } - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); + private static void log(String msg) { + if (VERBOSE) { + System.out.println(msg); + } } } diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 7095a8882f..041d19464c 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,2 +1,4 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar +distributionType=source +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar +wrapperVersion=3.3.4 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3fce0b70ab..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: java -sudo: false - -jdk: - - oraclejdk9 - - oraclejdk8 - - openjdk8 - -after_success: - - chmod -R 777 ./travis/after_success.sh - - ./travis/after_success.sh - -env: - global: - - secure: "dPX83x9q53WLJjNEsOJZTj2yjcusMp3Rg2SeF9xGUgxLa0NAdiWKM/ejSiJO\nzRM+5nmnQGb4SSigqb99N0ndB0S5YhMzbpACD8+SmCfpYRPdUQtE5dW22xpd\nPGPPUwPAGcqISwq5lcFCbPeve8k4g5Co/ZWGMMkoYy8DfjRss6g=" - - secure: "X/+tqgmKqR6wIvSLzDyYAc3Q0NtzFjnQZT7b4yD9MI+/9S1bLFyZJ8mLhymK\nEakyrz8syFAl38ebl2pGox3yaJ9GRfcQXjJ2Qv0Pgb0r+RQTrtU2Fpvb+3Nr\nh5ymtRIkU0XOpOBCq5M9AxV1TESftw3p1IUD9dzPilRqQdXzTGs=" diff --git a/LICENSE b/LICENSE index 57bc88a15a..7e835b2fa9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -192,7 +192,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/LICENSE_HEADER b/LICENSE_HEADER new file mode 100644 index 0000000000..eed366734e --- /dev/null +++ b/LICENSE_HEADER @@ -0,0 +1,13 @@ +Copyright ${license.git.copyrightYears} the original author or authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/NOTICE b/NOTICE index 8617f29c7d..79fd4a0c7d 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ MyBatis Spring -Copyright 2010-2013 +Copyright 2010-2023 This product includes software developed by The MyBatis Team (http://www.mybatis.org/). diff --git a/README.md b/README.md index bbcab732b3..8f02d7d053 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,25 @@ MyBatis Spring Adapter ====================== -[![Build Status](https://travis-ci.org/mybatis/spring.svg?branch=master)](https://travis-ci.org/mybatis/spring) +[![Java CI](https://github.com/mybatis/spring/actions/workflows/ci.yaml/badge.svg)](https://github.com/mybatis/spring/actions/workflows/ci.yaml) [![Coverage Status](https://coveralls.io/repos/mybatis/spring/badge.svg?branch=master&service=github)](https://coveralls.io/github/mybatis/spring?branch=master) -[![Dependency Status](https://www.versioneye.com/user/projects/5619b698a193340f2f000520/badge.svg?style=flat)](https://www.versioneye.com/user/projects/5619b698a193340f2f000520) [![Maven central](https://maven-badges.herokuapp.com/maven-central/org.mybatis/mybatis-spring/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.mybatis/mybatis-spring) -[![License](http://img.shields.io/:license-apache-brightgreen.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) +[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/org.mybatis/mybatis-spring.svg)](https://oss.sonatype.org/content/repositories/snapshots/org/mybatis/mybatis-spring/) +[![License](https://img.shields.io/:license-apache-brightgreen.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) -![mybatis-spring](http://mybatis.github.io/images/mybatis-logo.png) +![mybatis-spring](https://mybatis.org/images/mybatis-logo.png) -MyBatis-Spring adapter is an easy-to-use Spring3 bridge for MyBatis sql mapping framework. +MyBatis-Spring adapter is an easy-to-use [Spring Framework](https://github.com/spring-projects/spring-framework) bridge for [MyBatis](https://github.com/mybatis/mybatis-3) sql mapping framework. Supported Versions ------------------ -- 1.3.x - Continued support for Java 6 -- master (2.0.x) - Support for Java 8, Spring 5, and Junit 5 plus other java 8 requirements +- master - Support for Spring 7 and Spring Batch 6 +- 3.0.x - Support for Spring 6 and Spring Batch 5 +- 2.1.x - Maintenance for Spring 5 and Spring Batch 4 Essentials ---------- -* [See the docs](http://mybatis.github.io/spring/) +* [See the published docs](https://mybatis.org/spring/) +* [See the snapshot docs](src/site/markdown) (Note: may contain explanation of unreleased features) diff --git a/format.xml b/format.xml new file mode 100644 index 0000000000..de9c97c0c5 --- /dev/null +++ b/format.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/license.txt b/license.txt deleted file mode 100644 index 4ce1777ad3..0000000000 --- a/license.txt +++ /dev/null @@ -1,13 +0,0 @@ - Copyright ${license.git.copyrightYears} the original author or authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/mvnw b/mvnw index 961a825001..da7a37009b 100755 --- a/mvnw +++ b/mvnw @@ -8,7 +8,7 @@ # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Required ENV vars: # ------------------ @@ -27,260 +27,315 @@ # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- -if [ -z "$MAVEN_SKIP_RC" ] ; then +if [ -z "$MAVEN_SKIP_RC" ]; then - if [ -f /etc/mavenrc ] ; then + if [ -f /usr/local/etc/mavenrc ]; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ]; then . /etc/mavenrc fi - if [ -f "$HOME/.mavenrc" ] ; then + if [ -f "$HOME/.mavenrc" ]; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; +cygwin=false +darwin=false mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi +case "$(uname)" in +CYGWIN*) cygwin=true ;; +MINGW*) mingw=true ;; +Darwin*) + darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)" + export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home" + export JAVA_HOME fi - ;; + fi + ;; esac -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` +if [ -z "$JAVA_HOME" ]; then + if [ -r /etc/gentoo-release ]; then + JAVA_HOME=$(java-config --jre-home) fi fi -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - # For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +if $cygwin; then + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? +if $mingw; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ + && JAVA_HOME="$( + cd "$JAVA_HOME" || ( + echo "cannot cd into $JAVA_HOME." >&2 + exit 1 + ) + pwd + )" fi if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin; then + javaHome="$(dirname "$javaExecutable")" + javaExecutable="$(cd "$javaHome" && pwd -P)/javac" else - javaExecutable="`readlink -f \"$javaExecutable\"`" + javaExecutable="$(readlink -f "$javaExecutable")" fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` + javaHome="$(dirname "$javaExecutable")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then +if [ -z "$JAVACMD" ]; then + if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="`which java`" + JAVACMD="$( + \unset -f command 2>/dev/null + \command -v java + )" fi fi -if [ ! -x "$JAVACMD" ] ; then +if [ ! -x "$JAVACMD" ]; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." +if [ -z "$JAVA_HOME" ]; then + echo "Warning: JAVA_HOME environment variable is not set." >&2 fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" + if [ -z "$1" ]; then + echo "Path not specified to find_maven_basedir" >&2 return 1 fi basedir="$1" wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then + while [ "$wdir" != '/' ]; do + if [ -d "$wdir"/.mvn ]; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` + wdir=$( + cd "$wdir/.." || exit 1 + pwd + ) fi # end of workaround done - echo "${basedir}" + printf '%s' "$( + cd "$basedir" || exit 1 + pwd + )" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' <"$1" fi } -BASE_DIR=`find_maven_basedir "$(pwd)"` +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then - exit 1; + exit 1 fi +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar" - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + fi + while IFS="=" read -r key value; do + case "$key" in wrapperUrl) + wrapperUrl=$(trim "${value-}") + break + ;; + esac + done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - wget "$jarUrl" -O "$wrapperJarPath" - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - curl -o "$wrapperJarPath" "$jarUrl" + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl >/dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi + curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi fi ########################################################################################## # End of extension ########################################################################################## -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in wrapperSha256Sum) + wrapperSha256Sum=$(trim "${value-}") + break + ;; + esac +done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi fi + MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] \ + && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +# Maven main class is here to fix maven 4.0.0-beta-5 through 4.0.0-rc-4 +MAVEN_MAIN_CLASS=org.apache.maven.cling.MavenCling WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +# shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + "-Dmaven.mainClass=${MAVEN_MAIN_CLASS}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 830073a17e..4d22d665ea 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -7,7 +7,7 @@ @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM -@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM https://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @@ -18,15 +18,14 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script +@REM Apache Maven Wrapper startup batch script, version 3.3.4 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -37,7 +36,7 @@ @echo off @REM set title of command window title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME @@ -46,8 +45,8 @@ if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal @@ -60,22 +59,22 @@ set ERROR_CODE=0 @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome -echo. +echo. >&2 echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 -echo. +echo. >&2 goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init -echo. +echo. >&2 echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 -echo. +echo. >&2 goto error @REM ==== END VALIDATION ==== @@ -118,26 +117,76 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" + +@REM Maven main class is here to fix maven 4.0.0-beta-5 through 4.0.0-rc-4 +set MAVEN_MAIN_CLASS=org.apache.maven.cling.MavenCling set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar" -FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( - echo Found %WRAPPER_JAR% + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) ) else ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" - echo Finished downloading %WRAPPER_JAR% + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) ) @REM End of extension -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + "-Dmaven.mainClass=%MAVEN_MAIN_CLASS%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end @@ -147,15 +196,15 @@ set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause +if "%MAVEN_BATCH_PAUSE%"=="on" pause -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% -exit /B %ERROR_CODE% +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 92bf86cc3b..20be13cf4a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,38 +1,39 @@ - + + 4.0.0 org.mybatis mybatis-parent - 30 - + 51 + + org.mybatis mybatis-spring - 2.0.0-SNAPSHOT - jar + 4.0.1-SNAPSHOT mybatis-spring An easy-to-use Spring bridge for MyBatis sql mapping framework. - http://www.mybatis.org/spring/ + https://www.mybatis.org/spring/ @@ -77,51 +78,54 @@ - http://github.com/mybatis/spring - scm:git:ssh://github.com/mybatis/spring.git + scm:git:ssh://git@github.com/mybatis/spring.git scm:git:ssh://git@github.com/mybatis/spring.git - HEAD + mybatis-spring-4.0.0 + https://github.com/mybatis/spring/ GitHub Issue Management https://github.com/mybatis/spring/issues - Travis CI - https://travis-ci.org/mybatis/spring + GitHub Actions + https://github.com/mybatis/spring/actions - gh-pages + gh-pages-scm Mybatis GitHub Pages - git:ssh://git@github.com/mybatis/spring.git?gh-pages# + scm:git:ssh://git@github.com/mybatis/spring.git - 1.2.2 + 2.1.0 org.mybatis.spring.*,org.mybatis.spring.mapper.*,org.mybatis.spring.support.*,org.mybatis.spring.transaction.* Spring - 1.8 - 1.8 - 1.8 - 1.8 org.springframework.batch.*;resolution:=optional,* * - 5.0.4.RELEASE - 4.0.1.RELEASE - 1.1.0 - 5.1.0 + + 17 + 17 + 17 + 17 + + 10.17.1.0 + 3.5.19 + 7.0.2 + 4.0.1 + 6.0.1 + org.mybatis.spring + + 6.0.1 - - org.codehaus.mojo.signature - java18 - 1.0 + + 1764529975 - - - 2.19.1 + + true @@ -129,7 +133,7 @@ org.mybatis mybatis - 3.4.6 + ${mybatis.version} provided @@ -153,26 +157,27 @@ provided + + org.aspectj + aspectjweaver + 1.9.25.1 + compile + true + + com.atomikos transactions-jdbc - 4.0.5 + 6.0.0 test org.apache.derby derby - 10.14.1.0 - test - - - - org.junit.jupiter - junit-jupiter-api - ${junit.version} + ${derby.version} test @@ -183,17 +188,10 @@ test - - org.junit.platform - junit-platform-runner - ${junit-platform.version} - test - - org.jboss.byteman byteman-bmunit - 3.0.10 + 4.0.26 test @@ -223,40 +221,53 @@ spring-batch-test ${spring-batch.version} test + + + junit + junit + + + + + + org.springframework.boot + spring-boot-autoconfigure + ${spring-boot.version} + test org.hsqldb hsqldb - 2.4.0 + 2.7.4 test - log4j - log4j - 1.2.17 + org.slf4j + slf4j-simple + 2.0.17 test org.mockito - mockito-core - 2.15.0 + mockito-junit-jupiter + 5.21.0 test org.assertj assertj-core - 3.8.0 + 3.27.6 test com.mockrunner mockrunner-core - 1.1.2 + 2.0.7 test @@ -280,7 +291,7 @@ com.mockrunner mockrunner-ejb - 1.1.2 + 2.0.7 test @@ -296,23 +307,41 @@ com.mockrunner mockrunner-jdbc - 1.1.2 + 2.0.7 test + + + commons-logging + commons-logging + + - javax.transaction - javax.transaction-api - 1.2 + jakarta.transaction + jakarta.transaction-api + 2.0.1 test - javax.servlet - javax.servlet-api - 4.0.0 + jakarta.servlet + jakarta.servlet-api + 6.1.0 test + + net.bytebuddy + byte-buddy + 1.18.3 + test + + + net.bytebuddy + byte-buddy-agent + 1.18.3 + test + @@ -320,27 +349,65 @@ sonatype-oss-snapshots Sonatype OSS Snapshots Repository https://oss.sonatype.org/content/repositories/snapshots - - true - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone + + false + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + false + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + + + false + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + + + false + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + + + + + + META-INF + ${project.basedir} + + LICENSE + NOTICE + + + + ${project.basedir}/src/main/resources + + org.apache.maven.plugins maven-surefire-plugin - + derby.stream.error.file ${project.build.directory}/derby.log @@ -349,54 +416,74 @@ com.atomikos.icatch.log_base_dir ${project.build.directory} - + - - - org.junit.platform - junit-platform-surefire-provider - ${junit-platform.version} - - - org.junit.jupiter - junit-jupiter-engine - ${junit.version} - - + + + org.apache.maven.plugins + maven-resources-plugin + + + + filter-site + + copy-resources + + pre-site + + ${project.build.directory}/site-src + + + ${project.basedir}/src/site + true + + + + + org.apache.maven.plugins maven-site-plugin - en,es,zh_CN,ja,ko + default,es,zh_CN,ja,ko + ${project.build.directory}/site-src + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + + + + - - - - ${project.basedir} - META-INF - - LICENSE - NOTICE - - - - ${project.build.sourceDirectory} - - **/*.java - - - - - - ${project.build.testSourceDirectory} - - **/*.java - - - + + + 17 + + [17,) + + + 10.16.1.1 + + + + 19 + + [19,) + + + 10.17.1.0 + + + diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000000..5db72dd6a9 --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +} diff --git a/scripts/get_latest_version.sh b/scripts/get_latest_version.sh new file mode 100755 index 0000000000..5bdbcc9ec1 --- /dev/null +++ b/scripts/get_latest_version.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# +# Copyright 2010-2022 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +targetArtifact=$1 +targetMinorVersion=$2 +majorVersion=${targetMinorVersion%.*} +minorVersion=${targetMinorVersion#*.} +while read -r line; do + maintenanceVersion=${line#${targetMinorVersion}.} && maintenanceVersion=${maintenanceVersion%%.*} + maintenanceVersions="${maintenanceVersions}${maintenanceVersion}"$'\n' +done<${majorVersion}\.${minorVersion}\.[0-9]*") +END +echo "${targetMinorVersion}.$(echo "${maintenanceVersions}" | sort -n | tail -n 1).RELEASE" diff --git a/src/changes/changes.xml b/src/changes/changes.xml deleted file mode 100644 index 4eb2bf9920..0000000000 --- a/src/changes/changes.xml +++ /dev/null @@ -1,201 +0,0 @@ - - - - - The MyBatis Team - Changes - - - - - - mybatis-spring OSGi imports - Spring batch should be optional - - - org.mybatis.spring.transaction.SpringManagedTransaction.logger should be static - - - - - Build a namespace for MyBatis - - - mybatis-spring OSGi imports - Spring batch should be optional - - - Allow setting the super type for domain classes when scanning for type aliases via Spring - - - MapperScannerConfigurer can't work with AnnotationConfigApplicationContext - - - Remove finals in SqlSessionDaoSupport to enable adding Qualifiers - - - Proxy message by istantiating Objects with Spring - - - SqlSessionFactoryBean: add setters for Object[Wrapper]Factory - - - MapperScannerConfigurer should allow setting a BeanNameGenerator - - - - - 2nd level cache consistency with MyBatis-Spring - - - Overriding sqlSessionTemplate in MapperFactoryBean not working in certain circumstances - - - MapperScannerConfigurer causes app initialization problems - - - - - Prevent class loading / add zero-method mappers while scanning for mappers - - - Lazy loads do not execute within Spring-managed transactions - - - MapperScannerConfigurer does not work with PropertyPlaceholderConfigurer - - - - - Add SqlSession.flushStatements() - - - configurationProperties not applied in mappings - - - SqlSessionTemplate throws NullPointerException if ExceptionTranslator cannot translate - exception - - - Replace BeanPostProcessor with BeanDefinitionRegistryPostProcessor - - - - - Added a fail fast checking to ensure that all statements are fully loaded - - - SqlSessionFactoryBean should allow sql fragments to be shared across multiple mapper - files - - - Spring MapperScannerConfigurer does not work properly in OSGi - - - Enable typehandler and alias registration in mybatis-spring - - - - - Support multiple character sets (parse XML from InputStream vs. Reader) - - - Reset error context after calling XMLConfigBuilder or XMLMapperBuilder - - - SqlSession rolls back with no Spring Tx + autocommit=false + dirty=true (an - update was executed) - - - setTransactionFactoryProperties property in SqlSessionFactory has been removed - - - setTransactionFactoryClass property in SqlSessionFactory has been replaced by - setTransactionFactory - - - Improved logging - - - Doesn't propagate exceptions up the stack when using batch mode - - - - - - getSqlSessionTemplate() method in SqlSessionDaoSupport has been replaced by getSqlSession() - - - MapperFactoryBean has been moved to org.mybatis.spring.mapper.MapperFactoryBean - - - MapperScannerPostProcessor has been moved to org.mybatis.spring.mapper.MapperScannerConfigurer - - - Removed @Mapper annotation - - - Mapper scanning should not rely on proprietary @Mapper annotation - - - Avoid one Proxy creation when using injected mappers - - - Add new selectMap methods to SqlSessionTemplate - - - - - - Improved documentation manual. - - - Add operation select(String, ResultHandler) - - - Component Scan for Mappers - - - SqlSessionCallback.doInSqlSession should not throw SqlException - - - getExceptionTranslator() may not be called in mybatis-spring - - - Setting a specific datasource for one mapper does not work - - - should not let changing the executor type during a tx - - - mapperLocations does not work for xml files inside JARs - - - Use a reference to SqlSessionFactory in MapperScanner - - - - - - First RC release. - - - - - diff --git a/src/main/java/org/mybatis/logging/Logger.java b/src/main/java/org/mybatis/logging/Logger.java index b5ab059297..d1b57df919 100644 --- a/src/main/java/org/mybatis/logging/Logger.java +++ b/src/main/java/org/mybatis/logging/Logger.java @@ -1,24 +1,24 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.logging; -import org.apache.ibatis.logging.Log; - import java.util.function.Supplier; +import org.apache.ibatis.logging.Log; + /** * Wrapper of {@link Log}, allow log with lambda expressions. * @@ -26,30 +26,69 @@ */ public class Logger { + /** The log. */ private final Log log; + /** + * Instantiates a new logger. + * + * @param log + * the log + */ Logger(Log log) { this.log = log; } + /** + * Error. + * + * @param s + * the s + * @param e + * the e + */ public void error(Supplier s, Throwable e) { log.error(s.get(), e); } + /** + * Error. + * + * @param s + * the s + */ public void error(Supplier s) { log.error(s.get()); } + /** + * Warn. + * + * @param s + * the s + */ public void warn(Supplier s) { log.warn(s.get()); } + /** + * Debug. + * + * @param s + * the s + */ public void debug(Supplier s) { if (log.isDebugEnabled()) { log.debug(s.get()); } } + /** + * Trace. + * + * @param s + * the s + */ public void trace(Supplier s) { if (log.isTraceEnabled()) { log.trace(s.get()); diff --git a/src/main/java/org/mybatis/logging/LoggerFactory.java b/src/main/java/org/mybatis/logging/LoggerFactory.java index f64443c5b0..dca5433077 100644 --- a/src/main/java/org/mybatis/logging/LoggerFactory.java +++ b/src/main/java/org/mybatis/logging/LoggerFactory.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.logging; @@ -24,10 +24,33 @@ */ public class LoggerFactory { + /** + * Instantiates a new logger factory. + */ + private LoggerFactory() { + // NOP + } + + /** + * Gets the logger. + * + * @param aClass + * the a class + * + * @return the logger + */ public static Logger getLogger(Class aClass) { return new Logger(LogFactory.getLog(aClass)); } + /** + * Gets the logger. + * + * @param logger + * the logger + * + * @return the logger + */ public static Logger getLogger(String logger) { return new Logger(LogFactory.getLog(logger)); } diff --git a/src/main/java/org/mybatis/spring/MyBatisExceptionTranslator.java b/src/main/java/org/mybatis/spring/MyBatisExceptionTranslator.java index 222e4b19ad..cfcdefe202 100644 --- a/src/main/java/org/mybatis/spring/MyBatisExceptionTranslator.java +++ b/src/main/java/org/mybatis/spring/MyBatisExceptionTranslator.java @@ -1,90 +1,119 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring; import java.sql.SQLException; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; import javax.sql.DataSource; import org.apache.ibatis.exceptions.PersistenceException; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.jdbc.UncategorizedSQLException; import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; import org.springframework.jdbc.support.SQLExceptionTranslator; import org.springframework.transaction.TransactionException; /** * Default exception translator. - * - * Translates MyBatis SqlSession returned exception into a Spring - * {@code DataAccessException} using Spring's {@code SQLExceptionTranslator} - * Can load {@code SQLExceptionTranslator} eagerly or when the - * first exception is translated. + *

+ * Translates MyBatis SqlSession returned exception into a Spring {@code DataAccessException} using Spring's + * {@code SQLExceptionTranslator} Can load {@code SQLExceptionTranslator} eagerly or when the first exception is + * translated. * * @author Eduardo Macarron */ public class MyBatisExceptionTranslator implements PersistenceExceptionTranslator { - private final DataSource dataSource; - + private final Supplier exceptionTranslatorSupplier; private SQLExceptionTranslator exceptionTranslator; + private ReentrantLock lock = new ReentrantLock(); /** - * Creates a new {@code DataAccessExceptionTranslator} instance. + * Creates a new {@code PersistenceExceptionTranslator} instance with {@code SQLErrorCodeSQLExceptionTranslator}. * - * @param dataSource DataSource to use to find metadata and establish which error codes are usable. - * @param exceptionTranslatorLazyInit if true, the translator instantiates internal stuff only the first time will - * have the need to translate exceptions. + * @param dataSource + * DataSource to use to find metadata and establish which error codes are usable. + * @param exceptionTranslatorLazyInit + * if true, the translator instantiates internal stuff only the first time will have the need to translate + * exceptions. */ public MyBatisExceptionTranslator(DataSource dataSource, boolean exceptionTranslatorLazyInit) { - this.dataSource = dataSource; + this(() -> new SQLErrorCodeSQLExceptionTranslator(dataSource), exceptionTranslatorLazyInit); + } + /** + * Creates a new {@code PersistenceExceptionTranslator} instance with specified {@code SQLExceptionTranslator}. + * + * @param exceptionTranslatorSupplier + * Supplier for creating a {@code SQLExceptionTranslator} instance + * @param exceptionTranslatorLazyInit + * if true, the translator instantiates internal stuff only the first time will have the need to translate + * exceptions. + * + * @since 2.0.3 + */ + public MyBatisExceptionTranslator(Supplier exceptionTranslatorSupplier, + boolean exceptionTranslatorLazyInit) { + this.exceptionTranslatorSupplier = exceptionTranslatorSupplier; if (!exceptionTranslatorLazyInit) { this.initExceptionTranslator(); } } - /** - * {@inheritDoc} - */ @Override public DataAccessException translateExceptionIfPossible(RuntimeException e) { if (e instanceof PersistenceException) { // Batch exceptions come inside another PersistenceException // recursion has a risk of infinite loop so better make another if + var msg = e.getMessage(); if (e.getCause() instanceof PersistenceException) { e = (PersistenceException) e.getCause(); + if (msg == null) { + msg = e.getMessage(); + } } if (e.getCause() instanceof SQLException) { this.initExceptionTranslator(); - return this.exceptionTranslator.translate(e.getMessage() + "\n", null, (SQLException) e.getCause()); - } else if (e.getCause() instanceof TransactionException) { + var task = e.getMessage() + "\n"; + var se = (SQLException) e.getCause(); + var dae = this.exceptionTranslator.translate(task, null, se); + return dae != null ? dae : new UncategorizedSQLException(task, null, se); + } + if (e.getCause() instanceof TransactionException) { throw (TransactionException) e.getCause(); } - return new MyBatisSystemException(e); - } + return new MyBatisSystemException(msg, e); + } return null; } /** * Initializes the internal translator reference. */ - private synchronized void initExceptionTranslator() { - if (this.exceptionTranslator == null) { - this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(this.dataSource); + private void initExceptionTranslator() { + lock.lock(); + try { + if (this.exceptionTranslator == null) { + this.exceptionTranslator = exceptionTranslatorSupplier.get(); + } + } finally { + lock.unlock(); } } diff --git a/src/main/java/org/mybatis/spring/MyBatisSystemException.java b/src/main/java/org/mybatis/spring/MyBatisSystemException.java index e3f1e4f170..ee071a3f08 100644 --- a/src/main/java/org/mybatis/spring/MyBatisSystemException.java +++ b/src/main/java/org/mybatis/spring/MyBatisSystemException.java @@ -1,38 +1,59 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring; import org.springframework.dao.UncategorizedDataAccessException; /** - * MyBatis specific subclass of {@code UncategorizedDataAccessException}, for MyBatis system errors that do - * not match any concrete {@code org.springframework.dao} exceptions. - * - * In MyBatis 3 {@code org.apache.ibatis.exceptions.PersistenceException} is a {@code RuntimeException}, - * but using this wrapper class to bring everything under a single hierarchy will be easier for client code to - * handle. + * MyBatis specific subclass of {@code UncategorizedDataAccessException}, for MyBatis system errors that do not match + * any concrete {@code org.springframework.dao} exceptions. + *

+ * In MyBatis 3 {@code org.apache.ibatis.exceptions.PersistenceException} is a {@code RuntimeException}, but using this + * wrapper class to bring everything under a single hierarchy will be easier for client code to handle. * * @author Hunter Presnall */ +@SuppressWarnings("squid:MaximumInheritanceDepth") // It is the intended design public class MyBatisSystemException extends UncategorizedDataAccessException { private static final long serialVersionUID = -5284728621670758939L; + /** + * Instantiates a new my batis system exception. + * + * @param cause + * the cause + * + * @deprecated as of 3.0.4, use {@link #MyBatisSystemException(String, Throwable)} instead + */ + @Deprecated(since = "3.0.4", forRemoval = true) public MyBatisSystemException(Throwable cause) { - super(null, cause); + this(cause.getMessage(), cause); + } + + /** + * Instantiates a new my batis system exception. + * + * @param msg + * the msg + * @param cause + * the cause + */ + public MyBatisSystemException(String msg, Throwable cause) { + super(msg, cause); } } diff --git a/src/main/java/org/mybatis/spring/SqlSessionFactoryBean.java b/src/main/java/org/mybatis/spring/SqlSessionFactoryBean.java index 61c560ebdf..6a268ced8f 100644 --- a/src/main/java/org/mybatis/spring/SqlSessionFactoryBean.java +++ b/src/main/java/org/mybatis/spring/SqlSessionFactoryBean.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring; @@ -22,8 +22,17 @@ import static org.springframework.util.StringUtils.tokenizeToStringArray; import java.io.IOException; +import java.lang.reflect.Modifier; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; import java.util.Properties; +import java.util.Set; +import java.util.function.IntFunction; +import java.util.stream.Stream; import javax.sql.DataSource; @@ -31,12 +40,14 @@ import org.apache.ibatis.builder.xml.XMLMapperBuilder; import org.apache.ibatis.cache.Cache; import org.apache.ibatis.executor.ErrorContext; +import org.apache.ibatis.io.Resources; import org.apache.ibatis.io.VFS; import org.apache.ibatis.mapping.DatabaseIdProvider; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; +import org.apache.ibatis.scripting.LanguageDriver; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; @@ -47,36 +58,44 @@ import org.mybatis.spring.transaction.SpringManagedTransactionFactory; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.core.NestedIOException; import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; +import org.springframework.util.ClassUtils; /** - * {@code FactoryBean} that creates an MyBatis {@code SqlSessionFactory}. - * This is the usual way to set up a shared MyBatis {@code SqlSessionFactory} in a Spring application context; - * the SqlSessionFactory can then be passed to MyBatis-based DAOs via dependency injection. - * - * Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction - * demarcation in combination with a {@code SqlSessionFactory}. JTA should be used for transactions - * which span multiple databases or when container managed transactions (CMT) are being used. + * {@code FactoryBean} that creates a MyBatis {@code SqlSessionFactory}. This is the usual way to set up a shared + * MyBatis {@code SqlSessionFactory} in a Spring application context; the SqlSessionFactory can then be passed to + * MyBatis-based DAOs via dependency injection. + *

+ * Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction demarcation + * in combination with a {@code SqlSessionFactory}. JTA should be used for transactions which span multiple databases or + * when container managed transactions (CMT) are being used. * * @author Putthiphong Boonphong * @author Hunter Presnall * @author Eduardo Macarron * @author Eddú Meléndez * @author Kazuki Shimizu + * @author Jens Schauder * * @see #setConfigLocation * @see #setDataSource */ -public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener { +public class SqlSessionFactoryBean + implements FactoryBean, InitializingBean, ApplicationListener { private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class); + private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver(); + private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory(); + private Resource configLocation; private Configuration configuration; @@ -93,7 +112,7 @@ public class SqlSessionFactoryBean implements FactoryBean, In private SqlSessionFactory sqlSessionFactory; - //EnvironmentAware requires spring 3.1 + // EnvironmentAware requires spring 3.1 private String environment = SqlSessionFactoryBean.class.getSimpleName(); private boolean failFast; @@ -104,13 +123,20 @@ public class SqlSessionFactoryBean implements FactoryBean, In private String typeHandlersPackage; + @SuppressWarnings("rawtypes") + private Class defaultEnumTypeHandler; + private Class[] typeAliases; private String typeAliasesPackage; private Class typeAliasesSuperType; - //issue #19. No default provider. + private LanguageDriver[] scriptingLanguageDrivers; + + private Class defaultScriptingLanguageDriver; + + // issue #19. No default provider. private DatabaseIdProvider databaseIdProvider; private Class vfs; @@ -125,7 +151,9 @@ public class SqlSessionFactoryBean implements FactoryBean, In * Sets the ObjectFactory. * * @since 1.1.2 + * * @param objectFactory + * a custom ObjectFactory */ public void setObjectFactory(ObjectFactory objectFactory) { this.objectFactory = objectFactory; @@ -135,7 +163,9 @@ public void setObjectFactory(ObjectFactory objectFactory) { * Sets the ObjectWrapperFactory. * * @since 1.1.2 + * * @param objectWrapperFactory + * a specified ObjectWrapperFactory */ public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) { this.objectWrapperFactory = objectWrapperFactory; @@ -145,35 +175,59 @@ public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) { * Gets the DatabaseIdProvider * * @since 1.1.0 - * @return + * + * @return a specified DatabaseIdProvider */ public DatabaseIdProvider getDatabaseIdProvider() { return databaseIdProvider; } /** - * Sets the DatabaseIdProvider. - * As of version 1.2.2 this variable is not initialized by default. + * Sets the DatabaseIdProvider. As of version 1.2.2 this variable is not initialized by default. * * @since 1.1.0 + * * @param databaseIdProvider + * a DatabaseIdProvider */ public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) { this.databaseIdProvider = databaseIdProvider; } + /** + * Gets the VFS. + * + * @return a specified VFS + */ public Class getVfs() { return this.vfs; } + /** + * Sets the VFS. + * + * @param vfs + * a VFS + */ public void setVfs(Class vfs) { this.vfs = vfs; } + /** + * Gets the Cache. + * + * @return a specified Cache + */ public Cache getCache() { return this.cache; } + /** + * Sets the Cache. + * + * @param cache + * a Cache + */ public void setCache(Cache cache) { this.cache = cache; } @@ -183,33 +237,35 @@ public void setCache(Cache cache) { * * @since 1.0.1 * - * @param plugins list of plugins - * + * @param plugins + * list of plugins */ - public void setPlugins(Interceptor[] plugins) { + public void setPlugins(Interceptor... plugins) { this.plugins = plugins; } /** * Packages to search for type aliases. + *

+ * Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.model}. * * @since 1.0.1 * - * @param typeAliasesPackage package to scan for domain objects - * + * @param typeAliasesPackage + * package to scan for domain objects */ public void setTypeAliasesPackage(String typeAliasesPackage) { this.typeAliasesPackage = typeAliasesPackage; } /** - * Super class which domain objects have to extend to have a type alias created. - * No effect if there is no package to scan configured. + * Super class which domain objects have to extend to have a type alias created. No effect if there is no package to + * scan configured. * * @since 1.1.2 * - * @param typeAliasesSuperType super class for domain objects - * + * @param typeAliasesSuperType + * super class for domain objects */ public void setTypeAliasesSuperType(Class typeAliasesSuperType) { this.typeAliasesSuperType = typeAliasesSuperType; @@ -217,11 +273,13 @@ public void setTypeAliasesSuperType(Class typeAliasesSuperType) { /** * Packages to search for type handlers. + *

+ * Since 2.0.1, allow to specify a wildcard such as {@code com.example.*.typehandler}. * * @since 1.0.1 * - * @param typeHandlersPackage package to scan for type handlers - * + * @param typeHandlersPackage + * package to scan for type handlers */ public void setTypeHandlersPackage(String typeHandlersPackage) { this.typeHandlersPackage = typeHandlersPackage; @@ -232,31 +290,46 @@ public void setTypeHandlersPackage(String typeHandlersPackage) { * * @since 1.0.1 * - * @param typeHandlers Type handler list + * @param typeHandlers + * Type handler list */ - public void setTypeHandlers(TypeHandler[] typeHandlers) { + public void setTypeHandlers(TypeHandler... typeHandlers) { this.typeHandlers = typeHandlers; } + /** + * Set the default type handler class for enum. + * + * @since 2.0.5 + * + * @param defaultEnumTypeHandler + * The default type handler class for enum + */ + public void setDefaultEnumTypeHandler( + @SuppressWarnings("rawtypes") Class defaultEnumTypeHandler) { + this.defaultEnumTypeHandler = defaultEnumTypeHandler; + } + /** * List of type aliases to register. They can be annotated with {@code Alias} * * @since 1.0.1 * - * @param typeAliases Type aliases list + * @param typeAliases + * Type aliases list */ - public void setTypeAliases(Class[] typeAliases) { + public void setTypeAliases(Class... typeAliases) { this.typeAliases = typeAliases; } /** - * If true, a final check is done on Configuration to assure that all mapped - * statements are fully loaded and there is no one still pending to resolve - * includes. Defaults to false. + * If true, a final check is done on Configuration to assure that all mapped statements are fully loaded and there is + * no one still pending to resolve includes. Defaults to false. * * @since 1.0.1 * - * @param failFast enable failFast + * @param failFast + * enable failFast */ public void setFailFast(boolean failFast) { this.failFast = failFast; @@ -265,6 +338,9 @@ public void setFailFast(boolean failFast) { /** * Set the location of the MyBatis {@code SqlSessionFactory} config file. A typical value is * "WEB-INF/mybatis-configuration.xml". + * + * @param configLocation + * a location the MyBatis config file */ public void setConfigLocation(Resource configLocation) { this.configLocation = configLocation; @@ -272,7 +348,10 @@ public void setConfigLocation(Resource configLocation) { /** * Set a customized MyBatis configuration. - * @param configuration MyBatis configuration + * + * @param configuration + * MyBatis configuration + * * @since 1.3.0 */ public void setConfiguration(Configuration configuration) { @@ -280,40 +359,48 @@ public void setConfiguration(Configuration configuration) { } /** - * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} - * configuration at runtime. - * - * This is an alternative to specifying "<sqlmapper>" entries in an MyBatis config file. - * This property being based on Spring's resource abstraction also allows for specifying - * resource patterns here: e.g. "classpath*:sqlmap/*-mapper.xml". + * Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} configuration + * at runtime. + *

+ * This is an alternative to specifying "<sqlmapper>" entries in an MyBatis config file. This property being + * based on Spring's resource abstraction also allows for specifying resource patterns here: e.g. + * "classpath*:sqlmap/*-mapper.xml". + * + * @param mapperLocations + * location of MyBatis mapper files */ - public void setMapperLocations(Resource[] mapperLocations) { + public void setMapperLocations(Resource... mapperLocations) { this.mapperLocations = mapperLocations; } /** * Set optional properties to be passed into the SqlSession configuration, as alternative to a - * {@code <properties>} tag in the configuration xml file. This will be used to - * resolve placeholders in the config file. + * {@code <properties>} tag in the configuration xml file. This will be used to resolve placeholders in the + * config file. + * + * @param sqlSessionFactoryProperties + * optional properties for the SqlSessionFactory */ public void setConfigurationProperties(Properties sqlSessionFactoryProperties) { this.configurationProperties = sqlSessionFactoryProperties; } /** - * Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource} - * should match the one used by the {@code SqlSessionFactory}: for example, you could specify the same - * JNDI DataSource for both. - * - * A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code - * accessing this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}. - * - * The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not - * a {@code TransactionAwareDataSourceProxy}. Only data access code may work with - * {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the - * underlying target {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy} - * passed in, it will be unwrapped to extract its target {@code DataSource}. - * + * Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource} should + * match the one used by the {@code SqlSessionFactory}: for example, you could specify the same JNDI DataSource for + * both. + *

+ * A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code accessing + * this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}. + *

+ * The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not a + * {@code TransactionAwareDataSourceProxy}. Only data access code may work with + * {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the underlying target + * {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy} passed in, it will be + * unwrapped to extract its target {@code DataSource}. + * + * @param dataSource + * a JDBC {@code DataSource} */ public void setDataSource(DataSource dataSource) { if (dataSource instanceof TransactionAwareDataSourceProxy) { @@ -329,198 +416,296 @@ public void setDataSource(DataSource dataSource) { /** * Sets the {@code SqlSessionFactoryBuilder} to use when creating the {@code SqlSessionFactory}. + *

+ * This is mainly meant for testing so that mock SqlSessionFactory classes can be injected. By default, + * {@code SqlSessionFactoryBuilder} creates {@code DefaultSqlSessionFactory} instances. * - * This is mainly meant for testing so that mock SqlSessionFactory classes can be injected. By - * default, {@code SqlSessionFactoryBuilder} creates {@code DefaultSqlSessionFactory} instances. - * + * @param sqlSessionFactoryBuilder + * a SqlSessionFactoryBuilder */ public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) { this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder; } /** - * Set the MyBatis TransactionFactory to use. Default is {@code SpringManagedTransactionFactory} - * - * The default {@code SpringManagedTransactionFactory} should be appropriate for all cases: - * be it Spring transaction management, EJB CMT or plain JTA. If there is no active transaction, - * SqlSession operations will execute SQL statements non-transactionally. - * - * It is strongly recommended to use the default {@code TransactionFactory}. If not used, any - * attempt at getting an SqlSession through Spring's MyBatis framework will throw an exception if - * a transaction is active. + * Set the MyBatis TransactionFactory to use. Default is {@code SpringManagedTransactionFactory}. + *

+ * The default {@code SpringManagedTransactionFactory} should be appropriate for all cases: be it Spring transaction + * management, EJB CMT or plain JTA. If there is no active transaction, SqlSession operations will execute SQL + * statements non-transactionally. + *

+ * It is strongly recommended to use the default {@code TransactionFactory}. If not used, any attempt at + * getting an SqlSession through Spring's MyBatis framework will throw an exception if a transaction is active. * * @see SpringManagedTransactionFactory - * @param transactionFactory the MyBatis TransactionFactory + * + * @param transactionFactory + * the MyBatis TransactionFactory */ public void setTransactionFactory(TransactionFactory transactionFactory) { this.transactionFactory = transactionFactory; } /** - * NOTE: This class overrides any {@code Environment} you have set in the MyBatis - * config file. This is used only as a placeholder name. The default value is - * {@code SqlSessionFactoryBean.class.getSimpleName()}. + * NOTE: This class overrides any {@code Environment} you have set in the MyBatis config file. This is + * used only as a placeholder name. The default value is {@code SqlSessionFactoryBean.class.getSimpleName()}. * - * @param environment the environment name + * @param environment + * the environment name */ public void setEnvironment(String environment) { this.environment = environment; } /** - * {@inheritDoc} + * Set scripting language drivers. + * + * @param scriptingLanguageDrivers + * scripting language drivers + * + * @since 2.0.2 + */ + public void setScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) { + this.scriptingLanguageDrivers = scriptingLanguageDrivers; + } + + /** + * Set a default scripting language driver class. + * + * @param defaultScriptingLanguageDriver + * A default scripting language driver class + * + * @since 2.0.2 + */ + public void setDefaultScriptingLanguageDriver(Class defaultScriptingLanguageDriver) { + this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver; + } + + /** + * Add locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory} configuration + * at runtime. + *

+ * This is an alternative to specifying "<sqlmapper>" entries in an MyBatis config file. This property being + * based on Spring's resource abstraction also allows for specifying resource patterns here: e.g. + * "classpath*:sqlmap/*-mapper.xml". + * + * @param mapperLocations + * location of MyBatis mapper files + * + * @see #setMapperLocations(Resource...) + * + * @since 3.0.2 + */ + public void addMapperLocations(Resource... mapperLocations) { + setMapperLocations(appendArrays(this.mapperLocations, mapperLocations, Resource[]::new)); + } + + /** + * Add type handlers. + * + * @param typeHandlers + * Type handler list + * + * @since 3.0.2 + */ + public void addTypeHandlers(TypeHandler... typeHandlers) { + setTypeHandlers(appendArrays(this.typeHandlers, typeHandlers, TypeHandler[]::new)); + } + + /** + * Add scripting language drivers. + * + * @param scriptingLanguageDrivers + * scripting language drivers + * + * @since 3.0.2 + */ + public void addScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) { + setScriptingLanguageDrivers( + appendArrays(this.scriptingLanguageDrivers, scriptingLanguageDrivers, LanguageDriver[]::new)); + } + + /** + * Add Mybatis plugins. + * + * @param plugins + * list of plugins + * + * @since 3.0.2 + */ + public void addPlugins(Interceptor... plugins) { + setPlugins(appendArrays(this.plugins, plugins, Interceptor[]::new)); + } + + /** + * Add type aliases. + * + * @param typeAliases + * Type aliases list + * + * @since 3.0.2 */ + public void addTypeAliases(Class... typeAliases) { + setTypeAliases(appendArrays(this.typeAliases, typeAliases, Class[]::new)); + } + + private T[] appendArrays(T[] oldArrays, T[] newArrays, IntFunction generator) { + if (oldArrays == null) { + return newArrays; + } + if (newArrays == null) { + return oldArrays; + } + List newList = new ArrayList<>(Arrays.asList(oldArrays)); + newList.addAll(Arrays.asList(newArrays)); + return newList.toArray(generator.apply(0)); + } + @Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); + // TODO Review this statement as it seems off! state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), - "Property 'configuration' and 'configLocation' can not specified with together"); + "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); } /** * Build a {@code SqlSessionFactory} instance. - * + *

* The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a - * {@code SqlSessionFactory} instance based on an Reader. - * Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file). + * {@code SqlSessionFactory} instance based on a Reader. Since 1.3.0, it can be specified a {@link Configuration} + * instance directly(without config file). * * @return SqlSessionFactory - * @throws IOException if loading the config file failed + * + * @throws Exception + * if configuration is failed */ - protected SqlSessionFactory buildSqlSessionFactory() throws IOException { + protected SqlSessionFactory buildSqlSessionFactory() throws Exception { - Configuration configuration; + final Configuration targetConfiguration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { - configuration = this.configuration; - if (configuration.getVariables() == null) { - configuration.setVariables(this.configurationProperties); + targetConfiguration = this.configuration; + if (targetConfiguration.getVariables() == null) { + targetConfiguration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { - configuration.getVariables().putAll(this.configurationProperties); + targetConfiguration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); - configuration = xmlConfigBuilder.getConfiguration(); + targetConfiguration = xmlConfigBuilder.getConfiguration(); } else { - LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); - configuration = new Configuration(); - if (this.configurationProperties != null) { - configuration.setVariables(this.configurationProperties); - } - } - - if (this.objectFactory != null) { - configuration.setObjectFactory(this.objectFactory); + LOGGER.debug( + () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); + targetConfiguration = new Configuration(); + Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables); } - if (this.objectWrapperFactory != null) { - configuration.setObjectWrapperFactory(this.objectWrapperFactory); - } - - if (this.vfs != null) { - configuration.setVfsImpl(this.vfs); - } + Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory); + Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory); + Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl); if (hasLength(this.typeAliasesPackage)) { - String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, - ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); - for (String packageToScan : typeAliasPackageArray) { - configuration.getTypeAliasRegistry().registerAliases(packageToScan, - typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); - LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases"); - } + scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream() + .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()) + .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias); } if (!isEmpty(this.typeAliases)) { - for (Class typeAlias : this.typeAliases) { - configuration.getTypeAliasRegistry().registerAlias(typeAlias); + Stream.of(this.typeAliases).forEach(typeAlias -> { + targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias); LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'"); - } + }); } if (!isEmpty(this.plugins)) { - for (Interceptor plugin : this.plugins) { - configuration.addInterceptor(plugin); + Stream.of(this.plugins).forEach(plugin -> { + targetConfiguration.addInterceptor(plugin); LOGGER.debug(() -> "Registered plugin: '" + plugin + "'"); - } + }); } if (hasLength(this.typeHandlersPackage)) { - String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, - ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); - for (String packageToScan : typeHandlersPackageArray) { - configuration.getTypeHandlerRegistry().register(packageToScan); - LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers"); - } + scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass()) + .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) + .forEach(targetConfiguration.getTypeHandlerRegistry()::register); } if (!isEmpty(this.typeHandlers)) { - for (TypeHandler typeHandler : this.typeHandlers) { - configuration.getTypeHandlerRegistry().register(typeHandler); + Stream.of(this.typeHandlers).forEach(typeHandler -> { + targetConfiguration.getTypeHandlerRegistry().register(typeHandler); LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'"); - } + }); + } + + targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler); + + if (!isEmpty(this.scriptingLanguageDrivers)) { + Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> { + targetConfiguration.getLanguageRegistry().register(languageDriver); + LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'"); + }); } + Optional.ofNullable(this.defaultScriptingLanguageDriver) + .ifPresent(targetConfiguration::setDefaultScriptingLanguage); - if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls + if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls try { - configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); + targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { - throw new NestedIOException("Failed getting a databaseId", e); + throw new IOException("Failed getting a databaseId", e); } } - if (this.cache != null) { - configuration.addCache(this.cache); - } + Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache); if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'"); } catch (Exception ex) { - throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); + throw new IOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } - if (this.transactionFactory == null) { - this.transactionFactory = new SpringManagedTransactionFactory(); - } - - configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); - - if (!isEmpty(this.mapperLocations)) { - for (Resource mapperLocation : this.mapperLocations) { - if (mapperLocation == null) { - continue; + targetConfiguration.setEnvironment(new Environment(this.environment, + this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory, + this.dataSource)); + + if (this.mapperLocations != null) { + if (this.mapperLocations.length == 0) { + LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found."); + } else { + for (Resource mapperLocation : this.mapperLocations) { + if (mapperLocation == null) { + continue; + } + try { + var xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, + mapperLocation.toString(), targetConfiguration.getSqlFragments()); + xmlMapperBuilder.parse(); + } catch (Exception e) { + throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); + } finally { + ErrorContext.instance().reset(); + } + LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'"); } - - try { - XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), - configuration, mapperLocation.toString(), configuration.getSqlFragments()); - xmlMapperBuilder.parse(); - } catch (Exception e) { - throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); - } finally { - ErrorContext.instance().reset(); - } - LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'"); } } else { - LOGGER.debug(() -> "Property 'mapperLocations' was not specified or no matching resources found"); + LOGGER.debug(() -> "Property 'mapperLocations' was not specified."); } - return this.sqlSessionFactoryBuilder.build(configuration); + return this.sqlSessionFactoryBuilder.build(targetConfiguration); } - /** - * {@inheritDoc} - */ @Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { @@ -530,31 +715,44 @@ public SqlSessionFactory getObject() throws Exception { return this.sqlSessionFactory; } - /** - * {@inheritDoc} - */ @Override public Class getObjectType() { return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass(); } - /** - * {@inheritDoc} - */ @Override public boolean isSingleton() { return true; } - /** - * {@inheritDoc} - */ @Override - public void onApplicationEvent(ApplicationEvent event) { - if (failFast && event instanceof ContextRefreshedEvent) { + public void onApplicationEvent(ContextRefreshedEvent event) { + if (failFast) { // fail-fast -> check all statements are completed this.sqlSessionFactory.getConfiguration().getMappedStatementNames(); } } + private Set> scanClasses(String packagePatterns, Class assignableType) throws IOException { + Set> classes = new HashSet<>(); + var packagePatternArray = tokenizeToStringArray(packagePatterns, + ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); + for (String packagePattern : packagePatternArray) { + var resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class"); + for (Resource resource : resources) { + try { + var classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata(); + Class clazz = Resources.classForName(classMetadata.getClassName()); + if (assignableType == null || assignableType.isAssignableFrom(clazz)) { + classes.add(clazz); + } + } catch (Throwable e) { + LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString()); + } + } + } + return classes; + } + } diff --git a/src/main/java/org/mybatis/spring/SqlSessionHolder.java b/src/main/java/org/mybatis/spring/SqlSessionHolder.java index e4ced13a80..91d4b07573 100644 --- a/src/main/java/org/mybatis/spring/SqlSessionHolder.java +++ b/src/main/java/org/mybatis/spring/SqlSessionHolder.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring; @@ -23,10 +23,9 @@ import org.springframework.transaction.support.ResourceHolderSupport; /** - * Used to keep current {@code SqlSession} in {@code TransactionSynchronizationManager}. - * The {@code SqlSessionFactory} that created that {@code SqlSession} is used as a key. - * {@code ExecutorType} is also kept to be able to check if the user is trying to change it - * during a TX (that is not allowed) and throw a Exception in that case. + * Used to keep current {@code SqlSession} in {@code TransactionSynchronizationManager}. The {@code SqlSessionFactory} + * that created that {@code SqlSession} is used as a key. {@code ExecutorType} is also kept to be able to check if the + * user is trying to change it during a TX (that is not allowed) and throw a Exception in that case. * * @author Hunter Presnall * @author Eduardo Macarron @@ -42,11 +41,14 @@ public final class SqlSessionHolder extends ResourceHolderSupport { /** * Creates a new holder instance. * - * @param sqlSession the {@code SqlSession} has to be hold. - * @param executorType the {@code ExecutorType} has to be hold. + * @param sqlSession + * the {@code SqlSession} has to be hold. + * @param executorType + * the {@code ExecutorType} has to be hold. + * @param exceptionTranslator + * the {@code PersistenceExceptionTranslator} has to be hold. */ - public SqlSessionHolder(SqlSession sqlSession, - ExecutorType executorType, + public SqlSessionHolder(SqlSession sqlSession, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSession, "SqlSession must not be null"); @@ -57,14 +59,29 @@ public SqlSessionHolder(SqlSession sqlSession, this.exceptionTranslator = exceptionTranslator; } + /** + * Gets the sql session. + * + * @return the sql session + */ public SqlSession getSqlSession() { return sqlSession; } + /** + * Gets the executor type. + * + * @return the executor type + */ public ExecutorType getExecutorType() { return executorType; } + /** + * Gets the persistence exception translator. + * + * @return the persistence exception translator + */ public PersistenceExceptionTranslator getPersistenceExceptionTranslator() { return exceptionTranslator; } diff --git a/src/main/java/org/mybatis/spring/SqlSessionTemplate.java b/src/main/java/org/mybatis/spring/SqlSessionTemplate.java index 5933b21701..d1b22c0e7f 100644 --- a/src/main/java/org/mybatis/spring/SqlSessionTemplate.java +++ b/src/main/java/org/mybatis/spring/SqlSessionTemplate.java @@ -1,21 +1,22 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring; import static java.lang.reflect.Proxy.newProxyInstance; + import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable; import static org.mybatis.spring.SqlSessionUtils.closeSqlSession; import static org.mybatis.spring.SqlSessionUtils.getSqlSession; @@ -41,23 +42,20 @@ import org.springframework.dao.support.PersistenceExceptionTranslator; /** - * Thread safe, Spring managed, {@code SqlSession} that works with Spring - * transaction management to ensure that that the actual SqlSession used is the - * one associated with the current Spring transaction. In addition, it manages - * the session life-cycle, including closing, committing or rolling back the - * session as necessary based on the Spring transaction configuration. + * Thread safe, Spring managed, {@code SqlSession} that works with Spring transaction management to ensure that the + * actual SqlSession used is the one associated with the current Spring transaction. In addition, it manages the session + * life-cycle, including closing, committing or rolling back the session as necessary based on the Spring transaction + * configuration. *

- * The template needs a SqlSessionFactory to create SqlSessions, passed as a - * constructor argument. It also can be constructed indicating the executor type - * to be used, if not, the default executor type, defined in the session factory - * will be used. + * The template needs a SqlSessionFactory to create SqlSessions, passed as a constructor argument. It also can be + * constructed indicating the executor type to be used, if not, the default executor type, defined in the session + * factory will be used. *

- * This template converts MyBatis PersistenceExceptions into unchecked - * DataAccessExceptions, using, by default, a {@code MyBatisExceptionTranslator}. + * This template converts MyBatis PersistenceExceptions into unchecked DataAccessExceptions, using, by default, a + * {@code MyBatisExceptionTranslator}. *

- * Because SqlSessionTemplate is thread safe, a single instance can be shared - * by all DAOs; there should also be a small memory savings by doing this. This - * pattern can be used in Spring configuration files as follows: + * Because SqlSessionTemplate is thread safe, a single instance can be shared by all DAOs; there should also be a small + * memory savings by doing this. This pattern can be used in Spring configuration files as follows: * *

  * {@code
@@ -85,43 +83,41 @@ public class SqlSessionTemplate implements SqlSession, DisposableBean {
   private final PersistenceExceptionTranslator exceptionTranslator;
 
   /**
-   * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory}
-   * provided as an argument.
+   * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory} provided as an argument.
    *
    * @param sqlSessionFactory
+   *          a factory of SqlSession
    */
   public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
     this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
   }
 
   /**
-   * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory}
-   * provided as an argument and the given {@code ExecutorType}
-   * {@code ExecutorType} cannot be changed once the {@code SqlSessionTemplate}
-   * is constructed.
+   * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory} provided as an argument and the given
+   * {@code ExecutorType} {@code ExecutorType} cannot be changed once the {@code SqlSessionTemplate} is constructed.
    *
    * @param sqlSessionFactory
+   *          a factory of SqlSession
    * @param executorType
+   *          an executor type on session
    */
   public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
     this(sqlSessionFactory, executorType,
-        new MyBatisExceptionTranslator(
-            sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
+        new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
   }
 
   /**
-   * Constructs a Spring managed {@code SqlSession} with the given
-   * {@code SqlSessionFactory} and {@code ExecutorType}.
-   * A custom {@code SQLExceptionTranslator} can be provided as an
-   * argument so any {@code PersistenceException} thrown by MyBatis
-   * can be custom translated to a {@code RuntimeException}
-   * The {@code SQLExceptionTranslator} can also be null and thus no
-   * exception translation will be done and MyBatis exceptions will be
-   * thrown
+   * Constructs a Spring managed {@code SqlSession} with the given {@code SqlSessionFactory} and {@code ExecutorType}. A
+   * custom {@code SQLExceptionTranslator} can be provided as an argument so any {@code PersistenceException} thrown by
+   * MyBatis can be custom translated to a {@code RuntimeException} The {@code SQLExceptionTranslator} can also be null
+   * and thus no exception translation will be done and MyBatis exceptions will be thrown
    *
    * @param sqlSessionFactory
+   *          a factory of SqlSession
    * @param executorType
+   *          an executor type on session
    * @param exceptionTranslator
+   *          a translator of exception
    */
   public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
       PersistenceExceptionTranslator exceptionTranslator) {
@@ -132,305 +128,224 @@ public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType exec
     this.sqlSessionFactory = sqlSessionFactory;
     this.executorType = executorType;
     this.exceptionTranslator = exceptionTranslator;
-    this.sqlSessionProxy = (SqlSession) newProxyInstance(
-        SqlSessionFactory.class.getClassLoader(),
-        new Class[] { SqlSession.class },
-        new SqlSessionInterceptor());
+    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
+        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
   }
 
+  /**
+   * Gets the sql session factory.
+   *
+   * @return the sql session factory
+   */
   public SqlSessionFactory getSqlSessionFactory() {
     return this.sqlSessionFactory;
   }
 
+  /**
+   * Gets the executor type.
+   *
+   * @return the executor type
+   */
   public ExecutorType getExecutorType() {
     return this.executorType;
   }
 
+  /**
+   * Gets the persistence exception translator.
+   *
+   * @return the persistence exception translator
+   */
   public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
     return this.exceptionTranslator;
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  T selectOne(String statement) {
     return this.sqlSessionProxy.selectOne(statement);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  T selectOne(String statement, Object parameter) {
     return this.sqlSessionProxy.selectOne(statement, parameter);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  Map selectMap(String statement, String mapKey) {
     return this.sqlSessionProxy.selectMap(statement, mapKey);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  Map selectMap(String statement, Object parameter, String mapKey) {
     return this.sqlSessionProxy.selectMap(statement, parameter, mapKey);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
     return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  Cursor selectCursor(String statement) {
     return this.sqlSessionProxy.selectCursor(statement);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  Cursor selectCursor(String statement, Object parameter) {
     return this.sqlSessionProxy.selectCursor(statement, parameter);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  Cursor selectCursor(String statement, Object parameter, RowBounds rowBounds) {
     return this.sqlSessionProxy.selectCursor(statement, parameter, rowBounds);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  List selectList(String statement) {
     return this.sqlSessionProxy.selectList(statement);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  List selectList(String statement, Object parameter) {
     return this.sqlSessionProxy.selectList(statement, parameter);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
     return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public void select(String statement, ResultHandler handler) {
     this.sqlSessionProxy.select(statement, handler);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public void select(String statement, Object parameter, ResultHandler handler) {
     this.sqlSessionProxy.select(statement, parameter, handler);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
     this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public int insert(String statement) {
     return this.sqlSessionProxy.insert(statement);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public int insert(String statement, Object parameter) {
     return this.sqlSessionProxy.insert(statement, parameter);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public int update(String statement) {
     return this.sqlSessionProxy.update(statement);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public int update(String statement, Object parameter) {
     return this.sqlSessionProxy.update(statement, parameter);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public int delete(String statement) {
     return this.sqlSessionProxy.delete(statement);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public int delete(String statement, Object parameter) {
     return this.sqlSessionProxy.delete(statement, parameter);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public  T getMapper(Class type) {
     return getConfiguration().getMapper(type, this);
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public void commit() {
     throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public void commit(boolean force) {
     throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public void rollback() {
     throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public void rollback(boolean force) {
     throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public void close() {
     throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public void clearCache() {
     this.sqlSessionProxy.clearCache();
   }
 
-  /**
-   * {@inheritDoc}
-   *
-   */
   @Override
   public Configuration getConfiguration() {
     return this.sqlSessionFactory.getConfiguration();
   }
 
-  /**
-   * {@inheritDoc}
-   */
   @Override
   public Connection getConnection() {
     return this.sqlSessionProxy.getConnection();
   }
 
-  /**
-   * {@inheritDoc}
-   *
-   * @since 1.0.2
-   *
-   */
   @Override
   public List flushStatements() {
     return this.sqlSessionProxy.flushStatements();
   }
 
   /**
-  * Allow gently dispose bean:
-  * 
-  * {@code
-  *
-  * 
-  *  
-  * 
-  * }
-  *
- * - * The implementation of {@link DisposableBean} forces spring context to use {@link DisposableBean#destroy()} method instead of {@link SqlSessionTemplate#close()} to shutdown gently. - * - * @see SqlSessionTemplate#close() - * @see org.springframework.beans.factory.support.DisposableBeanAdapter#inferDestroyMethodIfNecessary - * @see org.springframework.beans.factory.support.DisposableBeanAdapter#CLOSE_METHOD_NAME - */ + * Allow gently dispose bean: + * + *
+   * {@code
+   *
+   * 
+   *  
+   * 
+   * }
+   * 
+ * + * The implementation of {@link DisposableBean} forces spring context to use {@link DisposableBean#destroy()} method + * instead of {@link SqlSessionTemplate#close()} to shutdown gently. + * + * @see SqlSessionTemplate#close() + * @see "org.springframework.beans.factory.support.DisposableBeanAdapter#inferDestroyMethodIfNecessary(Object, RootBeanDefinition)" + * @see "org.springframework.beans.factory.support.DisposableBeanAdapter#CLOSE_METHOD_NAME" + */ @Override public void destroy() throws Exception { - //This method forces spring disposer to avoid call of SqlSessionTemplate.close() which gives UnsupportedOperationException + // This method forces spring disposer to avoid call of SqlSessionTemplate.close() which gives + // UnsupportedOperationException } - /** - * Proxy needed to route MyBatis method calls to the proper SqlSession got - * from Spring's Transaction Manager - * It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to - * pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}. + /** + * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also + * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the + * {@code PersistenceExceptionTranslator}. */ private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - SqlSession sqlSession = getSqlSession( - SqlSessionTemplate.this.sqlSessionFactory, - SqlSessionTemplate.this.executorType, + var sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { - Object result = method.invoke(sqlSession, args); + var result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() @@ -438,12 +353,13 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl } return result; } catch (Throwable t) { - Throwable unwrapped = unwrapThrowable(t); + var unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; - Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); + Throwable translated = SqlSessionTemplate.this.exceptionTranslator + .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } diff --git a/src/main/java/org/mybatis/spring/SqlSessionUtils.java b/src/main/java/org/mybatis/spring/SqlSessionUtils.java index 11a2262bf4..bc629960d9 100644 --- a/src/main/java/org/mybatis/spring/SqlSessionUtils.java +++ b/src/main/java/org/mybatis/spring/SqlSessionUtils.java @@ -1,42 +1,40 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring; import static org.springframework.util.Assert.notNull; import org.apache.ibatis.exceptions.PersistenceException; -import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.logging.Logger; import org.mybatis.logging.LoggerFactory; import org.mybatis.spring.transaction.SpringManagedTransactionFactory; -import org.springframework.dao.DataAccessException; import org.springframework.dao.TransientDataAccessResourceException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.jdbc.datasource.DataSourceUtils; -import org.springframework.transaction.support.TransactionSynchronizationAdapter; +import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; /** - * Handles MyBatis SqlSession life cycle. It can register and get SqlSessions from - * Spring {@code TransactionSynchronizationManager}. Also works if no transaction is active. + * Handles MyBatis SqlSession life cycle. It can register and get SqlSessions from Spring + * {@code TransactionSynchronizationManager}. Also works if no transaction is active. * - * @author Hunter Presnall + * @author Hunter Presnall * @author Eduardo Macarron */ public final class SqlSessionUtils { @@ -55,40 +53,53 @@ private SqlSessionUtils() { } /** - * Creates a new MyBatis {@code SqlSession} from the {@code SqlSessionFactory} - * provided as a parameter and using its {@code DataSource} and {@code ExecutorType} + * Creates a new MyBatis {@code SqlSession} from the {@code SqlSessionFactory} provided as a parameter and using its + * {@code DataSource} and {@code ExecutorType} + * + * @param sessionFactory + * a MyBatis {@code SqlSessionFactory} to create new sessions * - * @param sessionFactory a MyBatis {@code SqlSessionFactory} to create new sessions * @return a MyBatis {@code SqlSession} - * @throws TransientDataAccessResourceException if a transaction is active and the - * {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory} + * + * @throws TransientDataAccessResourceException + * if a transaction is active and the {@code SqlSessionFactory} is not using a + * {@code SpringManagedTransactionFactory} */ public static SqlSession getSqlSession(SqlSessionFactory sessionFactory) { - ExecutorType executorType = sessionFactory.getConfiguration().getDefaultExecutorType(); + var executorType = sessionFactory.getConfiguration().getDefaultExecutorType(); return getSqlSession(sessionFactory, executorType, null); } /** - * Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. - * Tries to get a SqlSession out of current transaction. If there is not any, it creates a new one. - * Then, it synchronizes the SqlSession with the transaction if Spring TX is active and - * SpringManagedTransactionFactory is configured as a transaction manager. + * Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. Tries to get a SqlSession out of + * current transaction. If there is not any, it creates a new one. Then, it synchronizes the SqlSession with the + * transaction if Spring TX is active and SpringManagedTransactionFactory is configured as a transaction + * manager. + * + * @param sessionFactory + * a MyBatis {@code SqlSessionFactory} to create new sessions + * @param executorType + * The executor type of the SqlSession to create + * @param exceptionTranslator + * Optional. Translates SqlSession.commit() exceptions to Spring exceptions. + * + * @return an SqlSession managed by Spring Transaction Manager + * + * @throws TransientDataAccessResourceException + * if a transaction is active and the {@code SqlSessionFactory} is not using a + * {@code SpringManagedTransactionFactory} * - * @param sessionFactory a MyBatis {@code SqlSessionFactory} to create new sessions - * @param executorType The executor type of the SqlSession to create - * @param exceptionTranslator Optional. Translates SqlSession.commit() exceptions to Spring exceptions. - * @throws TransientDataAccessResourceException if a transaction is active and the - * {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory} * @see SpringManagedTransactionFactory */ - public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { + public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, + PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); - SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); + var holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); - SqlSession session = sessionHolder(executorType, holder); + var session = sessionHolder(executorType, holder); if (session != null) { return session; } @@ -103,50 +114,55 @@ public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, Executo /** * Register session holder if synchronization is active (i.e. a Spring TX is active). + *

+ * Note: The DataSource used by the Environment should be synchronized with the transaction either through + * DataSourceTxMgr or another tx synchronization. Further assume that if an exception is thrown, whatever started the + * transaction will handle closing / rolling back the Connection associated with the SqlSession. * - * Note: The DataSource used by the Environment should be synchronized with the - * transaction either through DataSourceTxMgr or another tx synchronization. - * Further assume that if an exception is thrown, whatever started the transaction will - * handle closing / rolling back the Connection associated with the SqlSession. - * - * @param sessionFactory sqlSessionFactory used for registration. - * @param executorType executorType used for registration. - * @param exceptionTranslator persistenceExceptionTranslator used for registration. - * @param session sqlSession used for registration. + * @param sessionFactory + * sqlSessionFactory used for registration. + * @param executorType + * executorType used for registration. + * @param exceptionTranslator + * persistenceExceptionTranslator used for registration. + * @param session + * sqlSession used for registration. */ private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { SqlSessionHolder holder; if (TransactionSynchronizationManager.isSynchronizationActive()) { - Environment environment = sessionFactory.getConfiguration().getEnvironment(); + var environment = sessionFactory.getConfiguration().getEnvironment(); if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]"); holder = new SqlSessionHolder(session, executorType, exceptionTranslator); TransactionSynchronizationManager.bindResource(sessionFactory, holder); - TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); + TransactionSynchronizationManager + .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); holder.setSynchronizedWithTransaction(true); holder.requested(); + } else if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) { + LOGGER.debug(() -> "SqlSession [" + session + + "] was not registered for synchronization because DataSource is not transactional"); } else { - if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) { - LOGGER.debug(() -> "SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional"); - } else { - throw new TransientDataAccessResourceException( - "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); - } + throw new TransientDataAccessResourceException( + "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); } } else { - LOGGER.debug(() -> "SqlSession [" + session + "] was not registered for synchronization because synchronization is not active"); + LOGGER.debug(() -> "SqlSession [" + session + + "] was not registered for synchronization because synchronization is not active"); } -} + } private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) { SqlSession session = null; if (holder != null && holder.isSynchronizedWithTransaction()) { if (holder.getExecutorType() != executorType) { - throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); + throw new TransientDataAccessResourceException( + "Cannot change the ExecutorType when there is an existing transaction"); } holder.requested(); @@ -159,18 +175,20 @@ private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHol /** * Checks if {@code SqlSession} passed as an argument is managed by Spring {@code TransactionSynchronizationManager} - * If it is not, it closes it, otherwise it just updates the reference counter and - * lets Spring call the close callback when the managed transaction ends + * If it is not, it closes it, otherwise it just updates the reference counter and lets Spring call the close callback + * when the managed transaction ends * * @param session + * a target SqlSession * @param sessionFactory + * a factory of SqlSession */ public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); - SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); - if ((holder != null) && (holder.getSqlSession() == session)) { + var holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); + if (holder != null && holder.getSqlSession() == session) { LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]"); holder.released(); } else { @@ -182,26 +200,28 @@ public static void closeSqlSession(SqlSession session, SqlSessionFactory session /** * Returns if the {@code SqlSession} passed as an argument is being managed by Spring * - * @param session a MyBatis SqlSession to check - * @param sessionFactory the SqlSessionFactory which the SqlSession was built with + * @param session + * a MyBatis SqlSession to check + * @param sessionFactory + * the SqlSessionFactory which the SqlSession was built with + * * @return true if session is transactional, otherwise false */ public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); - SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); + var holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); - return (holder != null) && (holder.getSqlSession() == session); + return holder != null && holder.getSqlSession() == session; } /** - * Callback for cleaning up resources. It cleans TransactionSynchronizationManager and - * also commits and closes the {@code SqlSession}. - * It assumes that {@code Connection} life cycle will be managed by + * Callback for cleaning up resources. It cleans TransactionSynchronizationManager and also commits and closes the + * {@code SqlSession}. It assumes that {@code Connection} life cycle will be managed by * {@code DataSourceTransactionManager} or {@code JtaTransactionManager} */ - private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter { + private static final class SqlSessionSynchronization implements TransactionSynchronization { private final SqlSessionHolder holder; @@ -217,18 +237,12 @@ public SqlSessionSynchronization(SqlSessionHolder holder, SqlSessionFactory sess this.sessionFactory = sessionFactory; } - /** - * {@inheritDoc} - */ @Override public int getOrder() { // order right before any Connection synchronization return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 1; } - /** - * {@inheritDoc} - */ @Override public void suspend() { if (this.holderActive) { @@ -237,9 +251,6 @@ public void suspend() { } } - /** - * {@inheritDoc} - */ @Override public void resume() { if (this.holderActive) { @@ -248,9 +259,6 @@ public void resume() { } } - /** - * {@inheritDoc} - */ @Override public void beforeCommit(boolean readOnly) { // Connection commit or rollback will be handled by ConnectionSynchronization or @@ -258,16 +266,14 @@ public void beforeCommit(boolean readOnly) { // But, do cleanup the SqlSession / Executor, including flushing BATCH statements so // they are actually executed. // SpringManagedTransaction will no-op the commit over the jdbc connection - // TODO This updates 2nd level caches but the tx may be rolledback later on! + // TODO This updates 2nd level caches but the tx may be rolledback later on! if (TransactionSynchronizationManager.isActualTransactionActive()) { try { LOGGER.debug(() -> "Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]"); this.holder.getSqlSession().commit(); } catch (PersistenceException p) { if (this.holder.getPersistenceExceptionTranslator() != null) { - DataAccessException translated = this.holder - .getPersistenceExceptionTranslator() - .translateExceptionIfPossible(p); + var translated = this.holder.getPersistenceExceptionTranslator().translateExceptionIfPossible(p); if (translated != null) { throw translated; } @@ -277,15 +283,13 @@ public void beforeCommit(boolean readOnly) { } } - /** - * {@inheritDoc} - */ @Override public void beforeCompletion() { // Issue #18 Close SqlSession and deregister it now // because afterCompletion may be called from a different thread if (!this.holder.isOpen()) { - LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]"); + LOGGER + .debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]"); TransactionSynchronizationManager.unbindResource(sessionFactory); this.holderActive = false; LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]"); @@ -293,15 +297,13 @@ public void beforeCompletion() { } } - /** - * {@inheritDoc} - */ @Override public void afterCompletion(int status) { if (this.holderActive) { // afterCompletion may have been called from a different thread // so avoid failing if there is nothing in this one - LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]"); + LOGGER + .debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]"); TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory); this.holderActive = false; LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]"); diff --git a/src/main/java/org/mybatis/spring/annotation/MapperScan.java b/src/main/java/org/mybatis/spring/annotation/MapperScan.java index 71401d1c08..93133ffdb2 100644 --- a/src/main/java/org/mybatis/spring/annotation/MapperScan.java +++ b/src/main/java/org/mybatis/spring/annotation/MapperScan.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.annotation; @@ -25,15 +25,22 @@ import org.mybatis.spring.mapper.MapperFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; +import org.springframework.core.annotation.AliasFor; /** - * Use this annotation to register MyBatis mapper interfaces when using Java - * Config. It performs when same work as {@link MapperScannerConfigurer} via - * {@link MapperScannerRegistrar}. + * Use this annotation to register MyBatis mapper interfaces when using Java Config. It performs when same work as + * {@link MapperScannerConfigurer} via {@link MapperScannerRegistrar}. + *

+ * Either {@link #basePackageClasses} or {@link #basePackages} (or its alias {@link #value}) may be specified to define + * specific packages to scan. Since 2.0.4, If specific packages are not defined, scanning will occur from the package of + * the class that declares this annotation. + *

+ * Configuration example: * - *

Configuration example:

*
  * @Configuration
  * @MapperScan("org.mybatis.spring.sample.mapper")
@@ -41,9 +48,7 @@
  *
  *   @Bean
  *   public DataSource dataSource() {
- *     return new EmbeddedDatabaseBuilder()
- *              .addScript("schema.sql")
- *              .build();
+ *     return new EmbeddedDatabaseBuilder().addScript("schema.sql").build();
  *   }
  *
  *   @Bean
@@ -62,8 +67,10 @@
  *
  * @author Michael Lanyon
  * @author Eduardo Macarron
+ * @author Qimiao Chen
  *
  * @since 1.2.0
+ *
  * @see MapperScannerRegistrar
  * @see MapperFactoryBean
  */
@@ -75,72 +82,125 @@
 public @interface MapperScan {
 
   /**
-   * Alias for the {@link #basePackages()} attribute. Allows for more concise
-   * annotation declarations e.g.:
-   * {@code @EnableMyBatisMapperScanner("org.my.pkg")} instead of {@code
-   * @EnableMyBatisMapperScanner(basePackages= "org.my.pkg"})}.
+   * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
+   * {@code @MapperScan("org.my.pkg")} instead of {@code @MapperScan(basePackages = "org.my.pkg"})}.
+   *
+   * @return base package names
    */
+  @AliasFor("basePackages")
   String[] value() default {};
 
   /**
-   * Base packages to scan for MyBatis interfaces. Note that only interfaces
-   * with at least one method will be registered; concrete classes will be
-   * ignored.
+   * Base packages to scan for MyBatis interfaces. Note that only interfaces with at least one method will be
+   * registered; concrete classes will be ignored.
+   *
+   * @return base package names for scanning mapper interface
    */
+  @AliasFor("value")
   String[] basePackages() default {};
 
   /**
-   * Type-safe alternative to {@link #basePackages()} for specifying the packages
-   * to scan for annotated components. The package of each class specified will be scanned.
-   * 

Consider creating a special no-op marker class or interface in each package - * that serves no purpose other than being referenced by this attribute. + * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The + * package of each class specified will be scanned. + *

+ * Consider creating a special no-op marker class or interface in each package that serves no purpose other than being + * referenced by this attribute. + * + * @return classes that indicate base package for scanning mapper interface */ Class[] basePackageClasses() default {}; /** - * The {@link BeanNameGenerator} class to be used for naming detected components - * within the Spring container. + * The {@link BeanNameGenerator} class to be used for naming detected components within the Spring container. + * + * @return the class of {@link BeanNameGenerator} */ Class nameGenerator() default BeanNameGenerator.class; /** * This property specifies the annotation that the scanner will search for. *

- * The scanner will register all interfaces in the base package that also have - * the specified annotation. + * The scanner will register all interfaces in the base package that also have the specified annotation. *

* Note this can be combined with markerInterface. + * + * @return the annotation that the scanner will search for */ Class annotationClass() default Annotation.class; /** * This property specifies the parent that the scanner will search for. *

- * The scanner will register all interfaces in the base package that also have - * the specified interface class as a parent. + * The scanner will register all interfaces in the base package that also have the specified interface class as a + * parent. *

* Note this can be combined with annotationClass. + * + * @return the parent that the scanner will search for */ Class markerInterface() default Class.class; /** - * Specifies which {@code SqlSessionTemplate} to use in the case that there is - * more than one in the spring context. Usually this is only needed when you - * have more than one datasource. + * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context. + * Usually this is only needed when you have more than one datasource. + * + * @return the bean name of {@code SqlSessionTemplate} */ String sqlSessionTemplateRef() default ""; /** - * Specifies which {@code SqlSessionFactory} to use in the case that there is - * more than one in the spring context. Usually this is only needed when you - * have more than one datasource. + * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context. + * Usually this is only needed when you have more than one datasource. + * + * @return the bean name of {@code SqlSessionFactory} */ String sqlSessionFactoryRef() default ""; /** * Specifies a custom MapperFactoryBean to return a mybatis proxy as spring bean. * + * @return the class of {@code MapperFactoryBean} */ Class factoryBean() default MapperFactoryBean.class; + /** + * Whether enable lazy initialization of mapper bean. + *

+ * Default is {@code false}. + * + * @return set {@code true} to enable lazy initialization + * + * @since 2.0.2 + */ + String lazyInitialization() default ""; + + /** + * Specifies the default scope of scanned mappers. + *

+ * Default is {@code ""} (equiv to singleton). + * + * @return the default scope + */ + String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT; + + /** + * Specifies a flag that whether execute a property placeholder processing or not. + *

+ * The default is {@literal true}. This means that a property placeholder processing execute. + * + * @since 3.0.3 + * + * @return a flag that whether execute a property placeholder processing or not + */ + boolean processPropertyPlaceHolders() default true; + + /** + * Specifies which types are not eligible for mapper scanning. + * + * @since 3.0.4 + * + * @return array of customized mapper excludeFilter + */ + ComponentScan.Filter[] excludeFilters() default {}; + } diff --git a/src/main/java/org/mybatis/spring/annotation/MapperScannerRegistrar.java b/src/main/java/org/mybatis/spring/annotation/MapperScannerRegistrar.java index 2b90f1126f..81ecd2f1ff 100644 --- a/src/main/java/org/mybatis/spring/annotation/MapperScannerRegistrar.java +++ b/src/main/java/org/mybatis/spring/annotation/MapperScannerRegistrar.java @@ -1,43 +1,53 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.annotation; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.mybatis.spring.mapper.ClassPathMapperScanner; import org.mybatis.spring.mapper.MapperFactoryBean; +import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.context.ResourceLoaderAware; +import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** - * A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of - * MyBatis mapper scanning. Using an @Enable annotation allows beans to be - * registered via @Component configuration, whereas implementing + * A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of MyBatis mapper scanning. Using + * an @Enable annotation allows beans to be registered via @Component configuration, whereas implementing * {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration. * * @author Michael Lanyon @@ -46,100 +56,202 @@ * * @see MapperFactoryBean * @see ClassPathMapperScanner + * * @since 1.2.0 */ public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { + // Note: Do not move resourceLoader via cleanup private ResourceLoader resourceLoader; - /** - * {@inheritDoc} - */ @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } - /** - * {@inheritDoc} - */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { - AnnotationAttributes mapperScanAttrs = AnnotationAttributes + var mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { - registerBeanDefinitions(mapperScanAttrs, registry); + registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, + generateBaseBeanName(importingClassMetadata, 0)); } } - void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) { + void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, + BeanDefinitionRegistry registry, String beanName) { - ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); - - // this check is needed in Spring 3.1 - if (resourceLoader != null) { - scanner.setResourceLoader(resourceLoader); - } + var builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); + builder.addPropertyValue("processPropertyPlaceHolders", annoAttrs.getBoolean("processPropertyPlaceHolders")); Class annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { - scanner.setAnnotationClass(annotationClass); + builder.addPropertyValue("annotationClass", annotationClass); } Class markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { - scanner.setMarkerInterface(markerInterface); + builder.addPropertyValue("markerInterface", markerInterface); } Class generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { - scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass)); + builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass)); } Class mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { - scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass)); + builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass); } - scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef")); - scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); + var sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef"); + if (StringUtils.hasText(sqlSessionTemplateRef)) { + builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef")); + } + + var sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef"); + if (StringUtils.hasText(sqlSessionFactoryRef)) { + builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef")); + } - List basePackages = new ArrayList<>(); - basePackages.addAll( - Arrays.stream(annoAttrs.getStringArray("value")) - .filter(StringUtils::hasText) - .collect(Collectors.toList())); + List basePackages = new ArrayList<>(Arrays.stream(annoAttrs.getStringArray("basePackages")) + .filter(StringUtils::hasText).collect(Collectors.toList())); + + basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName) + .collect(Collectors.toList())); + + if (basePackages.isEmpty()) { + basePackages.add(getDefaultBasePackage(annoMeta)); + } + + var excludeFilterArray = annoAttrs.getAnnotationArray("excludeFilters"); + if (excludeFilterArray.length > 0) { + List typeFilters = new ArrayList<>(); + List> rawTypeFilters = new ArrayList<>(); + for (AnnotationAttributes excludeFilters : excludeFilterArray) { + if (excludeFilters.getStringArray("pattern").length > 0) { + // in oder to apply placeholder resolver + rawTypeFilters.addAll(parseFiltersHasPatterns(excludeFilters)); + } else { + typeFilters.addAll(typeFiltersFor(excludeFilters)); + } + } + builder.addPropertyValue("excludeFilters", typeFilters); + builder.addPropertyValue("rawExcludeFilters", rawTypeFilters); + } + + var lazyInitialization = annoAttrs.getString("lazyInitialization"); + if (StringUtils.hasText(lazyInitialization)) { + builder.addPropertyValue("lazyInitialization", lazyInitialization); + } + + var defaultScope = annoAttrs.getString("defaultScope"); + if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) { + builder.addPropertyValue("defaultScope", defaultScope); + } - basePackages.addAll( - Arrays.stream(annoAttrs.getStringArray("basePackages")) - .filter(StringUtils::hasText) - .collect(Collectors.toList())); + builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages)); - basePackages.addAll( - Arrays.stream(annoAttrs.getClassArray("basePackageClasses")) - .map(ClassUtils::getPackageName) - .collect(Collectors.toList())); + // for spring-native + builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + + registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); + + } + + /** + * Parse excludeFilters which FilterType is REGEX or ASPECTJ + * + * @param filterAttributes + * AnnotationAttributes of excludeFilters + * + * @since 3.0.4 + */ + private List> parseFiltersHasPatterns(AnnotationAttributes filterAttributes) { + + List> rawTypeFilters = new ArrayList<>(); + FilterType filterType = filterAttributes.getEnum("type"); + var expressionArray = filterAttributes.getStringArray("pattern"); + for (String expression : expressionArray) { + switch (filterType) { + case REGEX: + case ASPECTJ: + Map typeFilter = new HashMap<>(16); + typeFilter.put("type", filterType.name().toLowerCase()); + typeFilter.put("expression", expression); + rawTypeFilters.add(typeFilter); + break; + default: + throw new IllegalArgumentException("Cannot specify the 'pattern' attribute if use the " + filterType + + " FilterType in exclude filter of @MapperScan"); + } + } + return rawTypeFilters; + } + + /** + * Parse excludeFilters which FilterType is ANNOTATION ASSIGNABLE or CUSTOM + * + * @param filterAttributes + * AnnotationAttributes of excludeFilters + * + * @since 3.0.4 + */ + private List typeFiltersFor(AnnotationAttributes filterAttributes) { + + List typeFilters = new ArrayList<>(); + FilterType filterType = filterAttributes.getEnum("type"); + + for (Class filterClass : filterAttributes.getClassArray("value")) { + switch (filterType) { + case ANNOTATION: + Assert.isAssignable(Annotation.class, filterClass, + "Specified an unsupported type in 'ANNOTATION' exclude filter of @MapperScan"); + @SuppressWarnings("unchecked") + var annoClass = (Class) filterClass; + typeFilters.add(new AnnotationTypeFilter(annoClass)); + break; + case ASSIGNABLE_TYPE: + typeFilters.add(new AssignableTypeFilter(filterClass)); + break; + case CUSTOM: + Assert.isAssignable(TypeFilter.class, filterClass, + "An error occured when processing a @ComponentScan " + "CUSTOM type filter: "); + typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class)); + break; + default: + throw new IllegalArgumentException("Cannot specify the 'value' or 'classes' attribute if use the " + + filterType + " FilterType in exclude filter of @MapperScan"); + } + } + return typeFilters; + } + + private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) { + return importingClassMetadata.getClassName() + "#" + MapperScannerRegistrar.class.getSimpleName() + "#" + index; + } - scanner.registerFilters(); - scanner.doScan(StringUtils.toStringArray(basePackages)); + private static String getDefaultBasePackage(AnnotationMetadata importingClassMetadata) { + return ClassUtils.getPackageName(importingClassMetadata.getClassName()); } /** * A {@link MapperScannerRegistrar} for {@link MapperScans}. + * * @since 2.0.0 */ static class RepeatingRegistrar extends MapperScannerRegistrar { - /** - * {@inheritDoc} - */ @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, - BeanDefinitionRegistry registry) { - AnnotationAttributes mapperScansAttrs = AnnotationAttributes + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + var mapperScansAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName())); if (mapperScansAttrs != null) { - Arrays.stream(mapperScansAttrs.getAnnotationArray("value")) - .forEach(mapperScanAttrs -> registerBeanDefinitions(mapperScanAttrs, registry)); + var annotations = mapperScansAttrs.getAnnotationArray("value"); + for (var i = 0; i < annotations.length; i++) { + registerBeanDefinitions(importingClassMetadata, annotations[i], registry, + generateBaseBeanName(importingClassMetadata, i)); + } } } } diff --git a/src/main/java/org/mybatis/spring/annotation/MapperScans.java b/src/main/java/org/mybatis/spring/annotation/MapperScans.java index 3cf0745b1e..989f1b2d97 100644 --- a/src/main/java/org/mybatis/spring/annotation/MapperScans.java +++ b/src/main/java/org/mybatis/spring/annotation/MapperScans.java @@ -1,38 +1,39 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.annotation; -import org.springframework.context.annotation.Import; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.context.annotation.Import; + /** * The Container annotation that aggregates several {@link MapperScan} annotations. - * - *

Can be used natively, declaring several nested {@link MapperScan} annotations. - * Can also be used in conjunction with Java 8's support for repeatable annotations, - * where {@link MapperScan} can simply be declared several times on the same method, - * implicitly generating this container annotation. + *

+ * Can be used natively, declaring several nested {@link MapperScan} annotations. Can also be used in conjunction with + * Java 8's support for repeatable annotations, where {@link MapperScan} can simply be declared several times on the + * same method, implicitly generating this container annotation. * * @author Kazuki Shimizu + * * @since 2.0.0 + * * @see MapperScan */ @Retention(RetentionPolicy.RUNTIME) @@ -40,5 +41,11 @@ @Documented @Import(MapperScannerRegistrar.RepeatingRegistrar.class) public @interface MapperScans { + + /** + * Value. + * + * @return the mapper scan[] + */ MapperScan[] value(); } diff --git a/src/main/java/org/mybatis/spring/annotation/package-info.java b/src/main/java/org/mybatis/spring/annotation/package-info.java index 0671db0538..3146d52da7 100644 --- a/src/main/java/org/mybatis/spring/annotation/package-info.java +++ b/src/main/java/org/mybatis/spring/annotation/package-info.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ /** * Contains MyBatis annotations diff --git a/src/main/java/org/mybatis/spring/batch/MyBatisBatchItemWriter.java b/src/main/java/org/mybatis/spring/batch/MyBatisBatchItemWriter.java index edbfb31035..28589a167f 100644 --- a/src/main/java/org/mybatis/spring/batch/MyBatisBatchItemWriter.java +++ b/src/main/java/org/mybatis/spring/batch/MyBatisBatchItemWriter.java @@ -1,55 +1,55 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.batch; import static org.springframework.util.Assert.isTrue; import static org.springframework.util.Assert.notNull; -import java.util.List; - -import org.apache.ibatis.executor.BatchResult; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.logging.Logger; import org.mybatis.logging.LoggerFactory; import org.mybatis.spring.SqlSessionTemplate; -import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.infrastructure.item.Chunk; +import org.springframework.batch.infrastructure.item.ItemWriter; import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.convert.converter.Converter; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.InvalidDataAccessResourceUsageException; /** - * {@code ItemWriter} that uses the batching features from - * {@code SqlSessionTemplate} to execute a batch of statements for all items - * provided.
- * - * Provided to facilitate the migration from Spring-Batch iBATIS 2 writers to MyBatis 3
+ * {@code ItemWriter} that uses the batching features from {@code SqlSessionTemplate} to execute a batch of statements + * for all items provided. + *

+ * Provided to facilitate the migration from Spring-Batch iBATIS 2 writers to MyBatis 3. + *

+ * The user must provide a MyBatis statement id that points to the SQL statement defined in the MyBatis. + *

+ * It is expected that {@link #write(Chunk)} is called inside a transaction. If it is not each statement call will be + * autocommitted and flushStatements will return no results. + *

+ * The writer is thread safe after its properties are set (normal singleton behavior), so it can be used to write in + * multiple concurrent transactions. * - * The user must provide a MyBatis statement id that points to the SQL statement defined - * in the MyBatis.
- * - * It is expected that {@link #write(List)} is called inside a transaction. If it is not - * each statement call will be autocommitted and flushStatements will return no results.
+ * @author Eduardo Macarron * - * The writer is thread safe after its properties are set (normal singleton - * behavior), so it can be used to write in multiple concurrent transactions. + * @param + * the generic type * - * @author Eduardo Macarron - * * @since 1.1.0 */ public class MyBatisBatchItemWriter implements ItemWriter, InitializingBean { @@ -62,11 +62,14 @@ public class MyBatisBatchItemWriter implements ItemWriter, InitializingBea private boolean assertUpdates = true; + private Converter itemToParameterConverter = new PassThroughConverter<>(); + /** - * Public setter for the flag that determines whether an assertion is made - * that all items cause at least one row to be updated. + * Public setter for the flag that determines whether an assertion is made that number of BatchResult objects returned + * is one and all items cause at least one row to be updated. * - * @param assertUpdates the flag to set. Defaults to true; + * @param assertUpdates + * the flag to set. Defaults to true; */ public void setAssertUpdates(boolean assertUpdates) { this.assertUpdates = assertUpdates; @@ -75,7 +78,8 @@ public void setAssertUpdates(boolean assertUpdates) { /** * Public setter for {@link SqlSessionFactory} for injection purposes. * - * @param sqlSessionFactory a factory object for the {@link SqlSession}. + * @param sqlSessionFactory + * a factory object for the {@link SqlSession}. */ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (sqlSessionTemplate == null) { @@ -86,64 +90,87 @@ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { /** * Public setter for the {@link SqlSessionTemplate}. * - * @param sqlSessionTemplate a template object for use the {@link SqlSession} on the Spring managed transaction + * @param sqlSessionTemplate + * a template object for use the {@link SqlSession} on the Spring managed transaction */ public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSessionTemplate = sqlSessionTemplate; } /** - * Public setter for the statement id identifying the statement in the SqlMap - * configuration file. + * Public setter for the statement id identifying the statement in the SqlMap configuration file. * - * @param statementId the id for the statement + * @param statementId + * the id for the statement */ public void setStatementId(String statementId) { this.statementId = statementId; } + /** + * Public setter for a converter that converting item to parameter object. + *

+ * By default implementation, an item does not convert. + * + * @param itemToParameterConverter + * a converter that converting item to parameter object + * + * @since 2.0.0 + */ + public void setItemToParameterConverter(Converter itemToParameterConverter) { + this.itemToParameterConverter = itemToParameterConverter; + } + /** * Check mandatory properties - there must be an SqlSession and a statementId. */ @Override public void afterPropertiesSet() { notNull(sqlSessionTemplate, "A SqlSessionFactory or a SqlSessionTemplate is required."); - isTrue(ExecutorType.BATCH == sqlSessionTemplate.getExecutorType(), "SqlSessionTemplate's executor type must be BATCH"); + isTrue(ExecutorType.BATCH == sqlSessionTemplate.getExecutorType(), + "SqlSessionTemplate's executor type must be BATCH"); notNull(statementId, "A statementId is required."); + notNull(itemToParameterConverter, "A itemToParameterConverter is required."); } - /** - * {@inheritDoc} - */ @Override - public void write(final List items) { + public void write(final Chunk items) { if (!items.isEmpty()) { LOGGER.debug(() -> "Executing batch with " + items.size() + " items."); for (T item : items) { - sqlSessionTemplate.update(statementId, item); + sqlSessionTemplate.update(statementId, itemToParameterConverter.convert(item)); } - List results = sqlSessionTemplate.flushStatements(); + var results = sqlSessionTemplate.flushStatements(); if (assertUpdates) { if (results.size() != 1) { - throw new InvalidDataAccessResourceUsageException("Batch execution returned invalid results. " + - "Expected 1 but number of BatchResult objects returned was " + results.size()); + throw new InvalidDataAccessResourceUsageException("Batch execution returned invalid results. " + + "Expected 1 but number of BatchResult objects returned was " + results.size()); } - int[] updateCounts = results.get(0).getUpdateCounts(); + var updateCounts = results.get(0).getUpdateCounts(); - for (int i = 0; i < updateCounts.length; i++) { - int value = updateCounts[i]; + for (var i = 0; i < updateCounts.length; i++) { + var value = updateCounts[i]; if (value == 0) { throw new EmptyResultDataAccessException("Item " + i + " of " + updateCounts.length - + " did not update any rows: [" + items.get(i) + "]", 1); + + " did not update any rows: [" + items.getItems().get(i) + "]", 1); } } } } } + private static class PassThroughConverter implements Converter { + + @Override + public T convert(T source) { + return source; + } + + } + } diff --git a/src/main/java/org/mybatis/spring/batch/MyBatisCursorItemReader.java b/src/main/java/org/mybatis/spring/batch/MyBatisCursorItemReader.java index decf2cebe6..6498c60983 100644 --- a/src/main/java/org/mybatis/spring/batch/MyBatisCursorItemReader.java +++ b/src/main/java/org/mybatis/spring/batch/MyBatisCursorItemReader.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.batch; @@ -21,18 +21,26 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; -import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader; +import org.springframework.batch.infrastructure.item.support.AbstractItemCountingItemStreamItemReader; import org.springframework.beans.factory.InitializingBean; /** + * {@code ItemReader} that uses MyBatis Cursor to read data. + * * @author Guillaume Darmont / guillaume@dropinocean.com + * + * @param + * the generic type */ -public class MyBatisCursorItemReader extends AbstractItemCountingItemStreamItemReader implements InitializingBean { +public class MyBatisCursorItemReader extends AbstractItemCountingItemStreamItemReader + implements InitializingBean { private String queryId; @@ -40,10 +48,14 @@ public class MyBatisCursorItemReader extends AbstractItemCountingItemStreamIt private SqlSession sqlSession; private Map parameterValues; + private Supplier> parameterValuesSupplier; private Cursor cursor; private Iterator cursorIterator; + /** + * Instantiates a new my batis cursor item reader. + */ public MyBatisCursorItemReader() { setName(getShortName(MyBatisCursorItemReader.class)); } @@ -64,6 +76,8 @@ protected void doOpen() throws Exception { parameters.putAll(parameterValues); } + Optional.ofNullable(parameterValuesSupplier).map(Supplier::get).ifPresent(parameters::putAll); + sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE); cursor = sqlSession.selectCursor(queryId, parameters); cursorIterator = cursor.iterator(); @@ -94,17 +108,18 @@ public void afterPropertiesSet() throws Exception { /** * Public setter for {@link SqlSessionFactory} for injection purposes. * - * @param sqlSessionFactory a factory object for the {@link SqlSession}. + * @param sqlSessionFactory + * a factory object for the {@link SqlSession}. */ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; } /** - * Public setter for the statement id identifying the statement in the SqlMap - * configuration file. + * Public setter for the statement id identifying the statement in the SqlMap configuration file. * - * @param queryId the id for the statement + * @param queryId + * the id for the statement */ public void setQueryId(String queryId) { this.queryId = queryId; @@ -113,10 +128,22 @@ public void setQueryId(String queryId) { /** * The parameter values to be used for the query execution. * - * @param parameterValues the values keyed by the parameter named used in - * the query string. + * @param parameterValues + * the values keyed by the parameter named used in the query string. */ public void setParameterValues(Map parameterValues) { this.parameterValues = parameterValues; } + + /** + * The parameter supplier used to get parameter values for the query execution. + * + * @param parameterValuesSupplier + * the supplier used to get values keyed by the parameter named used in the query string. + * + * @since 2.1.0 + */ + public void setParameterValuesSupplier(Supplier> parameterValuesSupplier) { + this.parameterValuesSupplier = parameterValuesSupplier; + } } diff --git a/src/main/java/org/mybatis/spring/batch/MyBatisPagingItemReader.java b/src/main/java/org/mybatis/spring/batch/MyBatisPagingItemReader.java index 5dd5fa0a8c..a8baed03a6 100644 --- a/src/main/java/org/mybatis/spring/batch/MyBatisPagingItemReader.java +++ b/src/main/java/org/mybatis/spring/batch/MyBatisPagingItemReader.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.batch; @@ -20,22 +20,27 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Supplier; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionTemplate; -import org.springframework.batch.item.database.AbstractPagingItemReader; +import org.springframework.batch.infrastructure.item.database.AbstractPagingItemReader; /** - * {@code org.springframework.batch.item.ItemReader} for reading database - * records using MyBatis in a paging fashion. + * {@code org.springframework.batch.infrastructure.item.ItemReader} for reading database records using MyBatis in a + * paging fashion. *

* Provided to facilitate the migration from Spring-Batch iBATIS 2 page item readers to MyBatis 3. * * @author Eduardo Macarron - * + * + * @param + * the generic type + * * @since 1.1.0 */ public class MyBatisPagingItemReader extends AbstractPagingItemReader { @@ -48,6 +53,11 @@ public class MyBatisPagingItemReader extends AbstractPagingItemReader { private Map parameterValues; + private Supplier> parameterValuesSupplier; + + /** + * Instantiates a new my batis paging item reader. + */ public MyBatisPagingItemReader() { setName(getShortName(MyBatisPagingItemReader.class)); } @@ -55,17 +65,18 @@ public MyBatisPagingItemReader() { /** * Public setter for {@link SqlSessionFactory} for injection purposes. * - * @param sqlSessionFactory a factory object for the {@link SqlSession}. + * @param sqlSessionFactory + * a factory object for the {@link SqlSession}. */ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; } /** - * Public setter for the statement id identifying the statement in the SqlMap - * configuration file. + * Public setter for the statement id identifying the statement in the SqlMap configuration file. * - * @param queryId the id for the statement + * @param queryId + * the id for the statement */ public void setQueryId(String queryId) { this.queryId = queryId; @@ -74,31 +85,47 @@ public void setQueryId(String queryId) { /** * The parameter values to be used for the query execution. * - * @param parameterValues the values keyed by the parameter named used in - * the query string. + * @param parameterValues + * the values keyed by the parameter named used in the query string. */ public void setParameterValues(Map parameterValues) { this.parameterValues = parameterValues; } + /** + * The parameter supplier used to get parameter values for the query execution. + * + * @param parameterValuesSupplier + * the supplier used to get values keyed by the parameter named used in the query string. + * + * @since 2.1.0 + */ + public void setParameterValuesSupplier(Supplier> parameterValuesSupplier) { + this.parameterValuesSupplier = parameterValuesSupplier; + } + /** * Check mandatory properties. + * * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); notNull(sqlSessionFactory, "A SqlSessionFactory is required."); - sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH); notNull(queryId, "A queryId is required."); } @Override protected void doReadPage() { + if (sqlSessionTemplate == null) { + sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH); + } Map parameters = new HashMap<>(); if (parameterValues != null) { parameters.putAll(parameterValues); } + Optional.ofNullable(parameterValuesSupplier).map(Supplier::get).ifPresent(parameters::putAll); parameters.put("_page", getPage()); parameters.put("_pagesize", getPageSize()); parameters.put("_skiprows", getPage() * getPageSize()); @@ -110,9 +137,4 @@ protected void doReadPage() { results.addAll(sqlSessionTemplate.selectList(queryId, parameters)); } - @Override - protected void doJumpToPage(int itemIndex) { - // Not Implemented - } - } diff --git a/src/main/java/org/mybatis/spring/batch/builder/MyBatisBatchItemWriterBuilder.java b/src/main/java/org/mybatis/spring/batch/builder/MyBatisBatchItemWriterBuilder.java index 3b52fffa4d..a55c34da15 100644 --- a/src/main/java/org/mybatis/spring/batch/builder/MyBatisBatchItemWriterBuilder.java +++ b/src/main/java/org/mybatis/spring/batch/builder/MyBatisBatchItemWriterBuilder.java @@ -1,29 +1,37 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.batch.builder; +import java.util.Optional; + import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.batch.MyBatisBatchItemWriter; +import org.springframework.core.convert.converter.Converter; /** * A builder for the {@link MyBatisBatchItemWriter}. * * @author Kazuki Shimizu + * + * @param + * the generic type + * * @since 2.0.0 + * * @see MyBatisBatchItemWriter */ public class MyBatisBatchItemWriterBuilder { @@ -32,16 +40,19 @@ public class MyBatisBatchItemWriterBuilder { private SqlSessionFactory sqlSessionFactory; private String statementId; private Boolean assertUpdates; + private Converter itemToParameterConverter; /** * Set the {@link SqlSessionTemplate} to be used by writer for database access. * - * @param sqlSessionTemplate the {@link SqlSessionTemplate} to be used by writer for database access + * @param sqlSessionTemplate + * the {@link SqlSessionTemplate} to be used by writer for database access + * * @return this instance for method chaining + * * @see MyBatisBatchItemWriter#setSqlSessionTemplate(SqlSessionTemplate) */ - public MyBatisBatchItemWriterBuilder sqlSessionTemplate( - SqlSessionTemplate sqlSessionTemplate) { + public MyBatisBatchItemWriterBuilder sqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSessionTemplate = sqlSessionTemplate; return this; } @@ -49,8 +60,11 @@ public MyBatisBatchItemWriterBuilder sqlSessionTemplate( /** * Set the {@link SqlSessionFactory} to be used by writer for database access. * - * @param sqlSessionFactory the {@link SqlSessionFactory} to be used by writer for database access + * @param sqlSessionFactory + * the {@link SqlSessionFactory} to be used by writer for database access + * * @return this instance for method chaining + * * @see MyBatisBatchItemWriter#setSqlSessionFactory(SqlSessionFactory) */ public MyBatisBatchItemWriterBuilder sqlSessionFactory(SqlSessionFactory sqlSessionFactory) { @@ -61,8 +75,11 @@ public MyBatisBatchItemWriterBuilder sqlSessionFactory(SqlSessionFactory sqlS /** * Set the statement id identifying the statement in the SqlMap configuration file. * - * @param statementId the id for the statement + * @param statementId + * the id for the statement + * * @return this instance for method chaining + * * @see MyBatisBatchItemWriter#setStatementId(String) */ public MyBatisBatchItemWriterBuilder statementId(String statementId) { @@ -71,11 +88,13 @@ public MyBatisBatchItemWriterBuilder statementId(String statementId) { } /** - * The flag that determines whether an assertion is made that all items cause at least one row to - * be updated. + * The flag that determines whether an assertion is made that all items cause at least one row to be updated. + * + * @param assertUpdates + * the flag to set. Defaults to true * - * @param assertUpdates the flag to set. Defaults to true * @return this instance for method chaining + * * @see MyBatisBatchItemWriter#setAssertUpdates(boolean) */ public MyBatisBatchItemWriterBuilder assertUpdates(boolean assertUpdates) { @@ -83,19 +102,33 @@ public MyBatisBatchItemWriterBuilder assertUpdates(boolean assertUpdates) { return this; } + /** + * Set a converter that converting item to parameter object. + * + * @param itemToParameterConverter + * a converter that converting item to parameter object + * + * @return this instance for method chaining + * + * @see MyBatisBatchItemWriter#setItemToParameterConverter(Converter) + */ + public MyBatisBatchItemWriterBuilder itemToParameterConverter(Converter itemToParameterConverter) { + this.itemToParameterConverter = itemToParameterConverter; + return this; + } + /** * Returns a fully built {@link MyBatisBatchItemWriter}. * * @return the writer */ public MyBatisBatchItemWriter build() { - MyBatisBatchItemWriter writer = new MyBatisBatchItemWriter<>(); + var writer = new MyBatisBatchItemWriter(); writer.setSqlSessionTemplate(this.sqlSessionTemplate); writer.setSqlSessionFactory(this.sqlSessionFactory); writer.setStatementId(this.statementId); - if (this.assertUpdates != null) { - writer.setAssertUpdates(this.assertUpdates); - } + Optional.ofNullable(this.assertUpdates).ifPresent(writer::setAssertUpdates); + Optional.ofNullable(this.itemToParameterConverter).ifPresent(writer::setItemToParameterConverter); return writer; } diff --git a/src/main/java/org/mybatis/spring/batch/builder/MyBatisCursorItemReaderBuilder.java b/src/main/java/org/mybatis/spring/batch/builder/MyBatisCursorItemReaderBuilder.java index 5389774ec9..93e21d4548 100644 --- a/src/main/java/org/mybatis/spring/batch/builder/MyBatisCursorItemReaderBuilder.java +++ b/src/main/java/org/mybatis/spring/batch/builder/MyBatisCursorItemReaderBuilder.java @@ -1,30 +1,37 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.batch.builder; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.batch.MyBatisCursorItemReader; -import java.util.Map; - /** * A builder for the {@link MyBatisCursorItemReader}. * * @author Kazuki Shimizu + * + * @param + * the generic type + * * @since 2.0.0 + * * @see MyBatisCursorItemReader */ public class MyBatisCursorItemReaderBuilder { @@ -32,14 +39,18 @@ public class MyBatisCursorItemReaderBuilder { private SqlSessionFactory sqlSessionFactory; private String queryId; private Map parameterValues; + private Supplier> parameterValuesSupplier; private Boolean saveState; private Integer maxItemCount; /** * Set the {@link SqlSessionFactory} to be used by reader for database access. * - * @param sqlSessionFactory the {@link SqlSessionFactory} to be used by writer for database access + * @param sqlSessionFactory + * the {@link SqlSessionFactory} to be used by writer for database access + * * @return this instance for method chaining + * * @see MyBatisCursorItemReader#setSqlSessionFactory(SqlSessionFactory) */ public MyBatisCursorItemReaderBuilder sqlSessionFactory(SqlSessionFactory sqlSessionFactory) { @@ -50,8 +61,11 @@ public MyBatisCursorItemReaderBuilder sqlSessionFactory(SqlSessionFactory sql /** * Set the query id identifying the statement in the SqlMap configuration file. * - * @param queryId the id for the query + * @param queryId + * the id for the query + * * @return this instance for method chaining + * * @see MyBatisCursorItemReader#setQueryId(String) */ public MyBatisCursorItemReaderBuilder queryId(String queryId) { @@ -62,8 +76,11 @@ public MyBatisCursorItemReaderBuilder queryId(String queryId) { /** * Set the parameter values to be used for the query execution. * - * @param parameterValues the parameter values to be used for the query execution + * @param parameterValues + * the parameter values to be used for the query execution + * * @return this instance for method chaining + * * @see MyBatisCursorItemReader#setParameterValues(Map) */ public MyBatisCursorItemReaderBuilder parameterValues(Map parameterValues) { @@ -72,13 +89,33 @@ public MyBatisCursorItemReaderBuilder parameterValues(Map par } /** - * Configure if the state of the {@link org.springframework.batch.item.ItemStreamSupport} should - * be persisted within the {@link org.springframework.batch.item.ExecutionContext} for restart - * purposes. + * Set the parameter supplier to be used to get parameters for the query execution. + * + * @param parameterValuesSupplier + * the parameter supplier to be used to get parameters for the query execution + * + * @return this instance for method chaining + * + * @see MyBatisCursorItemReader#setParameterValuesSupplier(Supplier) + * + * @since 2.1.0 + */ + public MyBatisCursorItemReaderBuilder parameterValuesSupplier( + Supplier> parameterValuesSupplier) { + this.parameterValuesSupplier = parameterValuesSupplier; + return this; + } + + /** + * Configure if the state of the {@link org.springframework.batch.infrastructure.item.ItemStreamSupport} should be + * persisted within the {@link org.springframework.batch.infrastructure.item.ExecutionContext} for restart purposes. + * + * @param saveState + * defaults to true * - * @param saveState defaults to true * @return The current instance of the builder. - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setSaveState(boolean) + * + * @see org.springframework.batch.infrastructure.item.support.AbstractItemCountingItemStreamItemReader#setSaveState(boolean) */ public MyBatisCursorItemReaderBuilder saveState(boolean saveState) { this.saveState = saveState; @@ -88,9 +125,12 @@ public MyBatisCursorItemReaderBuilder saveState(boolean saveState) { /** * Configure the max number of items to be read. * - * @param maxItemCount the max items to be read + * @param maxItemCount + * the max items to be read + * * @return The current instance of the builder. - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) + * + * @see org.springframework.batch.infrastructure.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) */ public MyBatisCursorItemReaderBuilder maxItemCount(int maxItemCount) { this.maxItemCount = maxItemCount; @@ -103,16 +143,13 @@ public MyBatisCursorItemReaderBuilder maxItemCount(int maxItemCount) { * @return the reader */ public MyBatisCursorItemReader build() { - MyBatisCursorItemReader reader = new MyBatisCursorItemReader<>(); + var reader = new MyBatisCursorItemReader(); reader.setSqlSessionFactory(this.sqlSessionFactory); reader.setQueryId(this.queryId); reader.setParameterValues(this.parameterValues); - if (this.saveState != null) { - reader.setSaveState(saveState); - } - if (this.maxItemCount != null) { - reader.setMaxItemCount(this.maxItemCount); - } + reader.setParameterValuesSupplier(this.parameterValuesSupplier); + Optional.ofNullable(this.saveState).ifPresent(reader::setSaveState); + Optional.ofNullable(this.maxItemCount).ifPresent(reader::setMaxItemCount); return reader; } diff --git a/src/main/java/org/mybatis/spring/batch/builder/MyBatisPagingItemReaderBuilder.java b/src/main/java/org/mybatis/spring/batch/builder/MyBatisPagingItemReaderBuilder.java index 6e7d224077..cf710a10a8 100644 --- a/src/main/java/org/mybatis/spring/batch/builder/MyBatisPagingItemReaderBuilder.java +++ b/src/main/java/org/mybatis/spring/batch/builder/MyBatisPagingItemReaderBuilder.java @@ -1,37 +1,45 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.batch.builder; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.batch.MyBatisPagingItemReader; -import java.util.Map; - /** * A builder for the {@link MyBatisPagingItemReader}. * * @author Kazuki Shimizu - * @since 2.0.0 + * + * @param + * the generic type + * * @see MyBatisPagingItemReader + * + * @since 2.0.0 */ public class MyBatisPagingItemReaderBuilder { private SqlSessionFactory sqlSessionFactory; private String queryId; private Map parameterValues; + private Supplier> parameterValuesSupplier; private Integer pageSize; private Boolean saveState; private Integer maxItemCount; @@ -39,8 +47,11 @@ public class MyBatisPagingItemReaderBuilder { /** * Set the {@link SqlSessionFactory} to be used by writer for database access. * - * @param sqlSessionFactory the {@link SqlSessionFactory} to be used by writer for database access + * @param sqlSessionFactory + * the {@link SqlSessionFactory} to be used by writer for database access + * * @return this instance for method chaining + * * @see MyBatisPagingItemReader#setSqlSessionFactory(SqlSessionFactory) */ public MyBatisPagingItemReaderBuilder sqlSessionFactory(SqlSessionFactory sqlSessionFactory) { @@ -51,8 +62,11 @@ public MyBatisPagingItemReaderBuilder sqlSessionFactory(SqlSessionFactory sql /** * Set the query id identifying the statement in the SqlMap configuration file. * - * @param queryId the id for the query + * @param queryId + * the id for the query + * * @return this instance for method chaining + * * @see MyBatisPagingItemReader#setQueryId(String) */ public MyBatisPagingItemReaderBuilder queryId(String queryId) { @@ -63,8 +77,11 @@ public MyBatisPagingItemReaderBuilder queryId(String queryId) { /** * Set the parameter values to be used for the query execution. * - * @param parameterValues the parameter values to be used for the query execution + * @param parameterValues + * the parameter values to be used for the query execution + * * @return this instance for method chaining + * * @see MyBatisPagingItemReader#setParameterValues(Map) */ public MyBatisPagingItemReaderBuilder parameterValues(Map parameterValues) { @@ -72,12 +89,33 @@ public MyBatisPagingItemReaderBuilder parameterValues(Map par return this; } + /** + * Set the parameter supplier to be used to get parameters for the query execution. + * + * @param parameterValuesSupplier + * the parameter supplier to be used to get parameters for the query execution + * + * @return this instance for method chaining + * + * @see MyBatisPagingItemReader#setParameterValuesSupplier(Supplier) + * + * @since 2.1.0 + */ + public MyBatisPagingItemReaderBuilder parameterValuesSupplier( + Supplier> parameterValuesSupplier) { + this.parameterValuesSupplier = parameterValuesSupplier; + return this; + } + /** * The number of records to request per page/query. Defaults to 10. Must be greater than zero. * - * @param pageSize number of items + * @param pageSize + * number of items + * * @return this instance for method chaining - * @see org.springframework.batch.item.database.AbstractPagingItemReader#setPageSize(int) + * + * @see org.springframework.batch.infrastructure.item.database.AbstractPagingItemReader#setPageSize(int) */ public MyBatisPagingItemReaderBuilder pageSize(int pageSize) { this.pageSize = pageSize; @@ -85,13 +123,15 @@ public MyBatisPagingItemReaderBuilder pageSize(int pageSize) { } /** - * Configure if the state of the {@link org.springframework.batch.item.ItemStreamSupport} should - * be persisted within the {@link org.springframework.batch.item.ExecutionContext} for restart - * purposes. + * Configure if the state of the {@link org.springframework.batch.infrastructure.item.ItemStreamSupport} should be + * persisted within the {@link org.springframework.batch.infrastructure.item.ExecutionContext} for restart purposes. + * + * @param saveState + * defaults to true * - * @param saveState defaults to true * @return The current instance of the builder. - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setSaveState(boolean) + * + * @see org.springframework.batch.infrastructure.item.support.AbstractItemCountingItemStreamItemReader#setSaveState(boolean) */ public MyBatisPagingItemReaderBuilder saveState(boolean saveState) { this.saveState = saveState; @@ -101,9 +141,12 @@ public MyBatisPagingItemReaderBuilder saveState(boolean saveState) { /** * Configure the max number of items to be read. * - * @param maxItemCount the max items to be read + * @param maxItemCount + * the max items to be read + * * @return The current instance of the builder. - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) + * + * @see org.springframework.batch.infrastructure.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) */ public MyBatisPagingItemReaderBuilder maxItemCount(int maxItemCount) { this.maxItemCount = maxItemCount; @@ -116,19 +159,14 @@ public MyBatisPagingItemReaderBuilder maxItemCount(int maxItemCount) { * @return the reader */ public MyBatisPagingItemReader build() { - MyBatisPagingItemReader reader = new MyBatisPagingItemReader<>(); + var reader = new MyBatisPagingItemReader(); reader.setSqlSessionFactory(this.sqlSessionFactory); reader.setQueryId(this.queryId); reader.setParameterValues(this.parameterValues); - if (this.pageSize != null) { - reader.setPageSize(this.pageSize); - } - if (this.saveState != null) { - reader.setSaveState(saveState); - } - if (this.maxItemCount != null) { - reader.setMaxItemCount(this.maxItemCount); - } + reader.setParameterValuesSupplier(this.parameterValuesSupplier); + Optional.ofNullable(this.pageSize).ifPresent(reader::setPageSize); + Optional.ofNullable(this.saveState).ifPresent(reader::setSaveState); + Optional.ofNullable(this.maxItemCount).ifPresent(reader::setMaxItemCount); return reader; } diff --git a/src/main/java/org/mybatis/spring/batch/builder/package-info.java b/src/main/java/org/mybatis/spring/batch/builder/package-info.java index d4550d5ae4..59758649c5 100644 --- a/src/main/java/org/mybatis/spring/batch/builder/package-info.java +++ b/src/main/java/org/mybatis/spring/batch/builder/package-info.java @@ -1,21 +1,21 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ /** - * Contains classes to builder classes for {@link org.springframework.batch.item.ItemReader} and - * {@link org.springframework.batch.item.ItemWriter}. + * Contains classes to builder classes for {@link org.springframework.batch.infrastructure.item.ItemReader} and + * {@link org.springframework.batch.infrastructure.item.ItemWriter}. * * @since 2.0.0 */ diff --git a/src/main/java/org/mybatis/spring/batch/package-info.java b/src/main/java/org/mybatis/spring/batch/package-info.java index 6b1cf9f4b4..6eefbe0536 100644 --- a/src/main/java/org/mybatis/spring/batch/package-info.java +++ b/src/main/java/org/mybatis/spring/batch/package-info.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ /** * Contains classes to facilitate the migration in Spring-Batch applications. diff --git a/src/main/java/org/mybatis/spring/config/MapperScannerBeanDefinitionParser.java b/src/main/java/org/mybatis/spring/config/MapperScannerBeanDefinitionParser.java index 514278ad6f..3ad4365cfd 100644 --- a/src/main/java/org/mybatis/spring/config/MapperScannerBeanDefinitionParser.java +++ b/src/main/java/org/mybatis/spring/config/MapperScannerBeanDefinitionParser.java @@ -1,47 +1,54 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.config; import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; -import org.mybatis.spring.mapper.MapperFactoryBean; import org.mybatis.spring.mapper.ClassPathMapperScanner; +import org.mybatis.spring.mapper.MapperFactoryBean; +import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanNameGenerator; -import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.beans.factory.xml.XmlReaderContext; -import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.w3c.dom.Element; +import org.w3c.dom.Node; /** - * A {#code BeanDefinitionParser} that handles the element scan of the MyBatis. - * namespace - * + * A {#code BeanDefinitionParser} that handles the element scan of the MyBatis. namespace + * * @author Lishu Luo * @author Eduardo Macarron * * @since 1.2.0 + * * @see MapperFactoryBean * @see ClassPathMapperScanner + * @see MapperScannerConfigurer */ - -public class MapperScannerBeanDefinitionParser implements BeanDefinitionParser { +public class MapperScannerBeanDefinitionParser extends AbstractBeanDefinitionParser { private static final String ATTRIBUTE_BASE_PACKAGE = "base-package"; private static final String ATTRIBUTE_ANNOTATION = "annotation"; @@ -49,45 +56,92 @@ public class MapperScannerBeanDefinitionParser implements BeanDefinitionParser { private static final String ATTRIBUTE_NAME_GENERATOR = "name-generator"; private static final String ATTRIBUTE_TEMPLATE_REF = "template-ref"; private static final String ATTRIBUTE_FACTORY_REF = "factory-ref"; + private static final String ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS = "mapper-factory-bean-class"; + private static final String ATTRIBUTE_LAZY_INITIALIZATION = "lazy-initialization"; + private static final String ATTRIBUTE_DEFAULT_SCOPE = "default-scope"; + private static final String ATTRIBUTE_PROCESS_PROPERTY_PLACEHOLDERS = "process-property-placeholders"; + private static final String ATTRIBUTE_EXCLUDE_FILTER = "exclude-filter"; - /** - * {@inheritDoc} - */ @Override - public synchronized BeanDefinition parse(Element element, ParserContext parserContext) { - ClassPathMapperScanner scanner = new ClassPathMapperScanner(parserContext.getRegistry()); - ClassLoader classLoader = scanner.getResourceLoader().getClassLoader(); - XmlReaderContext readerContext = parserContext.getReaderContext(); - scanner.setResourceLoader(readerContext.getResourceLoader()); + protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { + var builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); + + var classLoader = ClassUtils.getDefaultClassLoader(); + + var processPropertyPlaceHolders = element.getAttribute(ATTRIBUTE_PROCESS_PROPERTY_PLACEHOLDERS); + builder.addPropertyValue("processPropertyPlaceHolders", + !StringUtils.hasText(processPropertyPlaceHolders) || Boolean.parseBoolean(processPropertyPlaceHolders)); try { - String annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION); + var annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION); if (StringUtils.hasText(annotationClassName)) { @SuppressWarnings("unchecked") - Class markerInterface = (Class) classLoader.loadClass(annotationClassName); - scanner.setAnnotationClass(markerInterface); + Class annotationClass = (Class) classLoader + .loadClass(annotationClassName); + builder.addPropertyValue("annotationClass", annotationClass); } - String markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE); + var markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE); if (StringUtils.hasText(markerInterfaceClassName)) { Class markerInterface = classLoader.loadClass(markerInterfaceClassName); - scanner.setMarkerInterface(markerInterface); + builder.addPropertyValue("markerInterface", markerInterface); } - String nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR); + var nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR); if (StringUtils.hasText(nameGeneratorClassName)) { Class nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName); - BeanNameGenerator nameGenerator = BeanUtils.instantiateClass(nameGeneratorClass, BeanNameGenerator.class); - scanner.setBeanNameGenerator(nameGenerator); + var nameGenerator = BeanUtils.instantiateClass(nameGeneratorClass, BeanNameGenerator.class); + builder.addPropertyValue("nameGenerator", nameGenerator); + } + var mapperFactoryBeanClassName = element.getAttribute(ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS); + if (StringUtils.hasText(mapperFactoryBeanClassName)) { + @SuppressWarnings("unchecked") + Class mapperFactoryBeanClass = (Class) classLoader + .loadClass(mapperFactoryBeanClassName); + builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass); + } + + // parse raw exclude-filter in + var rawExcludeFilters = parseScanTypeFilters(element, parserContext); + if (!rawExcludeFilters.isEmpty()) { + builder.addPropertyValue("rawExcludeFilters", rawExcludeFilters); } + } catch (Exception ex) { + var readerContext = parserContext.getReaderContext(); readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause()); } - String sqlSessionTemplateBeanName = element.getAttribute(ATTRIBUTE_TEMPLATE_REF); - scanner.setSqlSessionTemplateBeanName(sqlSessionTemplateBeanName); - String sqlSessionFactoryBeanName = element.getAttribute(ATTRIBUTE_FACTORY_REF); - scanner.setSqlSessionFactoryBeanName(sqlSessionFactoryBeanName); - scanner.registerFilters(); - String basePackage = element.getAttribute(ATTRIBUTE_BASE_PACKAGE); - scanner.scan(StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); - return null; + + builder.addPropertyValue("sqlSessionTemplateBeanName", element.getAttribute(ATTRIBUTE_TEMPLATE_REF)); + builder.addPropertyValue("sqlSessionFactoryBeanName", element.getAttribute(ATTRIBUTE_FACTORY_REF)); + builder.addPropertyValue("lazyInitialization", element.getAttribute(ATTRIBUTE_LAZY_INITIALIZATION)); + builder.addPropertyValue("defaultScope", element.getAttribute(ATTRIBUTE_DEFAULT_SCOPE)); + builder.addPropertyValue("basePackage", element.getAttribute(ATTRIBUTE_BASE_PACKAGE)); + + // for spring-native + builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + + return builder.getBeanDefinition(); + } + + private List> parseScanTypeFilters(Element element, ParserContext parserContext) { + List> typeFilters = new ArrayList<>(); + var nodeList = element.getChildNodes(); + for (var i = 0; i < nodeList.getLength(); i++) { + var node = nodeList.item(i); + if (Node.ELEMENT_NODE == node.getNodeType()) { + var localName = parserContext.getDelegate().getLocalName(node); + if (ATTRIBUTE_EXCLUDE_FILTER.equals(localName)) { + Map filter = new HashMap<>(16); + filter.put("type", ((Element) node).getAttribute("type")); + filter.put("expression", ((Element) node).getAttribute("expression")); + typeFilters.add(filter); + } + } + } + return typeFilters; + } + + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; } } diff --git a/src/main/java/org/mybatis/spring/config/NamespaceHandler.java b/src/main/java/org/mybatis/spring/config/NamespaceHandler.java index cee4775931..12912c8dc6 100644 --- a/src/main/java/org/mybatis/spring/config/NamespaceHandler.java +++ b/src/main/java/org/mybatis/spring/config/NamespaceHandler.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.config; @@ -23,13 +23,11 @@ * @author Lishu Luo * * @see MapperScannerBeanDefinitionParser + * * @since 1.2.0 */ public class NamespaceHandler extends NamespaceHandlerSupport { - /** - * {@inheritDoc} - */ @Override public void init() { registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser()); diff --git a/src/main/java/org/mybatis/spring/config/mybatis-spring.xsd b/src/main/java/org/mybatis/spring/config/mybatis-spring.xsd deleted file mode 100644 index cf06bd8b82..0000000000 --- a/src/main/java/org/mybatis/spring/config/mybatis-spring.xsd +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/org/mybatis/spring/config/package-info.java b/src/main/java/org/mybatis/spring/config/package-info.java index e75740849e..8d06c1ed76 100644 --- a/src/main/java/org/mybatis/spring/config/package-info.java +++ b/src/main/java/org/mybatis/spring/config/package-info.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ /** * Contains the MyBatis namespace schema and element handlers. diff --git a/src/main/java/org/mybatis/spring/mapper/ClassPathMapperScanner.java b/src/main/java/org/mybatis/spring/mapper/ClassPathMapperScanner.java index 7163405f64..3ce01d9248 100644 --- a/src/main/java/org/mybatis/spring/mapper/ClassPathMapperScanner.java +++ b/src/main/java/org/mybatis/spring/mapper/ClassPathMapperScanner.java @@ -1,62 +1,78 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.mapper; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.logging.Logger; import org.mybatis.logging.LoggerFactory; import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.aop.scope.ScopedProxyFactoryBean; +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.aot.AotDetector; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; +import org.springframework.core.NativeDetector; +import org.springframework.core.env.Environment; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.core.type.filter.TypeFilter; import org.springframework.util.StringUtils; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Set; - /** - * A {@link ClassPathBeanDefinitionScanner} that registers Mappers by - * {@code basePackage}, {@code annotationClass}, or {@code markerInterface}. If - * an {@code annotationClass} and/or {@code markerInterface} is specified, only - * the specified types will be searched (searching for all interfaces will be - * disabled). + * A {@link ClassPathBeanDefinitionScanner} that registers Mappers by {@code basePackage}, {@code annotationClass}, or + * {@code markerInterface}. If an {@code annotationClass} and/or {@code markerInterface} is specified, only the + * specified types will be searched (searching for all interfaces will be disabled). *

- * This functionality was previously a private class of - * {@link MapperScannerConfigurer}, but was broken out in version 1.2.0. + * This functionality was previously a private class of {@link MapperScannerConfigurer}, but was broken out in version + * 1.2.0. * * @author Hunter Presnall * @author Eduardo Macarron - * + * * @see MapperFactoryBean + * * @since 1.2.0 */ public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { private static final Logger LOGGER = LoggerFactory.getLogger(ClassPathMapperScanner.class); + // Copy of FactoryBean#OBJECT_TYPE_ATTRIBUTE which was added in Spring 5.2 + static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType"; + private boolean addToConfig = true; + private boolean lazyInitialization; + + private boolean printWarnLogIfNotFoundMappers = true; + private SqlSessionFactory sqlSessionFactory; private SqlSessionTemplate sqlSessionTemplate; @@ -69,52 +85,193 @@ public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { private Class markerInterface; - private MapperFactoryBean mapperFactoryBean = new MapperFactoryBean<>(); + private Class mapperFactoryBeanClass = MapperFactoryBean.class; + + private String defaultScope; + private List excludeFilters; + /** + * Instantiates a new class path mapper scanner. + * + * @param registry + * the registry + * @param environment + * the environment + */ + public ClassPathMapperScanner(BeanDefinitionRegistry registry, Environment environment) { + super(registry, false, environment); + setIncludeAnnotationConfig(!AotDetector.useGeneratedArtifacts()); + setPrintWarnLogIfNotFoundMappers(!NativeDetector.inNativeImage()); + } + + /** + * Instantiates a new class path mapper scanner. + * + * @param registry + * the registry + * + * @deprecated Please use the {@link #ClassPathMapperScanner(BeanDefinitionRegistry, Environment)}. + */ + @Deprecated(since = "3.0.4", forRemoval = true) public ClassPathMapperScanner(BeanDefinitionRegistry registry) { super(registry, false); + setIncludeAnnotationConfig(!AotDetector.useGeneratedArtifacts()); + setPrintWarnLogIfNotFoundMappers(!NativeDetector.inNativeImage()); } + /** + * Sets the adds the to config. + * + * @param addToConfig + * the new adds the to config + */ public void setAddToConfig(boolean addToConfig) { this.addToConfig = addToConfig; } + /** + * Sets the annotation class. + * + * @param annotationClass + * the new annotation class + */ public void setAnnotationClass(Class annotationClass) { this.annotationClass = annotationClass; } + /** + * Set whether enable lazy initialization for mapper bean. + *

+ * Default is {@code false}. + * + * @param lazyInitialization + * Set the @{code true} to enable + * + * @since 2.0.2 + */ + public void setLazyInitialization(boolean lazyInitialization) { + this.lazyInitialization = lazyInitialization; + } + + /** + * Set whether print warning log if not found mappers that matches conditions. + *

+ * Default is {@code true}. But {@code false} when running in native image. + * + * @param printWarnLogIfNotFoundMappers + * Set the @{code true} to print + * + * @since 3.0.1 + */ + public void setPrintWarnLogIfNotFoundMappers(boolean printWarnLogIfNotFoundMappers) { + this.printWarnLogIfNotFoundMappers = printWarnLogIfNotFoundMappers; + } + + /** + * Sets the marker interface. + * + * @param markerInterface + * the new marker interface + */ public void setMarkerInterface(Class markerInterface) { this.markerInterface = markerInterface; } + /** + * Sets the exclude filters. + * + * @param excludeFilters + * the new exclude filters + */ + public void setExcludeFilters(List excludeFilters) { + this.excludeFilters = excludeFilters; + } + + /** + * Sets the sql session factory. + * + * @param sqlSessionFactory + * the new sql session factory + */ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; } + /** + * Sets the sql session template. + * + * @param sqlSessionTemplate + * the new sql session template + */ public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSessionTemplate = sqlSessionTemplate; } + /** + * Sets the sql session template bean name. + * + * @param sqlSessionTemplateBeanName + * the new sql session template bean name + */ public void setSqlSessionTemplateBeanName(String sqlSessionTemplateBeanName) { this.sqlSessionTemplateBeanName = sqlSessionTemplateBeanName; } + /** + * Sets the sql session factory bean name. + * + * @param sqlSessionFactoryBeanName + * the new sql session factory bean name + */ public void setSqlSessionFactoryBeanName(String sqlSessionFactoryBeanName) { this.sqlSessionFactoryBeanName = sqlSessionFactoryBeanName; } + /** + * Sets the mapper factory bean. + * + * @param mapperFactoryBean + * the new mapper factory bean + * + * @deprecated Since 2.0.1, Please use the {@link #setMapperFactoryBeanClass(Class)}. + */ + @Deprecated(since = "2.0.1", forRemoval = true) public void setMapperFactoryBean(MapperFactoryBean mapperFactoryBean) { - this.mapperFactoryBean = mapperFactoryBean != null ? mapperFactoryBean : new MapperFactoryBean<>(); + this.mapperFactoryBeanClass = mapperFactoryBean == null ? MapperFactoryBean.class : mapperFactoryBean.getClass(); } + /** + * Set the {@code MapperFactoryBean} class. + * + * @param mapperFactoryBeanClass + * the {@code MapperFactoryBean} class + * + * @since 2.0.1 + */ + public void setMapperFactoryBeanClass(Class mapperFactoryBeanClass) { + this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass; + } /** - * Configures parent scanner to search for the right interfaces. It can search - * for all interfaces or just for those that extends a markerInterface or/and - * those annotated with the annotationClass + * Set the default scope of scanned mappers. + *

+ * Default is {@code null} (equiv to singleton). + * + * @param defaultScope + * the scope + * + * @since 2.0.6 + */ + public void setDefaultScope(String defaultScope) { + this.defaultScope = defaultScope; + } + + /** + * Configures parent scanner to search for the right interfaces. It can search for all interfaces or just for those + * that extends a markerInterface or/and those annotated with the annotationClass */ public void registerFilters() { - boolean acceptAllInterfaces = true; + var acceptAllInterfaces = true; // if specified, use the given annotation and / or marker interface if (this.annotationClass != null) { @@ -140,22 +297,31 @@ protected boolean matchClassName(String className) { // exclude package-info.java addExcludeFilter((metadataReader, metadataReaderFactory) -> { - String className = metadataReader.getClassMetadata().getClassName(); + var className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); }); + + // exclude types declared by MapperScan.excludeFilters + if (excludeFilters != null && excludeFilters.size() > 0) { + for (TypeFilter excludeFilter : excludeFilters) { + addExcludeFilter(excludeFilter); + } + } } /** - * Calls the parent search that will search and register all the candidates. - * Then the registered objects are post processed to set them as - * MapperFactoryBeans + * Calls the parent search that will search and register all the candidates. Then the registered objects are post + * processed to set them as MapperFactoryBeans */ @Override public Set doScan(String... basePackages) { - Set beanDefinitions = super.doScan(basePackages); + var beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { - LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); + if (printWarnLogIfNotFoundMappers) { + LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + + "' package. Please check your configuration."); + } } else { processBeanDefinitions(beanDefinitions); } @@ -164,23 +330,44 @@ public Set doScan(String... basePackages) { } private void processBeanDefinitions(Set beanDefinitions) { - GenericBeanDefinition definition; + AbstractBeanDefinition definition; + var registry = getRegistry(); for (BeanDefinitionHolder holder : beanDefinitions) { - definition = (GenericBeanDefinition) holder.getBeanDefinition(); - String beanClassName = definition.getBeanClassName(); - LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() - + "' and '" + beanClassName + "' mapperInterface"); + definition = (AbstractBeanDefinition) holder.getBeanDefinition(); + var scopedProxy = false; + if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) { + definition = (AbstractBeanDefinition) Optional + .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition()) + .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException( + "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]")); + scopedProxy = true; + } + var beanClassName = definition.getBeanClassName(); + LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + + "' mapperInterface"); // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59 - definition.setBeanClass(this.mapperFactoryBean.getClass()); + try { + Class beanClass = Resources.classForName(beanClassName); + // Attribute for MockitoPostProcessor + // https://github.com/mybatis/spring-boot-starter/issues/475 + definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClass); + // for spring-native + definition.getPropertyValues().add("mapperInterface", beanClass); + } catch (ClassNotFoundException ignore) { + // ignore + } + + definition.setBeanClass(this.mapperFactoryBeanClass); definition.getPropertyValues().add("addToConfig", this.addToConfig); - boolean explicitFactoryUsed = false; + var explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { - definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); + definition.getPropertyValues().add("sqlSessionFactory", + new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); @@ -189,13 +376,16 @@ private void processBeanDefinitions(Set beanDefinitions) { if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { - LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); + LOGGER.warn( + () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } - definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); + definition.getPropertyValues().add("sqlSessionTemplate", + new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { if (explicitFactoryUsed) { - LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); + LOGGER.warn( + () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; @@ -205,30 +395,41 @@ private void processBeanDefinitions(Set beanDefinitions) { LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } + + definition.setLazyInit(lazyInitialization); + + if (scopedProxy) { + continue; + } + + if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) { + definition.setScope(defaultScope); + } + + if (!definition.isSingleton()) { + var proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true); + if (registry.containsBeanDefinition(proxyHolder.getBeanName())) { + registry.removeBeanDefinition(proxyHolder.getBeanName()); + } + registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition()); + } + } } - /** - * {@inheritDoc} - */ @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); } - /** - * {@inheritDoc} - */ @Override protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) { if (super.checkCandidate(beanName, beanDefinition)) { return true; - } else { - LOGGER.warn(() -> "Skipping MapperFactoryBean with name '" + beanName - + "' and '" + beanDefinition.getBeanClassName() + "' mapperInterface" - + ". Bean already defined with the same name!"); - return false; } + LOGGER.warn(() -> "Skipping MapperFactoryBean with name '" + beanName + "' and '" + + beanDefinition.getBeanClassName() + "' mapperInterface" + ". Bean already defined with the same name!"); + return false; } } diff --git a/src/main/java/org/mybatis/spring/mapper/MapperFactoryBean.java b/src/main/java/org/mybatis/spring/mapper/MapperFactoryBean.java index 13ba830efe..66132589f2 100644 --- a/src/main/java/org/mybatis/spring/mapper/MapperFactoryBean.java +++ b/src/main/java/org/mybatis/spring/mapper/MapperFactoryBean.java @@ -1,31 +1,30 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.mapper; import static org.springframework.util.Assert.notNull; import org.apache.ibatis.executor.ErrorContext; -import org.apache.ibatis.session.Configuration; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.support.SqlSessionDaoSupport; import org.springframework.beans.factory.FactoryBean; /** - * BeanFactory that enables injection of MyBatis mapper interfaces. It can be set up with a - * SqlSessionFactory or a pre-configured SqlSessionTemplate. + * BeanFactory that enables injection of MyBatis mapper interfaces. It can be set up with a SqlSessionFactory or a + * pre-configured SqlSessionTemplate. *

* Sample configuration: * @@ -49,6 +48,9 @@ * * @author Eduardo Macarron * + * @param + * the generic type + * * @see SqlSessionTemplate */ public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean { @@ -57,24 +59,30 @@ public class MapperFactoryBean extends SqlSessionDaoSupport implements Factor private boolean addToConfig = true; + /** + * Instantiates a new mapper factory bean. + */ public MapperFactoryBean() { - //intentionally empty + // intentionally empty } - + + /** + * Instantiates a new mapper factory bean. + * + * @param mapperInterface + * the mapper interface + */ public MapperFactoryBean(Class mapperInterface) { this.mapperInterface = mapperInterface; } - /** - * {@inheritDoc} - */ @Override protected void checkDaoConfig() { super.checkDaoConfig(); notNull(this.mapperInterface, "Property 'mapperInterface' is required"); - Configuration configuration = getSqlSession().getConfiguration(); + var configuration = getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { configuration.addMapper(this.mapperInterface); @@ -87,36 +95,28 @@ protected void checkDaoConfig() { } } - /** - * {@inheritDoc} - */ @Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } - /** - * {@inheritDoc} - */ @Override public Class getObjectType() { return this.mapperInterface; } - /** - * {@inheritDoc} - */ @Override public boolean isSingleton() { return true; } - //------------- mutators -------------- + // ------------- mutators -------------- /** * Sets the mapper interface of the MyBatis mapper * - * @param mapperInterface class of the interface + * @param mapperInterface + * class of the interface */ public void setMapperInterface(Class mapperInterface) { this.mapperInterface = mapperInterface; @@ -132,15 +132,15 @@ public Class getMapperInterface() { } /** - * If addToConfig is false the mapper will not be added to MyBatis. This means - * it must have been included in mybatis-config.xml. - *

- * If it is true, the mapper will be added to MyBatis in the case it is not already - * registered. - *

+ * If addToConfig is false the mapper will not be added to MyBatis. This means it must have been included in + * mybatis-config.xml. + *

+ * If it is true, the mapper will be added to MyBatis in the case it is not already registered. + *

* By default addToConfig is true. * * @param addToConfig + * a flag that whether add mapper to MyBatis or not */ public void setAddToConfig(boolean addToConfig) { this.addToConfig = addToConfig; @@ -149,8 +149,7 @@ public void setAddToConfig(boolean addToConfig) { /** * Return the flag for addition into MyBatis config. * - * @return true if the mapper will be added to MyBatis in the case it is not already - * registered. + * @return true if the mapper will be added to MyBatis in the case it is not already registered. */ public boolean isAddToConfig() { return addToConfig; diff --git a/src/main/java/org/mybatis/spring/mapper/MapperScannerConfigurer.java b/src/main/java/org/mybatis/spring/mapper/MapperScannerConfigurer.java index 8ac71883e8..6445421814 100644 --- a/src/main/java/org/mybatis/spring/mapper/MapperScannerConfigurer.java +++ b/src/main/java/org/mybatis/spring/mapper/MapperScannerConfigurer.java @@ -1,32 +1,36 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.mapper; import static org.springframework.util.Assert.notNull; import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; import org.apache.ibatis.session.SqlSessionFactory; +import org.jspecify.annotations.Nullable; import org.mybatis.spring.SqlSessionTemplate; -import org.springframework.beans.PropertyValue; +import org.springframework.beans.BeanUtils; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.PropertyResourceConfigurer; import org.springframework.beans.factory.config.TypedStringValue; @@ -37,43 +41,45 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.Environment; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.AspectJTypeFilter; +import org.springframework.core.type.filter.AssignableTypeFilter; +import org.springframework.core.type.filter.RegexPatternTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** - * BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for - * interfaces and registers them as {@code MapperFactoryBean}. Note that only interfaces with at - * least one method will be registered; concrete classes will be ignored. + * BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for interfaces and + * registers them as {@code MapperFactoryBean}. Note that only interfaces with at least one method will be registered; + * concrete classes will be ignored. *

- * This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to - * {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269 - * for the details. + * This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to + * {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269 for the + * details. *

- * The {@code basePackage} property can contain more than one package name, separated by either - * commas or semicolons. + * The {@code basePackage} property can contain more than one package name, separated by either commas or semicolons. *

- * This class supports filtering the mappers created by either specifying a marker interface or an - * annotation. The {@code annotationClass} property specifies an annotation to search for. The - * {@code markerInterface} property specifies a parent interface to search for. If both properties - * are specified, mappers are added for interfaces that match either criteria. By default, - * these two properties are null, so all interfaces in the given {@code basePackage} are added as - * mappers. + * This class supports filtering the mappers created by either specifying a marker interface or an annotation. The + * {@code annotationClass} property specifies an annotation to search for. The {@code markerInterface} property + * specifies a parent interface to search for. If both properties are specified, mappers are added for interfaces that + * match either criteria. By default, these two properties are null, so all interfaces in the given + * {@code basePackage} are added as mappers. *

- * This configurer enables autowire for all the beans that it creates so that they are - * automatically autowired with the proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}. - * If there is more than one {@code SqlSessionFactory} in the application, however, autowiring - * cannot be used. In this case you must explicitly specify either an {@code SqlSessionFactory} or - * an {@code SqlSessionTemplate} to use via the bean name properties. Bean names are used - * rather than actual objects because Spring does not initialize property placeholders until after - * this class is processed. + * This configurer enables autowire for all the beans that it creates so that they are automatically autowired with the + * proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}. If there is more than one {@code SqlSessionFactory} + * in the application, however, autowiring cannot be used. In this case you must explicitly specify either an + * {@code SqlSessionFactory} or an {@code SqlSessionTemplate} to use via the bean name properties. Bean names + * are used rather than actual objects because Spring does not initialize property placeholders until after this class + * is processed. *

- * Passing in an actual object which may require placeholders (i.e. DB user password) will fail. - * Using bean names defers actual object creation until later in the startup - * process, after all placeholder substitution is completed. However, note that this configurer - * does support property placeholders of its own properties. The basePackage - * and bean name properties all support ${property} style substitution. + * Passing in an actual object which may require placeholders (i.e. DB user password) will fail. Using bean names defers + * actual object creation until later in the startup process, after all placeholder substitution is completed. However, + * note that this configurer does support property placeholders of its own properties. The + * basePackage and bean name properties all support ${property} style substitution. *

* Configuration sample: - *

* *

  * {@code
@@ -91,12 +97,15 @@
  * @see MapperFactoryBean
  * @see ClassPathMapperScanner
  */
-public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
+public class MapperScannerConfigurer
+    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
 
   private String basePackage;
 
   private boolean addToConfig = true;
 
+  private String lazyInitialization;
+
   private SqlSessionFactory sqlSessionFactory;
 
   private SqlSessionTemplate sqlSessionTemplate;
@@ -109,6 +118,12 @@ public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProces
 
   private Class markerInterface;
 
+  private List excludeFilters;
+
+  private List> rawExcludeFilters;
+
+  private Class mapperFactoryBeanClass;
+
   private ApplicationContext applicationContext;
 
   private String beanName;
@@ -117,6 +132,8 @@ public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProces
 
   private BeanNameGenerator nameGenerator;
 
+  private String defaultScope;
+
   /**
    * This property lets you set the base package for your mapper interface files.
    * 

@@ -124,7 +141,8 @@ public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProces *

* Mappers will be searched for recursively starting in the specified package(s). * - * @param basePackage base package name + * @param basePackage + * base package name */ public void setBasePackage(String basePackage) { this.basePackage = basePackage; @@ -134,21 +152,37 @@ public void setBasePackage(String basePackage) { * Same as {@code MapperFactoryBean#setAddToConfig(boolean)}. * * @param addToConfig + * a flag that whether add mapper to MyBatis or not + * * @see MapperFactoryBean#setAddToConfig(boolean) */ public void setAddToConfig(boolean addToConfig) { this.addToConfig = addToConfig; } + /** + * Set whether enable lazy initialization for mapper bean. + *

+ * Default is {@code false}. + * + * @param lazyInitialization + * Set the @{code true} to enable + * + * @since 2.0.2 + */ + public void setLazyInitialization(String lazyInitialization) { + this.lazyInitialization = lazyInitialization; + } + /** * This property specifies the annotation that the scanner will search for. *

- * The scanner will register all interfaces in the base package that also have the - * specified annotation. + * The scanner will register all interfaces in the base package that also have the specified annotation. *

* Note this can be combined with markerInterface. * - * @param annotationClass annotation class + * @param annotationClass + * annotation class */ public void setAnnotationClass(Class annotationClass) { this.annotationClass = annotationClass; @@ -157,25 +191,54 @@ public void setAnnotationClass(Class annotationClass) { /** * This property specifies the parent that the scanner will search for. *

- * The scanner will register all interfaces in the base package that also have the - * specified interface class as a parent. + * The scanner will register all interfaces in the base package that also have the specified interface class as a + * parent. *

* Note this can be combined with annotationClass. * - * @param superClass parent class + * @param superClass + * parent class */ public void setMarkerInterface(Class superClass) { this.markerInterface = superClass; } /** - * Specifies which {@code SqlSessionTemplate} to use in the case that there is - * more than one in the spring context. Usually this is only needed when you - * have more than one datasource. + * Specifies which types are not eligible for the mapper scanner. + *

+ * The scanner will exclude types that define with excludeFilters. + * + * @since 3.0.4 + * + * @param excludeFilters + * list of TypeFilter + */ + public void setExcludeFilters(List excludeFilters) { + this.excludeFilters = excludeFilters; + } + + /** + * In order to support process PropertyPlaceHolders. *

+ * After parsed, it will be added to excludeFilters. + * + * @since 3.0.4 + * + * @param rawExcludeFilters + * list of rawExcludeFilter + */ + public void setRawExcludeFilters(List> rawExcludeFilters) { + this.rawExcludeFilters = rawExcludeFilters; + } + + /** + * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context. + * Usually this is only needed when you have more than one datasource. + * * @deprecated Use {@link #setSqlSessionTemplateBeanName(String)} instead * * @param sqlSessionTemplate + * a template of SqlSession */ @Deprecated public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { @@ -183,30 +246,29 @@ public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { } /** - * Specifies which {@code SqlSessionTemplate} to use in the case that there is - * more than one in the spring context. Usually this is only needed when you - * have more than one datasource. + * Specifies which {@code SqlSessionTemplate} to use in the case that there is more than one in the spring context. + * Usually this is only needed when you have more than one datasource. *

- * Note bean names are used, not bean references. This is because the scanner - * loads early during the start process and it is too early to build mybatis - * object instances. + * Note bean names are used, not bean references. This is because the scanner loads early during the start process and + * it is too early to build mybatis object instances. * * @since 1.1.0 * - * @param sqlSessionTemplateName Bean name of the {@code SqlSessionTemplate} + * @param sqlSessionTemplateName + * Bean name of the {@code SqlSessionTemplate} */ public void setSqlSessionTemplateBeanName(String sqlSessionTemplateName) { this.sqlSessionTemplateBeanName = sqlSessionTemplateName; } /** - * Specifies which {@code SqlSessionFactory} to use in the case that there is - * more than one in the spring context. Usually this is only needed when you - * have more than one datasource. - *

+ * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context. + * Usually this is only needed when you have more than one datasource. + * * @deprecated Use {@link #setSqlSessionFactoryBeanName(String)} instead. * * @param sqlSessionFactory + * a factory of SqlSession */ @Deprecated public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { @@ -214,43 +276,52 @@ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { } /** - * Specifies which {@code SqlSessionFactory} to use in the case that there is - * more than one in the spring context. Usually this is only needed when you - * have more than one datasource. + * Specifies which {@code SqlSessionFactory} to use in the case that there is more than one in the spring context. + * Usually this is only needed when you have more than one datasource. *

- * Note bean names are used, not bean references. This is because the scanner - * loads early during the start process and it is too early to build mybatis - * object instances. + * Note bean names are used, not bean references. This is because the scanner loads early during the start process and + * it is too early to build mybatis object instances. * * @since 1.1.0 * - * @param sqlSessionFactoryName Bean name of the {@code SqlSessionFactory} + * @param sqlSessionFactoryName + * Bean name of the {@code SqlSessionFactory} */ public void setSqlSessionFactoryBeanName(String sqlSessionFactoryName) { this.sqlSessionFactoryBeanName = sqlSessionFactoryName; } /** + * Specifies a flag that whether execute a property placeholder processing or not. + *

+ * The default is {@literal false}. This means that a property placeholder processing does not execute. * * @since 1.1.1 * * @param processPropertyPlaceHolders + * a flag that whether execute a property placeholder processing or not */ public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) { this.processPropertyPlaceHolders = processPropertyPlaceHolders; } /** - * {@inheritDoc} + * The class of the {@link MapperFactoryBean} to return a mybatis proxy as spring bean. + * + * @param mapperFactoryBeanClass + * The class of the MapperFactoryBean + * + * @since 2.0.1 */ + public void setMapperFactoryBeanClass(Class mapperFactoryBeanClass) { + this.mapperFactoryBeanClass = mapperFactoryBeanClass; + } + @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } - /** - * {@inheritDoc} - */ @Override public void setBeanName(String name) { this.beanName = name; @@ -260,6 +331,7 @@ public void setBeanName(String name) { * Gets beanNameGenerator to be used while running the scanner. * * @return the beanNameGenerator BeanNameGenerator that has been configured + * * @since 1.2.0 */ public BeanNameGenerator getNameGenerator() { @@ -269,7 +341,9 @@ public BeanNameGenerator getNameGenerator() { /** * Sets beanNameGenerator to be used while running the scanner. * - * @param nameGenerator the beanNameGenerator to set + * @param nameGenerator + * the beanNameGenerator to set + * * @since 1.2.0 */ public void setNameGenerator(BeanNameGenerator nameGenerator) { @@ -277,64 +351,76 @@ public void setNameGenerator(BeanNameGenerator nameGenerator) { } /** - * {@inheritDoc} + * Sets the default scope of scanned mappers. + *

+ * Default is {@code null} (equiv to singleton). + * + * @param defaultScope + * the default scope + * + * @since 2.0.6 */ + public void setDefaultScope(String defaultScope) { + this.defaultScope = defaultScope; + } + @Override public void afterPropertiesSet() throws Exception { notNull(this.basePackage, "Property 'basePackage' is required"); } - /** - * {@inheritDoc} - */ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { // left intentionally blank } - /** - * {@inheritDoc} - * - * @since 1.0.2 - */ @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } - ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); + var scanner = new ClassPathMapperScanner(registry, getEnvironment()); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); + scanner.setExcludeFilters(this.excludeFilters = mergeExcludeFilters()); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); + scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass); + if (StringUtils.hasText(lazyInitialization)) { + scanner.setLazyInitialization(Boolean.parseBoolean(lazyInitialization)); + } + if (StringUtils.hasText(defaultScope)) { + scanner.setDefaultScope(defaultScope); + } scanner.registerFilters(); - scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); + scanner.scan( + StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } /* - * BeanDefinitionRegistries are called early in application startup, before - * BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been - * loaded and any property substitution of this class' properties will fail. To avoid this, find - * any PropertyResourceConfigurers defined in the context and run them on this class' bean + * BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that + * PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will + * fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean * definition. Then update the values. */ private void processPropertyPlaceHolders() { - Map prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class); + Map prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class, + false, false); if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) { - BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext) - .getBeanFactory().getBeanDefinition(beanName); + var mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory() + .getBeanDefinition(beanName); // PropertyResourceConfigurer does not expose any methods to explicitly perform // property placeholder substitution. Instead, create a BeanFactory that just // contains this mapper scanner and post process the factory. - DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + var factory = new DefaultListableBeanFactory(); factory.registerBeanDefinition(beanName, mapperScannerBean); for (PropertyResourceConfigurer prc : prcs.values()) { @@ -343,30 +429,110 @@ private void processPropertyPlaceHolders() { PropertyValues values = mapperScannerBean.getPropertyValues(); - this.basePackage = updatePropertyValue("basePackage", values); - this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values); - this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values); + this.basePackage = getPropertyValue("basePackage", values); + this.sqlSessionFactoryBeanName = getPropertyValue("sqlSessionFactoryBeanName", values); + this.sqlSessionTemplateBeanName = getPropertyValue("sqlSessionTemplateBeanName", values); + this.lazyInitialization = getPropertyValue("lazyInitialization", values); + this.defaultScope = getPropertyValue("defaultScope", values); + this.rawExcludeFilters = getPropertyValueForTypeFilter("rawExcludeFilters", values); } + this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null); + this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName) + .map(getEnvironment()::resolvePlaceholders).orElse(null); + this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName) + .map(getEnvironment()::resolvePlaceholders).orElse(null); + this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders) + .orElse(null); + this.defaultScope = Optional.ofNullable(this.defaultScope).map(getEnvironment()::resolvePlaceholders).orElse(null); + } + + private Environment getEnvironment() { + return this.applicationContext.getEnvironment(); } - private String updatePropertyValue(String propertyName, PropertyValues values) { - PropertyValue property = values.getPropertyValue(propertyName); + private String getPropertyValue(String propertyName, PropertyValues values) { + var property = values.getPropertyValue(propertyName); if (property == null) { return null; } - Object value = property.getValue(); + var value = property.getValue(); if (value == null) { return null; - } else if (value instanceof String) { + } + if (value instanceof String) { return value.toString(); - } else if (value instanceof TypedStringValue) { + } + if (value instanceof TypedStringValue) { return ((TypedStringValue) value).getValue(); - } else { + } + return null; + } + + @SuppressWarnings("unchecked") + private List> getPropertyValueForTypeFilter(String propertyName, PropertyValues values) { + var property = values.getPropertyValue(propertyName); + Object value; + if (property == null || (value = property.getValue()) == null || !(value instanceof List)) { return null; } + return (List>) value; + } + + private List mergeExcludeFilters() { + List typeFilters = new ArrayList<>(); + if (this.rawExcludeFilters == null || this.rawExcludeFilters.isEmpty()) { + return this.excludeFilters; + } + if (this.excludeFilters != null && !this.excludeFilters.isEmpty()) { + typeFilters.addAll(this.excludeFilters); + } + try { + for (Map typeFilter : this.rawExcludeFilters) { + typeFilters.add( + createTypeFilter(typeFilter.get("type"), typeFilter.get("expression"), this.getClass().getClassLoader())); + } + } catch (ClassNotFoundException exception) { + throw new RuntimeException("ClassNotFoundException occur when to load the Specified excludeFilter classes.", + exception); + } + return typeFilters; + } + + @SuppressWarnings("unchecked") + private TypeFilter createTypeFilter(String filterType, String expression, @Nullable ClassLoader classLoader) + throws ClassNotFoundException { + + if (this.processPropertyPlaceHolders) { + expression = this.getEnvironment().resolvePlaceholders(expression); + } + + switch (filterType) { + case "annotation": + Class filterAnno = ClassUtils.forName(expression, classLoader); + if (!Annotation.class.isAssignableFrom(filterAnno)) { + throw new IllegalArgumentException( + "Class is not assignable to [" + Annotation.class.getName() + "]: " + expression); + } + return new AnnotationTypeFilter((Class) filterAnno); + case "custom": + Class filterClass = ClassUtils.forName(expression, classLoader); + if (!TypeFilter.class.isAssignableFrom(filterClass)) { + throw new IllegalArgumentException( + "Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression); + } + return (TypeFilter) BeanUtils.instantiateClass(filterClass); + case "assignable": + return new AssignableTypeFilter(ClassUtils.forName(expression, classLoader)); + case "regex": + return new RegexPatternTypeFilter(Pattern.compile(expression)); + case "aspectj": + return new AspectJTypeFilter(expression, classLoader); + default: + throw new IllegalArgumentException("Unsupported filter type: " + filterType); + } } } diff --git a/src/main/java/org/mybatis/spring/mapper/package-info.java b/src/main/java/org/mybatis/spring/mapper/package-info.java index a0757d1812..8b35aac467 100644 --- a/src/main/java/org/mybatis/spring/mapper/package-info.java +++ b/src/main/java/org/mybatis/spring/mapper/package-info.java @@ -1,20 +1,19 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ /** - * Contains classes for automatically building MyBatis mapper proxy classes at - * application startup. + * Contains classes for automatically building MyBatis mapper proxy classes at application startup. */ package org.mybatis.spring.mapper; diff --git a/src/main/java/org/mybatis/spring/package-info.java b/src/main/java/org/mybatis/spring/package-info.java index 86098e1a58..88b29a94f5 100644 --- a/src/main/java/org/mybatis/spring/package-info.java +++ b/src/main/java/org/mybatis/spring/package-info.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ /** * Contains core classes to build the MyBatis integration with Spring3.X. diff --git a/src/main/java/org/mybatis/spring/support/SqlSessionDaoSupport.java b/src/main/java/org/mybatis/spring/support/SqlSessionDaoSupport.java index 4aee710848..ed6573932b 100644 --- a/src/main/java/org/mybatis/spring/support/SqlSessionDaoSupport.java +++ b/src/main/java/org/mybatis/spring/support/SqlSessionDaoSupport.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.support; @@ -23,13 +23,11 @@ import org.springframework.dao.support.DaoSupport; /** - * Convenient super class for MyBatis SqlSession data access objects. - * It gives you access to the template which can then be used to execute SQL methods. - *

- * This class needs a SqlSessionTemplate or a SqlSessionFactory. - * If both are set the SqlSessionFactory will be ignored. + * Convenient super class for MyBatis SqlSession data access objects. It gives you access to the template which can then + * be used to execute SQL methods. *

- * + * This class needs a SqlSessionTemplate or a SqlSessionFactory. If both are set the SqlSessionFactory will be ignored. + * * @author Putthiphong Boonphong * @author Eduardo Macarron * @@ -42,8 +40,11 @@ public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSessionTemplate sqlSessionTemplate; /** - * Set MyBatis SqlSessionFactory to be used by this DAO. - * Will automatically create SqlSessionTemplate for the given SqlSessionFactory. + * Set MyBatis SqlSessionFactory to be used by this DAO. Will automatically create SqlSessionTemplate for the given + * SqlSessionFactory. + * + * @param sqlSessionFactory + * a factory of SqlSession */ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) { @@ -52,12 +53,17 @@ public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { } /** - * Create a SqlSessionTemplate for the given SqlSessionFactory. - * Only invoked if populating the DAO with a SqlSessionFactory reference! - *

Can be overridden in subclasses to provide a SqlSessionTemplate instance - * with different configuration, or a custom SqlSessionTemplate subclass. - * @param sqlSessionFactory the MyBatis SqlSessionFactory to create a SqlSessionTemplate for + * Create a SqlSessionTemplate for the given SqlSessionFactory. Only invoked if populating the DAO with a + * SqlSessionFactory reference! + *

+ * Can be overridden in subclasses to provide a SqlSessionTemplate instance with different configuration, or a custom + * SqlSessionTemplate subclass. + * + * @param sqlSessionFactory + * the MyBatis SqlSessionFactory to create a SqlSessionTemplate for + * * @return the new SqlSessionTemplate instance + * * @see #setSqlSessionFactory */ @SuppressWarnings("WeakerAccess") @@ -67,15 +73,19 @@ protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessi /** * Return the MyBatis SqlSessionFactory used by this DAO. + * + * @return a factory of SqlSession */ public final SqlSessionFactory getSqlSessionFactory() { - return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null); + return this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null; } - /** - * Set the SqlSessionTemplate for this DAO explicitly, - * as an alternative to specifying a SqlSessionFactory. + * Set the SqlSessionTemplate for this DAO explicitly, as an alternative to specifying a SqlSessionFactory. + * + * @param sqlSessionTemplate + * a template of SqlSession + * * @see #setSqlSessionFactory */ public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { @@ -83,9 +93,8 @@ public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { } /** - * Users should use this method to get a SqlSession to call its statement methods - * This is SqlSession is managed by spring. Users should not commit/rollback/close it - * because it will be automatically done. + * Users should use this method to get a SqlSession to call its statement methods This is SqlSession is managed by + * spring. Users should not commit/rollback/close it because it will be automatically done. * * @return Spring managed thread safe SqlSession */ @@ -94,22 +103,19 @@ public SqlSession getSqlSession() { } /** - * Return the SqlSessionTemplate for this DAO, - * pre-initialized with the SessionFactory or set explicitly. - *

Note: The returned SqlSessionTemplate is a shared instance. - * You may introspect its configuration, but not modify the configuration - * (other than from within an {@link #initDao} implementation). - * Consider creating a custom SqlSessionTemplate instance via - * {@code new SqlSessionTemplate(getSqlSessionFactory())}, in which case - * you're allowed to customize the settings on the resulting instance. + * Return the SqlSessionTemplate for this DAO, pre-initialized with the SessionFactory or set explicitly. + *

+ * Note: The returned SqlSessionTemplate is a shared instance. You may introspect its configuration, but not + * modify the configuration (other than from within an {@link #initDao} implementation). Consider creating a custom + * SqlSessionTemplate instance via {@code new SqlSessionTemplate(getSqlSessionFactory())}, in which case you're + * allowed to customize the settings on the resulting instance. + * + * @return a template of SqlSession */ public SqlSessionTemplate getSqlSessionTemplate() { return this.sqlSessionTemplate; } - /** - * {@inheritDoc} - */ @Override protected void checkDaoConfig() { notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"); diff --git a/src/main/java/org/mybatis/spring/support/package-info.java b/src/main/java/org/mybatis/spring/support/package-info.java index 6f00e38163..9418f62203 100644 --- a/src/main/java/org/mybatis/spring/support/package-info.java +++ b/src/main/java/org/mybatis/spring/support/package-info.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ /** * Contains Spring3.X support classes for MyBatis. diff --git a/src/main/java/org/mybatis/spring/transaction/SpringManagedTransaction.java b/src/main/java/org/mybatis/spring/transaction/SpringManagedTransaction.java index 59685df407..4b3eefc683 100644 --- a/src/main/java/org/mybatis/spring/transaction/SpringManagedTransaction.java +++ b/src/main/java/org/mybatis/spring/transaction/SpringManagedTransaction.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2025 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.transaction; @@ -30,12 +30,11 @@ import org.springframework.transaction.support.TransactionSynchronizationManager; /** - * {@code SpringManagedTransaction} handles the lifecycle of a JDBC connection. - * It retrieves a connection from Spring's transaction manager and returns it back to it - * when it is no longer needed. + * {@code SpringManagedTransaction} handles the lifecycle of a JDBC connection. It retrieves a connection from Spring's + * transaction manager and returns it back to it when it is no longer needed. *

- * If Spring's transaction handling is active it will no-op all commit/rollback/close calls - * assuming that the Spring transaction manager will do the job. + * If Spring's transaction handling is active it will no-op all commit/rollback/close calls assuming that the Spring + * transaction manager will do the job. *

* If it is not it will behave like {@code JdbcTransaction}. * @@ -54,14 +53,17 @@ public class SpringManagedTransaction implements Transaction { private boolean autoCommit; + /** + * Instantiates a new spring managed transaction. + * + * @param dataSource + * the data source + */ public SpringManagedTransaction(DataSource dataSource) { notNull(dataSource, "No DataSource specified"); this.dataSource = dataSource; } - /** - * {@inheritDoc} - */ @Override public Connection getConnection() throws SQLException { if (this.connection == null) { @@ -71,29 +73,21 @@ public Connection getConnection() throws SQLException { } /** - * Gets a connection from Spring transaction manager and discovers if this - * {@code Transaction} should manage connection or let it to Spring. + * Gets a connection from Spring transaction manager and discovers if this {@code Transaction} should manage + * connection or let it to Spring. *

- * It also reads autocommit setting because when using Spring Transaction MyBatis - * thinks that autocommit is always false and will always call commit/rollback - * so we need to no-op that calls. + * It also reads autocommit setting because when using Spring Transaction MyBatis thinks that autocommit is always + * false and will always call commit/rollback so we need to no-op that calls. */ private void openConnection() throws SQLException { this.connection = DataSourceUtils.getConnection(this.dataSource); this.autoCommit = this.connection.getAutoCommit(); this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); - LOGGER.debug(() -> - "JDBC Connection [" - + this.connection - + "] will" - + (this.isConnectionTransactional ? " " : " not ") - + "be managed by Spring"); + LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will" + + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring"); } - /** - * {@inheritDoc} - */ @Override public void commit() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { @@ -102,9 +96,6 @@ public void commit() throws SQLException { } } - /** - * {@inheritDoc} - */ @Override public void rollback() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { @@ -113,23 +104,17 @@ public void rollback() throws SQLException { } } - /** - * {@inheritDoc} - */ @Override public void close() throws SQLException { DataSourceUtils.releaseConnection(this.connection, this.dataSource); } - - /** - * {@inheritDoc} - */ + @Override public Integer getTimeout() throws SQLException { - ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); + var holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (holder != null && holder.hasTimeout()) { return holder.getTimeToLiveInSeconds(); - } + } return null; } diff --git a/src/main/java/org/mybatis/spring/transaction/SpringManagedTransactionFactory.java b/src/main/java/org/mybatis/spring/transaction/SpringManagedTransactionFactory.java index 6020558c19..371c574d54 100644 --- a/src/main/java/org/mybatis/spring/transaction/SpringManagedTransactionFactory.java +++ b/src/main/java/org/mybatis/spring/transaction/SpringManagedTransactionFactory.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.transaction; @@ -31,25 +31,16 @@ */ public class SpringManagedTransactionFactory implements TransactionFactory { - /** - * {@inheritDoc} - */ @Override public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) { return new SpringManagedTransaction(dataSource); } - /** - * {@inheritDoc} - */ @Override public Transaction newTransaction(Connection conn) { throw new UnsupportedOperationException("New Spring transactions require a DataSource"); } - /** - * {@inheritDoc} - */ @Override public void setProperties(Properties props) { // not needed in this version diff --git a/src/main/java/org/mybatis/spring/transaction/package-info.java b/src/main/java/org/mybatis/spring/transaction/package-info.java index 1581b51db0..d4013e013c 100644 --- a/src/main/java/org/mybatis/spring/transaction/package-info.java +++ b/src/main/java/org/mybatis/spring/transaction/package-info.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ /** * Contains core classes to manage MyBatis transactions in Spring3.X. diff --git a/src/main/java/META-INF/spring.handlers b/src/main/resources/META-INF/spring.handlers similarity index 75% rename from src/main/java/META-INF/spring.handlers rename to src/main/resources/META-INF/spring.handlers index ed66932b28..7ff1099c4a 100644 --- a/src/main/java/META-INF/spring.handlers +++ b/src/main/resources/META-INF/spring.handlers @@ -1 +1 @@ -http\://mybatis.org/schema/mybatis-spring=org.mybatis.spring.config.NamespaceHandler \ No newline at end of file +http\://mybatis.org/schema/mybatis-spring=org.mybatis.spring.config.NamespaceHandler diff --git a/src/main/java/META-INF/spring.schemas b/src/main/resources/META-INF/spring.schemas similarity index 100% rename from src/main/java/META-INF/spring.schemas rename to src/main/resources/META-INF/spring.schemas diff --git a/src/main/resources/org/mybatis/spring/config/mybatis-spring.xsd b/src/main/resources/org/mybatis/spring/config/mybatis-spring.xsd new file mode 100644 index 0000000000..196a6fe35b --- /dev/null +++ b/src/main/resources/org/mybatis/spring/config/mybatis-spring.xsd @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/site/es/markdown/README.md b/src/site/es/markdown/README.md new file mode 100644 index 0000000000..a3abaf2e40 --- /dev/null +++ b/src/site/es/markdown/README.md @@ -0,0 +1,18 @@ +# Tabla de contenido + +Esta página es para representar el índice en GitHub. + +> **NOTE:** +> +> Dado que el destino del enlace se especifica asumiendo que se convierte a html con maven-site-plugin, hay un ancla que se rompe en el renderizado en GitHub. + +* [Introducción](./index.md) +* [Primeros pasos](./getting-started.md) +* [SqlSessionFactoryBean](./factorybean.md) +* [Transactions](./transactions.md) +* [Uso de SqlSession](./sqlsession.md) +* [Inyección de Mappers](./mappers.md) +* [Spring Boot](./boot.md) +* [Uso del API de MyBatis](./using-api.md) +* [Spring Batch](./batch.md) +* [Código de ejemplo](./sample.md) diff --git a/src/site/es/markdown/batch.md b/src/site/es/markdown/batch.md new file mode 100644 index 0000000000..76ba12b0c7 --- /dev/null +++ b/src/site/es/markdown/batch.md @@ -0,0 +1,351 @@ + +# Spring Batch + +Desde la versión 1.1.0 MyBatis-Spring proporciona dos beans para construir aplicaciones Spring Batch: `MyBatisPagingItemReader` y `MyBatisCursorItemReader` y `MyBatisBatchItemWriter`. +Also, As of version 2.0.0 provides three builder classes for supporting the Java Configuration: the `MyBatisPagingItemReaderBuilder`, the `MyBatisCursorItemReaderBuilder` and the `MyBatisBatchItemWriterBuilder`. + +NOTA +Esta sección se refiere a [Spring Batch](http://static.springsource.org/spring-batch/) y no a sesiones batch de MyBatis. Para obtener información sobre las sesiones batch ve a la sección [Usnado un SqlSession](sqlsession.html). + +## MyBatisPagingItemReader + +Este bean es un `ItemReader` que lee registros de una base de datos usando paginación. + +Ejecuta la sentencia especificada mediante la propiedad `setQueryId` para obtener los datos. La sentencia se ejecuta usando peticiones paginadas del tamaño indicando en la propiedad `setPageSize`. +Al llamar al método `read()` éste devuelve el objeto que corresponde a la posición actual y solicita más páginas si es necesario. + +El reader recibe algunos parametros estándar y la SQL deberá hacer uso de algunos de ellos para construir un resultset del tamaño requerido. Los parametros son: + +* `_page`: el número de página a leer (comenzando en 0) +* `_pagesize`: el tamaño de la página, es decir, el número de filas a devolver +* `_skiprows`: el producto de `_page` por `_pagesize` + +Se pueden mapear en un statement de tipo select de la siguiente forma: + +```xml + +``` + +A continuación se muestra un ejemplo de configuración: + +```xml + + + + +``` +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisPagingItemReader reader() { + return new MyBatisPagingItemReaderBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") + .build(); + } +} +``` + +**Veamos un ejemplo más complejo:** + +```xml + +``` +```xml + + + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @StepScope + @Bean + public MyBatisPagingItemReader dateBasedCriteriaReader( + @Value("#{@datesParameters}") Map datesParameters) throws Exception { + return new MyBatisPagingItemReaderBuilder() + .sqlSessionFactory(batchReadingSessionFactory()) + .queryId("com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot") + .parameterValues(datesParameters) + .pageSize(200) + .build(); + } + + @StepScope + @Bean + public Map datesParameters( + @Value("#{jobExecutionContext['EXTRACTION_START_DATE']}") LocalDate yesterday, + @Value("#{jobExecutionContext['TODAY_DATE']}") LocalDate today, + @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}") LocalDate firstDayOfTheMonth, + @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}") LocalDate firstDayOfThePreviousMonth) { + Map map = new HashMap<>(); + map.put("yesterday", yesterday); + map.put("today", today); + map.put("first_day_of_the_month", firstDayOfTheMonth); + map.put("first_day_of_the_previous_month", firstDayOfThePreviousMonth); + return map; + } +} +``` + +El ejemplo anterior hace uso de tres cosas distintas: + +* `sqlSessionFactory`: Puedes tu propio sessionFactory, podría ser útil si quires leer de varias bases de datos. +* `queryId`: Si el código accede a varias tablas, y tienes distintas sentencias de consulta, puede ser interesante usar ficheros de mapeo distintos con namespaces distintos. + En este caso, al referirte a la query, no olvides incluir el namespace correspondiente. +* `parameterValues`: Puedes pasar parametros adicionales en este mapa, el ejemplo de arriba usa un mapa que se construye usando una expresion SpEL y obteniendo valores del jobExecutionContext. + Las claves del mapa puede usarse en el fichero mapper de MyBatis (por ejemplo: *yesterday* se puede usar como `#{yesterday,jdbcType=TIMESTAMP}`). + Observa que el mapa y el reader se consutruyen en un solo `step` para que sea posible usar la expresión SpEL con el `jobExecutionContext`. + Adicionalmente si los type handlers de MyBatis están configurados correctamente puedes pasar instancias personalizadas como los parametros del ejemplo que son fechas JodaTime. +* `pageSize`: Si le flujo batch está configurado con un tamaño de bloque (chunk size), es importante pasar esta información al reader, y eso se hace mediante esta propiedad. + +## MyBatisCursorItemReader + +Este bean es un `ItemReader` que lee registros de la base de datos usando un cursor. + +NOTA +Para usar este bean necesitas al menos MyBatis 3.4.0 o superior. + +Ejecuta la sentencia especificada mediante la propiedad `setQueryId` para obtener los datos usando el método `selectCursor()`. +Al llamar al método `read()` se devolverá el siguiente elemento del cursor hasta que no quede ninguno por devolver. + +El reader usa una conexión separada para que la sentencia no participe en ninguna transacción creada como parte del proceso del step. + +Cuando se usar un cursor puedes usar una sentencia convencional: + +```xml + +``` + +A continuación se muestra un ejemplo de configuración: + +```xml + + + + +``` +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisCursorItemReader reader() { + return new MyBatisCursorItemReaderBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") + .build(); + } +} +``` + +## MyBatisBatchItemWriter + +Es un `ItemWriter` que usa las capacidades de batch de `SqlSessionTemplate` para ejecutar sentencias batch para todos los elementos (items) proporcionados. +El `SqlSessionFactory` debe configurarse con un executor de tipo `BATCH`. + +Ejecuta la sentencia indicada en la propiedad `statementId` cuando se invoca a `write()`. Se supone que `write()` se invoca dentro de una transacción. + +A continuación se muestra un ejemplo de configuración: + +```xml + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisBatchItemWriter writer() { + return new MyBatisBatchItemWriterBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .statementId("com.my.name.space.batch.EmployeeMapper.updateEmployee") + .build(); + } +} +``` + +**Converting a item that read using ItemReader to an any parameter object:** + +By default behavior, the `MyBatisBatchItemWriter` passes a item that read using `ItemReader` (or convert by `ItemProcessor`) to the MyBatis(`SqlSession#update()`) as the parameter object. +If you want to customize a parameter object that passes to the MyBatis, you can realize to use the `itemToParameterConverter` option. For example using `itemToParameterConverter` option, you can passes any objects other than the item object to the MyBatis. +Follows below a sample: + +At first, you create a custom converter class (or factory method). The following sample uses a factory method. + +```java +public class ItemToParameterMapConverters { + public static Converter> createItemToParameterMapConverter(String operationBy, LocalDateTime operationAt) { + return item -> { + Map parameter = new HashMap<>(); + parameter.put("item", item); + parameter.put("operationBy", operationBy); + parameter.put("operationAt", operationAt); + return parameter; + }; + } +} +``` + +At next, you write a sql mapping. + +```xml + +``` + +At last, you configure the `MyBatisBatchItemWriter`. + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisBatchItemWriter writer() throws Exception { + return new MyBatisBatchItemWriterBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .statementId("org.mybatis.spring.sample.mapper.PersonMapper.createPerson") + .itemToParameterConverter(createItemToParameterMapConverter("batch_java_config_user", LocalDateTime.now())) + .build(); + } +} +``` + +```xml + + + + + + + + + + +``` + +**Escribiendo en distintas tablas usando composite writers (con algunos condicionantes):** + +Esta técnica sólo puede usarse con MyBatis 3.2+, por que había un [error](http://code.google.com/p/mybatis/issues/detail?id=741) en las versiones anteriores que hacían que el writer funcionara de forma incorrecta. + +Si el batch necesita escribir datos complejos, como registros con asociaciones, o en distintas bases de datos, entonces es necesario sortear el problema de que los insert statements solo pueden escribir en una tabla. +Para conseguir esto debes preparar un Item para que sea escrito por el writer. Sin embargo, dependiendo de las circunstancias puede ser interesante usar la siguiente técnica. +El truco siguiente funciona con items con asociaciones simples o con tablas no relacionadas. + +Elabora el `item` de forma que *contenta* todos los resgistros distintos. Supon que para cada `item` hay una *Interaction* que tiene una asociación *InteractionMetadata* y dos filas no asociadas *VisitorInteraction* and *CustomerInteraction*. +El objeto contenedor será de la siguiente forma: + +```java +public class InteractionRecordToWriteInMultipleTables { + private final VisitorInteraction visitorInteraction; + private final CustomerInteraction customerInteraction; + private final Interaction interaction; + // ... +} +``` +```java +public class Interaction { + private final InteractionMetadata interactionMetadata; +} +``` + +Entonces en la configuración de spring habrá un `CompositeItemWriter` que usará writers delegados configurados especificamente para cada tipo de registro. +Fijate que el *InteractionMetadata* es una asociacióin en el ejemplo por lo que debe ser escrita antes para que la Interaction pueda recibir la clave generada. + +```xml + + + + + + + + + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public CompositeItemWriter interactionsItemWriter() { + CompositeItemWriter compositeItemWriter = new CompositeItemWriter(); + List> writers = new ArrayList<>(4); + writers.add(visitorInteractionsWriter()); + writers.add(customerInteractionsWriter()); + writers.add(interactionMetadataWriter()); + writers.add(interactionWriter()); + compositeItemWriter.setDelegates(writers); + return compositeItemWriter; + } +} +``` + +Cada writer delegados se configura como sea necesario, por ejemplo para *Interaction* y *InteractionMetadata*: + +```xml + +``` +```xml + +``` + +Al igual que con el reader el `statementId` puede hacer referencia al statement con un namespace como prefijo. + +Ahora es debe elaborarse el fichero de mapeo para cada tipo de registro, de la siguiente forma: + +```xml + + + +``` +```xml + + + +``` + +Lo que sucede es que primeramente se llamará a `insertInteractionMetadata`, y la sentencia de update está configurada para devolver las claves autogeneradas (`keyProperty` y `keyColumn`). +Una vez que el `InteractionMetadata` se ha almacenado por esta sentencia se puede ejecutar la siguiente para escribir el objeto padre `Interaction` mediante `insertInteraction`. + +***Sin embargo, ten en cuenta que los drivers JDBC se comportan distinto en este aspecto. A la fecha en la que se escribe esto +el driver H2 1.3.168 solo devuelve el último ID incluso en modo BATCH (see `org.h2.jdbc.JdbcStatement#getGeneratedKeys`), +mientras que el driver JDBC de MySQL se comporta como es de esperar y devuelve todos los IDs.*** diff --git a/src/site/es/markdown/boot.md b/src/site/es/markdown/boot.md new file mode 100644 index 0000000000..678db5d753 --- /dev/null +++ b/src/site/es/markdown/boot.md @@ -0,0 +1,4 @@ + +# Using Spring Boot + +Please see the [MyBatis Spring-boot-starter](http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure) sub project docs for details. diff --git a/src/site/es/markdown/factorybean.md b/src/site/es/markdown/factorybean.md new file mode 100644 index 0000000000..cdb5d6e57a --- /dev/null +++ b/src/site/es/markdown/factorybean.md @@ -0,0 +1,181 @@ + +# SqlSessionFactoryBean + +En MyBatis una `SqlSessionFactory` se crea mediante la clase `SqlSessionFactoryBuilder`. En MyBatis-Spring se usa la clase `SqlSessionFactoryBean` en su lugar. + +## Configuración + +Para crear un factory bean, pon lo siguiente en el fichero XML de configuración de Spring: + +```xml + + + +``` + +La clase `SqlSessionFactoryBean` implementa el interfaz `FactoryBean` (see [the Spring documentation(Core Technologies -Customizing instantiation logic with a FactoryBean-](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-extension-factorybean)). +Lo cual significa que el bean que crea Spring en última instancia **no** es un `SqlSessionFactoryBean` en si mismo, sino el objeto que la factoria devuelve como resultado de la llamada al método `getObject()`. +En este caso, Spring creará un bean `SqlSessionFactory` durante el arranque de la aplicación y lo guardará bajo el nombre `sqlSessionFactory`. En Java, el código equivalente sería: + +```java +@Configuration +public class MyBatisConfig { + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + return factoryBean.getObject(); + } +} +``` + +Normalmente no necesitarás utilizar directamente un `SqlSessionFactoryBean` o su correspondiente `SqlSessionFactory` directly. +En su lugar, la factoría se utilizará para ser inyectada en `MapperFactoryBean`s o DAOs que extiendan de `SqlSessionDaoSupport`. + +## Properties + +La clase `SqlSessionFactory` solo tiene una propiedad obligatoria, un `DataSource`. +Puede ser cualquier `DataSource` y se puede configurar como cualquier otra conexión a base de daots de Spring. + +Una propiedad muy común es la `configLocation` que se utiliza para indicar la localización del fichero de configuración XML de MyBatis. +Normalmente solo es necesario dicho fichero si se requiere cambiar los valores por defecto de las secciones `` o ``. + +Es importante saber que este fichero de configuración **no** tiene por qué ser un fichero de configuración de MyBatis completo. +Concretamente, los environments, dataSources y transactionManagers serán **ignorados**. +`SqlSessionFactoryBean` crea su propio `Environment` de MyBatis con los valores configurados tal y como se requieren. + +Otro motivo para necesitar un fichero de configuración es que los ficheros de mapeo XML no estén en el mismo lugar del classpath que los mapper interfaces. +En este caso hay dos opciones. La primera es especificar manualmente el classpath de los ficheros XML usando la sección `` del fichero de configuración de MyBatis. +La segunda opción es usar la propiedad `mapperLocations` del factory bean. + +La propiedad `mapperLocations` recibe una lista de localizaciones de recursos. Se utiliza para indicar la ubicación de los ficheros de mapeo XML de MyBatis. +El valor puede contener un patron tipo Ant para cargar todos los ficheros de un directorio o buscar de forma recursiva en todos los paths desde una localización base. Por ejemplo: + +```xml + + + + +``` + +In Java, the equivalent code would be: + +```java +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sample/config/mappers/**/*.xml")); + return factoryBean.getObject(); +} +``` + +Esto cargaría todos los ficheros de mapeo XML en el paquete sample.config.mappers y sus subpaquetes. + +Otra propiedad que puede ser necesaria en un entorno con transacciones gestionadas por contenedor es la `transactionFactoryClass`. Lee la sección de transacciones para obtener más detalles. + +En caso de usar la característica multi-db necesitarás informar la propiedad `databaseIdProvider` de la siguiente forma: + +```xml + + + + sqlserver + db2 + oracle + mysql + + + +``` +```xml + + + + + +``` + +In Java, the equivalent code would be: + +```java +@Bean +public VendorDatabaseIdProvider databaseIdProvider() { + VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); + Properties properties = new Properties(); + properties.setProperty("SQL Server", "sqlserver"); + properties.setProperty("DB2", "db2"); + properties.setProperty("Oracle", "oracle"); + properties.setProperty("MySQL", "mysql"); + databaseIdProvider.setProperties(properties); + return databaseIdProvider; +} + +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource, DatabaseIdProvider databaseIdProvider) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + factoryBean.setDatabaseIdProvider(databaseIdProvider); + return factoryBean.getObject(); +} +``` + +NOTE +Since 1.3.0, `configuration` property has been added. It can be specified a `Configuration` instance directly without MyBatis XML configuration file. +For example: + +```xml + + + + + + + + +``` + +In Java, the equivalent code would be: + +```java +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + + return factoryBean.getObject(); +} +``` + +## Java Configuration Example + +Here is a complete example of a configuration class that combines the properties described above. + +```java +@Configuration +public class MyBatisConfig { + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + + // Setting mapper locations + factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sample/config/mappers/**/*.xml")); + + // Setting configuration property + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + + return factoryBean.getObject(); + } +} +``` + +NOTE +This configuration class must be located within a package scanned by the Spring container (e.g., within the main application package). The class name itself (e.g., `MyBatisConfig`) is arbitrary; only the `@Configuration` annotation is required. diff --git a/src/site/es/markdown/getting-started.md b/src/site/es/markdown/getting-started.md new file mode 100644 index 0000000000..fdaedcaa30 --- /dev/null +++ b/src/site/es/markdown/getting-started.md @@ -0,0 +1,99 @@ + +# Primeros pasos + +Este capítulo te mostrará en pocos pasos cómo instalar y configurar MyBatis-Spring y cómo construir +una pequeña aplicación transaccional. + +## Instalación + +Para usar el módulo MyBatis-Spring, debes incluir el fichero `mybatis-spring-${project.version}.jar` y sus dependencias en el classpath. + +Si usas Maven simplemente añade la siguiente dependencia a tu pom.xml: + +```xml + + org.mybatis + mybatis-spring + ${project.version} + +``` + +## Configuración rápida + +Para usar MyBatis con Spring necesitas definir al menos dos cosas en tu contexto Spring: una `SqlSessionFactory` y al menos un mapper interface. + +En MyBatis-Spring se usa un `SqlSessionFactoryBean` para crear una `SqlSessionFactory`. Para configurar la factory bean pon lo siguiente en tu fichero de configuración de Spring: + +```xml + + + +``` + +```java +@Configuration +public class MyBatisConfig { + @Bean + public SqlSessionFactory sqlSessionFactory() throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource()); + return factoryBean.getObject(); + } +} +``` + +Observa que la `SqlSessionFactory` requiere un `DataSource`. Éste puede ser cualquier `DataSource` y debe configurarse como cualquier otra conexión a base de datos de Spring. + +Asumamos que tienes un mapper interface definido de la siguiente forma: + +```java +public interface UserMapper { + @Select("SELECT * FROM users WHERE id = #{userId}") + User getUser(@Param("userId") String userId); +} +``` + +Este interface se añade a Spring usando un `MapperFactoryBean` de la siguiente forma: + +```xml + + + + +``` + +Observa que la clase del mapper indicada **debe** ser un interface, no una implementación. En este ejemplo se usan anotaciones para especificar la SQL, pero también es posible usar un fichero de mapeo XML. + +Una vez configurado, puedes inyectar mappers directamente en tus beans de servicio/negocio de la misma forma que inyectarías cualquier otro bean en Spring. +La clase `MapperFactoryBean` se encargará de obtener una `SqlSession` y de cerrarla. Si hay una transación Spring en curso, la sesión se comitará o se hará rollback cuando la transacción finalice. +Finalmente, cualquier excepción será traducida a una excepión `DataAccessException`s de Spring. + +If you use the Java Configuration: + +```java +@Configuration +public class MyBatisConfig { + @Bean + public UserMapper userMapper() throws Exception { + SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory()); + return sqlSessionTemplate.getMapper(UserMapper.class); + } +} +``` + +Invocar a MyBatis es sólo una línea de código: + +```java +public class FooServiceImpl implements FooService { + + private final UserMapper userMapper; + + public FooServiceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public User doSomeBusinessStuff(String userId) { + return this.userMapper.getUser(userId); + } +} +``` diff --git a/src/site/es/markdown/index.md b/src/site/es/markdown/index.md new file mode 100644 index 0000000000..919be84a0b --- /dev/null +++ b/src/site/es/markdown/index.md @@ -0,0 +1,58 @@ + +# Introduction + +## ¿Qué es MyBatis-Spring? + +MyBatis-Spring permite integrar MyBatis con Spring. Esta librería permite que MyBatis participe en trasacciones Spring, +se encarga de constuir mappers y `SqlSession`s e inyectarlos en otros beans, traduce excepciones de MyBatis en excepcines `DataAccessException`s de Spring y finalmente, permite construir aplicaciones libres de dependencias de MyBatis, Spring y MyBatis-Spring. + +## Motivación + +Spring version 2 sólo soporta iBATIS version 2. See hizo un intento de incluir el soporte de MyBatis 3 en Spring 3 (ver el [issue Jira](https://jira.springsource.org/browse/SPR-5991)). +Pero desafortunadamente, el desarrollo de Spring 3 finalizó antes de que MyBatis 3 fuera liberado oficialmente. +Dado que el equipo de Spring no quería liberar una versión basada en un producto no terminado el soporte de oficial tenía que esperar. +Dado el interés de la comunidad en el soporte de MyBatis, la comunidad de MyBatis decidió que era el momento de unificar a los colaboradores interesados y proporcionar la integración con Spring como un sub-projecto de MyBatis en su lugar. + +## Requisitos + +Antes de comenzar con MyBatis-Spring, es muy importante que estés familiarizado con la terminología tanto de MyBatis como de Spring. +Este documento no pretende proporcionar información de configuración básica de MyBatis o Spring. + +MyBatis-Spring requires following versions: + +| MyBatis-Spring | MyBatis | Spring Framework | Spring Batch | Java | +|----------------| --- |------------------|--------------| --- | +| **4.0** | 3.5+ | 7.0+ | 6.0+ | Java 17+ | +| **3.0** | 3.5+ | 6.x | 5.x | Java 17+ | +| **2.1** | 3.5+ | 5.x | 4.x | Java 8+ | +| ~~**2.0**~~ | ~~3.5+~~ | ~~5.x~~ | ~~4.x~~ | ~~Java 8+~~ | +| ~~**1.3**~~ | ~~3.4+~~ | ~~3.2.2+~~ | ~~2.1+~~ | ~~Java 6+~~ | + +## Agradecimientos + +Queremos agradecer a la todos los que han hecho de este proyecto una realidad (en orden alfabético): +Eduardo Macarron, Hunter Presnall y Putthiphong Boonphong por la codificación, pruebas y documentación; +a Andrius Juozapaitis, Giovanni Cuccu, Mike Lanyon, Raj Nagappan y Tomas Pinos por sus contribuciones; +y a Simone Tripodi por encontrarlos a todos y traerlos al proyecto MyBatis ;) Sin ellos este proyecto no existiría. + +## Colabora en mejorar esta documentación... + +Si ves que hay alguna carencia en esta documentación, o que falta alguna característica por documentar, te animamos a que lo investigues y la documentes tu mismo! + +Las fuentes de este manual están disponibles en formato markdown en el [Git del proyecto](https://github.com/mybatis/mybatis-3/tree/master/src/site). Haz un fork, cambialas y envía un pull request. + +Eres el mejor candidato para documentar porque los lectores de esta documentación son gente como tú! + +## Translations + +Users can read about MyBatis-Spring in the following translations: + +

+ +Do you want to read about MyBatis in your own native language? Fill an issue providing patches with your mother tongue documentation! diff --git a/src/site/es/markdown/mappers.md b/src/site/es/markdown/mappers.md new file mode 100644 index 0000000000..01e3bce019 --- /dev/null +++ b/src/site/es/markdown/mappers.md @@ -0,0 +1,185 @@ + +# Inyectar mappers + +En lugar de codificar DAOs (data access objects) manualmente usando la clase `SqlSessionDaoSupport` o `SqlSessionTemplate`, Mybatis-Spring puede crear un mapper thread-safe que puedes inyectar directamente en otros beans. + +```xml + + + +``` + + Una vez inyectado, el mapper está listo para se usado en la lógica de aplicación: + +```java +public class FooServiceImpl implements FooService { + + private final UserMapper userMapper; + + public FooServiceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public User doSomeBusinessStuff(String userId) { + return this.userMapper.getUser(userId); + } +} +``` + +Observa que no se usa la `SqlSession` ni ninguna otra referencia a MyBatis en este código. No es necesario ni siquiera crear o cerrar la sesión, MyBatis-Spring se encarga de ello. + + +## Registrar un mapper + +La forma de registrar un mapper varía según si quieres usar la configuración XML clásica o la nueva Java Config de Spring 3.0+ (También conocida como `@Configuration`). + +### Con confiugración XML + +Un mapper se registra en Spring incluyendo un `MapperFactoryBean` en tu fichero de configuración XML, de la siguiente forma: + +```xml + + + + +``` + +Si el mapper UserMapper tiene un fichero XML de mapeo asociado el `MapperFactoryBean` lo cargará automáticamente. +Por lo tanto no es necesario especificar dicho mapper en el fichero de configuración de MyBatis a no ser que los ficheros XML estén en una lugar distinto del classpath. +Ver la sección de `SqlSessionFactoryBean` y la propiedad [`configLocation`](factorybean.html) para más información. + +El `MapperFactoryBean` requiere o un `SqlSessionFactory` o un `SqlSessionTemplate`. +Ambos se pueden informar usando sendas propiedades `sqlSessionFactory` y `sqlSessionTemplate`. +Si ambas propiedades han sdo informadas la `SqlSessionFactory` se ignora. +Dado que un `SqlSessionTemplate` debe tener un session factory dicho factory se usará por el `MapperFactoryBean`. + +### Con Java Config + +```java +@Configuration +public class MyBatisConfig { + @Bean + public MapperFactoryBean userMapper() throws Exception { + MapperFactoryBean factoryBean = new MapperFactoryBean<>(UserMapper.class); + factoryBean.setSqlSessionFactory(sqlSessionFactory()); + return factoryBean; + } +} +``` + + +## Escanear mappers + +No es necesario registrar los mappers uno por uno en el fichero XML de Spring. En lugar de esto, puede dejar que MyBatis-Spring los busque en tu classpath. + +Hay tres formas distintas de hacerlo: + +* Usando el elemneto ``. +* Usando la anotación `@MapperScan` +* Usando un fichero clásico XML de configuración de Spring y añadiendo el bean `MapperScannerConfigurer` + +Tango `` como `@MapperScan` son características añadidas en MyBatis-Spring 1.2.0. `@MapperScan` requiere Spring 3.1+. + +Since 2.0.2, mapper scanning feature support a option (`lazy-initialization`) that control lazy initialization enabled/disabled of mapper bean. +The motivation for adding this option is supporting a lazy initialization control feature supported by Spring Boot 2.2. +The default of this option is `false` (= not use lazy initialization). +If developer want to use lazy initialization for mapper bean, it should be set to the `true` expressly. + +IMPORTANT +If use the lazy initialization feature, the developer need to understand following limitations. +If any of following conditions are matches, usually the lazy initialization feature cannot use on your application. + +* When refers to the statement of **other mapper** using ``(`@One`) and ``(`@Many`) +* When includes to the fragment of **other mapper** using `` +* When refers to the cache of **other mapper** using ``(`@CacheNamespaceRef`) +* When refers to the result mapping of **other mapper** using `]]> - -

A continuación se muestra un ejemplo de configuración:

- - - - -]]> - - reader() { - return new MyBatisPagingItemReaderBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") - .build(); -}]]> - -

Veamos un ejemplo más complejo:

- - - - - - - - -]]> - - dateBasedCriteriaReader( - @Value("#{@datesParameters}") Map datesParameters) throws Exception { - return new MyBatisPagingItemReaderBuilder() - .sqlSessionFactory(batchReadingSessionFactory()) - .queryId("com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot") - .parameterValues(datesParameters) - .pageSize(200) - .build(); -} - -@StepScope -@Bean -public Map datesParameters( - @Value("#{jobExecutionContext['EXTRACTION_START_DATE']}") LocalDate yesterday, - @Value("#{jobExecutionContext['TODAY_DATE']}") LocalDate today, - @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}") LocalDate firstDayOfTheMonth, - @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}") LocalDate firstDayOfThePreviousMonth) { - Map map = new HashMap<>(); - map.put("yesterday", yesterday); - map.put("today", today); - map.put("first_day_of_the_month", firstDayOfTheMonth); - map.put("first_day_of_the_previous_month", firstDayOfThePreviousMonth); - return map; -}]]> - -

- El ejemplo anterior hace uso de tres cosas distintas: -

- -
    -
  • sqlSessionFactory: Puedes tu propio sessionFactory, podría ser útil si quires leer de - varias bases de datos.
  • -
  • queryId: Si el código accede a varias tablas, y tienes distintas sentencias de consulta, - puede ser interesante usar ficheros de mapeo distintos con namespaces distintos. - En este caso, al referirte a la query, no olvides incluir el namespace correspondiente.
  • -
  • parameterValues: Puedes pasar parametros adicionales en este mapa, el ejemplo de arriba - usa un mapa que se construye usando una expresion SpEL y obteniendo valores del jobExecutionContext. - Las claves del mapa puede usarse en el fichero mapper de MyBatis (por ejemplo: - yesterday se puede usar como #{yesterday,jdbcType=TIMESTAMP}). - Observa que el mapa y el reader se consutruyen en un solo step para que sea posible usar la expresión - SpEL con el jobExecutionContext. Adicionalmente si los type handlers de MyBatis - están configurados correctamente puedes pasar instancias personalizadas como los parametros del ejemplo que son - fechas JodaTime.
  • -
  • pageSize: Si le flujo batch está configurado con un tamaño de bloque (chunk size), - es importante pasar esta información al reader, y eso se hace mediante esta propiedad.
  • -
- - - - -

- Este bean es un ItemReader que lee registros de la base de datos usando un cursor. -

- -

- NOTA Para usar este bean necesitas al menos MyBatis 3.4.0 o superior. -

- -

- Ejecuta la sentencia especificada mediante la propiedad setQueryId para obtener los datos - usando el método selectCursor(). - Al llamar al método read() se devolverá el siguiente elemento del cursor - hasta que no quede ninguno por devolver. -

- -

- El reader usa una conexión separada para que la sentencia no participe en ninguna transacción creada como parte - del proceso del step. -

- -

Cuando se usar un cursor puedes usar una sentencia convencional:

- - SELECT id, name, job FROM employees ORDER BY id ASC -]]> - -

A continuación se muestra un ejemplo de configuración:

- - - - -]]> - - reader() { - return new MyBatisCursorItemReaderBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") - .build(); -}]]> - -
- - - -

- Es un ItemWriter que usa las capacidades de batch de SqlSessionTemplate para - ejecutar sentencias batch para todos los elementos (items) proporcionados. - El SqlSessionFactory debe configurarse con un executor de tipo BATCH. -

- -

- Ejecuta la sentencia indicada en la propiedad statementId cuando se invoca a write(). - Se supone que write() se invoca dentro de una transacción.
-

- -

A continuación se muestra un ejemplo de configuración:

- - - - -]]> - - writer() { - return new MyBatisBatchItemWriterBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .statementId("com.my.name.space.batch.EmployeeMapper.updateEmployee") - .build(); -}]]> - -

Escribiendo en distintas tablas usando composite writers (con algunos condicionantes):

- -

Esta técnica sólo puede usarse con MyBatis 3.2+, por que había un - error - en las versiones anteriores que hacían que el writer funcionara de forma incorrecta. -

- -

Si el batch necesita escribir datos complejos, como registros con asociaciones, o en distintas bases de datos, - entonces es necesario sortear el problema de que los insert statements solo pueden escribir en una tabla. - Para conseguir esto debes preparar un Item para que sea escrito por el writer. Sin embargo, - dependiendo de las circunstancias puede ser interesante usar la siguiente técnica. - El truco siguiente funciona con items con asociaciones simples o con tablas no relacionadas. -

- -

- Elabora el item de forma que contenta todos los resgistros distintos. - Supon que para cada item hay una Interaction que tiene una asociación - InteractionMetadata y dos filas no asociadas VisitorInteraction and - CustomerInteraction. El objeto contenedor será de la siguiente forma: -

- - - -

- Entonces en la configuración de spring habrá un CompositeItemWriter que usará writers - delegados configurados especificamente para cada tipo de registro. Fijate que el InteractionMetadata - es una asociacióin en el ejemplo por lo que debe ser escrita antes para que la Interaction pueda recibir la clave - generada. -

- - - - - - - - - - - - -]]> - - interactionsItemWriter() { - CompositeItemWriter compositeItemWriter = new CompositeItemWriter(); - List> writers = new ArrayList<>(4); - writers.add(visitorInteractionsWriter()); - writers.add(customerInteractionsWriter()); - writers.add(interactionMetadataWriter()); - writers.add(interactionWriter()); - compositeItemWriter.setDelegates(writers); - return compositeItemWriter; -}]]> - -

Cada writer delegados se configura como sea necesario, por ejemplo para Interaction y - InteractionMetadata: -

- - -]]> - -

Al igual que con el reader el statementId puede hacer referencia al statement con un namespace como prefijo.

- -

Ahora es debe elaborarse el fichero de mapeo para cada tipo de registro, de la siguiente forma:

- - - - - - -]]> - -

- Lo que sucede es que primeramente se llamará a insertInteractionMetadata, y la sentencia de update - está configurada para devolver las claves autogeneradas (keyProperty y keyColumn). - Una vez que el InteractionMetadata se ha almacenado por esta sentencia se puede ejecutar la siguiente para - escribir el objeto padre Interaction mediante insertInteraction. -

- -

- Sin embargo, ten en cuenta que los drivers JDBC se comportan distinto en este aspecto. A la fecha en la que se escribe esto - el driver H2 1.3.168 solo devuelve el último ID incluso en modo BATCH (see org.h2.jdbc.JdbcStatement#getGeneratedKeys), - mientras que el driver JDBC de MySQL se comporta como es de esperar y devuelve todos los IDs. -

-
- - - \ No newline at end of file diff --git a/src/site/es/xdoc/boot.xml b/src/site/es/xdoc/boot.xml deleted file mode 100644 index 76432ac4d4..0000000000 --- a/src/site/es/xdoc/boot.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - MyBatis-Spring | Spring Boot - Eduardo Macarron - - - -
-

- Please see the MyBatis Spring-boot-stater - subproject docs for details. -

-
- -
diff --git a/src/site/es/xdoc/factorybean.xml b/src/site/es/xdoc/factorybean.xml deleted file mode 100644 index 60e1b0db05..0000000000 --- a/src/site/es/xdoc/factorybean.xml +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - MyBatis-Spring | SqlSessionFactoryBean - Hunter Presnall - Eduardo Macarron - - - -
-

- En MyBatis una SqlSessionFactory se crea mediante la clase - SqlSessionFactoryBuilder. En MyBatis-Spring se usa la clase - SqlSessionFactoryBean en su lugar. -

- - -

- Para crear un factory bean, pon lo siguiente en el fichero XML de configuración de Spring: -

- - -]]> -

- La clase SqlSessionFactoryBean implementa el interfaz FactoryBean - (see the Spring documentation(Core Technologies -Customizing instantiation logic with a FactoryBean-)). - Lo cual significa que el bean que crea Spring - en última instancia no es un SqlSessionFactoryBean en si mismo, sino el - objeto que la factoria devuelve como resultado de la llamada al método - getObject(). En este caso, Spring creará un bean - SqlSessionFactory durante el arranque de la aplicación y lo guardará bajo el nombre - sqlSessionFactory. En Java, el código equivalente sería: -

- - - -

- Normalmente no necesitarás utilizar directamente un SqlSessionFactoryBean - o su correspondiente SqlSessionFactory directly. - En su lugar, la factoría se utilizará para ser inyectada en MapperFactoryBeans o - DAOs que extiendan de SqlSessionDaoSupport. -

-
- - - -

- La clase SqlSessionFactory solo tiene una propiedad obligatoria, un DataSource. - Puede ser cualquier DataSource y se puede configurar como cualquier otra conexión a base de daots de Spring. -

- -

- Una propiedad muy común es la configLocation que se utiliza para indicar la localización del fichero de configuración - XML de MyBatis. Normalmente solo es necesario dicho fichero si se requiere cambiar los valores por defecto de las secciones - <settings> o <typeAliases>. -

- -

- Es importante saber que este fichero de configuración no tiene por qué ser un fichero de configuración de MyBatis completo. - Concretamente, los environments, dataSources y transactionManagers serán ignorados. - SqlSessionFactoryBean crea su propio Environment de MyBatis con los valores configurados tal y como se requieren. -

- -

- Otro motivo para necesitar un fichero de configuración es que los ficheros de mapeo XML no estén en el mismo lugar del classpath - que los mapper interfaces. En este caso hay dos opciones. La primera es especificar manualmente el classpath de los ficheros XML - usando la sección <mappers> del fichero de configuración de MyBatis. La segunda opción es usar la propiedad - mapperLocations del factory bean. -

- -

- La propiedad mapperLocations recibe una lista de localizaciones de recursos. Se utiliza para indicar la ubicación de los - ficheros de mapeo XML de MyBatis. El valor puede contener un patron tipo Ant para cargar todos los ficheros de un directorio o - buscar de forma recursiva en todos los paths desde una localización base. Por ejemplo: -

- - - - -]]> - -

- Esto cargaría todos los ficheros de mapeo XML en el paquete sample.config.mappers y sus subpaquetes. -

- -

- Otra propiedad que puede ser necesaria en un entorno con transacciones gestionadas por contenedor es la - transactionFactoryClass. Lee la sección de transacciones para obtener más detalles. -

- -

- En caso de usar la característica multi-db necesitarás informar la propiedad databaseIdProvider - de la siguiente forma: -

- - - - - sqlserver - db2 - oracle - mysql - - - - - - - - -]]> - -

- NOTE - Since 1.3.0, configuration property has been added. - It can be specified a Configuration instance directly without MyBatis XML configuration file. - For example: -

- - - - - - - - -]]> - -
-
- -
\ No newline at end of file diff --git a/src/site/es/xdoc/getting-started.xml.vm b/src/site/es/xdoc/getting-started.xml.vm deleted file mode 100644 index afdf728f55..0000000000 --- a/src/site/es/xdoc/getting-started.xml.vm +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - MyBatis-Spring | Primeros pasos - Hunter Presnall - Eduardo Macarron - - - -
-

- Este capítulo te mostrará en pocos pasos cómo instalar y configurar MyBatis-Spring y cómo construir - una pequeña aplicación transaccional. -

- - -

- Para usar el módulo MyBatis-Spring, debes incluir el fichero - mybatis-spring-${project.version}.jar - y sus dependencias en el classpath. -

-

- Si usas Maven simplemente añade la siguiente dependencia a tu pom.xml: -

- - org.mybatis - mybatis-spring - ${project.version} -]]> -
- - -

- Para usar MyBatis con Spring necesitas definir al menos dos cosas en tu contexto Spring: una - SqlSessionFactory y al menos un mapper interface. -

- -

- En MyBatis-Spring se usa un - SqlSessionFactoryBean - para crear una - SqlSessionFactory - . Para configurar la factory bean pon lo siguiente en tu - fichero de configuración de Spring: -

- - - -]]> - - - -

- Observa que la SqlSessionFactory - requiere un - DataSource - . Éste puede ser cualquier - DataSource - y debe configurarse como cualquier otra conexión a base de datos - de Spring. -

- -

- Asumamos que tienes un mapper interface definido de la siguiente forma: -

- - -

- Este interface se añade a Spring usando un - MapperFactoryBean - de la siguiente forma: -

- - - -]]> - -

- Observa que la clase del mapper indicada - debe - ser un interface, no una implementación. En este ejemplo se usan - anotaciones para especificar la SQL, pero también es posible - usar un fichero de mapeo XML. -

- -

- Una vez configurado, puedes inyectar mappers directamente en tus - beans de servicio/negocio de la misma forma que inyectarías cualquier - otro bean en Spring. - La clase MapperFactoryBean se encargará de obtener - una SqlSession y de cerrarla. Si hay una transación Spring - en curso, la sesión se comitará o se hará rollback cuando la transacción - finalice. Finalmente, cualquier excepción será traducida a una excepión - DataAccessExceptions de Spring. -

- -

- If you use the Java Configuration: -

- - - -

- Invocar a MyBatis es sólo una línea de código: -

- -
-
- -
diff --git a/src/site/es/xdoc/index.xml b/src/site/es/xdoc/index.xml deleted file mode 100644 index c2246c982e..0000000000 --- a/src/site/es/xdoc/index.xml +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - MyBatis-Spring | Introducción - Hunter Presnall - Eduardo Macarron - - - -
- -

- MyBatis-Spring permite integrar MyBatis con Spring. - Esta librería permite que MyBatis participe en trasacciones Spring, se encarga de constuir - mappers y SqlSessions e inyectarlos en otros beans, traduce excepciones - de MyBatis en excepcines DataAccessExceptions de Spring y finalmente, - permite construir aplicaciones libres de dependencias de MyBatis, Spring y MyBatis-Spring. -

-
- - -

- Spring version 2 sólo soporta iBATIS version 2. See hizo un intento de incluir el soporte - de MyBatis 3 en Spring 3 (ver el issue Jira). - Pero desafortunadamente, el desarrollo de Spring 3 finalizó antes de que MyBatis 3 fuera liberado oficialmente. - Dado que el equipo de Spring no quería liberar una versión basada en un producto no terminado el soporte de - oficial tenía que esperar. Dado el interés de la comunidad en el soporte de MyBatis, - la comunidad de MyBatis decidió que era el momento de unificar a los colaboradores interesados - y proporcionar la integración con Spring como un sub-projecto de MyBatis en su lugar. -

-
- - -

- Antes de comenzar con MyBatis-Spring, es muy importante que estés familiarizado con la terminología tanto de - MyBatis como de Spring. Este documento no pretende proporcionar información de configuración básica de - MyBatis o Spring. -

-

- MyBatis-Spring requires following versions: -

- - - - - - - - - - - - - - - - - - - - - - - - - - -
- MyBatis-Spring - - MyBatis - - Spring Framework - - Spring Batch - - Java -
- 2.0 - - 3.4+ - - 5.0+ - - 4.0+ - - Java 8+ -
- 1.3 - - 3.4+ - - 3.2.2+ - - 2.1+ - - Java 6+ -
-
- - -

- Queremos agradecer a la todos los que han hecho de este proyecto una realidad (en orden alfabético): - Eduardo Macarron, Hunter Presnall y Putthiphong Boonphong por la codificación, pruebas y documentación; - a Andrius Juozapaitis, Giovanni Cuccu, Mike Lanyon, Raj Nagappan y Tomas Pinos - por sus contribuciones; y a Simone Tripodi por encontrarlos a todos y traerlos al proyecto MyBatis ;) - Sin ellos este proyecto no existiría. -

-
- - -

- Si ves que hay alguna carencia en esta documentación, o que falta alguna característica por documentar, - te animamos a que lo investigues y la documentes tu mismo! -

-

- Las fuentes de este manual están disponibles en formato xdoc en el - Git del proyecto. - Haz un fork, cambialas y envía un pull request. -

-

- Eres el mejor candidato para documentar porque los lectores de esta documentación son gente como tú! -

-
- -

Users can read about MyBatis-Spring in the following translations:

- -

Do you want to read about MyBatis in your own native language? Fill an issue providing patches with your - mother tongue documentation!

-
-
- - -
diff --git a/src/site/es/xdoc/mappers.xml b/src/site/es/xdoc/mappers.xml deleted file mode 100644 index 5f0656b734..0000000000 --- a/src/site/es/xdoc/mappers.xml +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - MyBatis-Spring | Inyectar mappers - Hunter Presnall - Eduardo Macarron - - - -
-

- En lugar de codificar DAOs (data access objects) manualmente usando la clase - SqlSessionDaoSupport o SqlSessionTemplate, Mybatis-Spring - puede crear un mapper thread-safe que puedes inyectar directamente en otros beans. -

- - - -]]> - -

- Una vez inyectado, el mapper está listo para se usado en la lógica de aplicación: -

- -

- Observa que no se usa la SqlSession ni ninguna otra referencia a MyBatis en este código. - No es necesario ni siquiera crear o cerrar la sesión, MyBatis-Spring se encarga de ello. -

- - - -

- La forma de registrar un mapper varía según si quieres usar la configuración XML clásica o la nueva Java Config de Spring 3.0+ - (También conocida como @Configuration).

- -

Con confiugración XML

- -

- Un mapper se registra en Spring incluyendo un MapperFactoryBean en tu fichero de configuración XML, de la siguiente forma: -

- - - -]]> - -

- Si el mapper UserMapper tiene un fichero XML de mapeo asociado el MapperFactoryBean - lo cargará automáticamente. Por lo tanto no es necesario especificar dicho mapper en el fichero - de configuración de MyBatis a no ser que los ficheros XML estén en una lugar distinto del classpath. - Ver la sección de SqlSessionFactoryBean y la propiedad - configLocation - para más información. -

- -

- El MapperFactoryBean requiere o un - SqlSessionFactory o un SqlSessionTemplate. - Ambos se pueden informar usando sendas propiedades sqlSessionFactory y - sqlSessionTemplate. - Si ambas propiedades han sdo informadas la SqlSessionFactory se ignora. - Dado que un SqlSessionTemplate debe tener un session factory - dicho factory se usará por el MapperFactoryBean. -

- -

Con Java Config

- -

- Cuando uses Java Config puedes obtener un mapper directamente desde un SqlSessionTemplate como sigue: -

- - - -

- Ten en cuenta que no puedes devolver un mapper obtenido de la una SqlSession normal de MyBatis - porque no sería thread safe y solo viviría hasta que la SqlSession que lo creó se cierre. - Debes usar un - SqlSessionTemplate en su lugar, - como se muestra en el ejemplo. -

-
- - -

- No es necesario registrar los mappers uno por uno en el fichero XML de Spring. - En lugar de esto, puede dejar que MyBatis-Spring los busque en tu classpath. -

- -

- Hay tres formas distintas de hacerlo: -

-
    -
  • Usando el elemneto <mybatis:scan/>.
  • -
  • Usando la anotación @MapperScan
  • -
  • Usando un fichero clásico XML de configuración de Spring y añadiendo el bean MapperScannerConfigurer
  • -
- -

Tango <mybatis:scan/> como @MapperScan son características añadidas en MyBatis-Spring 1.2.0. - @MapperScan requiere Spring 3.1+.

- -

<mybatis:scan/>

- -

- El elemento XML <mybatis:scan/> busca mappers de una forma muy similar a cómo - <context:component-scan/> busca beans. -

- -

A continuación se muestra un fichero XML de configuración:

- - - - - - - -]]> - -

- La propiedad basePackage te permite indicar el paquete base donde residen tus mappers. - Puedes indicar más de un paquete usando un punto y coma o una coma como separador. Los mappers serán buscados - de forma recursiva comenzando en el/los paquetes especificados. -

- -

- Fíjate que no es necesario indicar una SqlSessionFactory o - SqlSessionTemplate porque el <mybatis:scan/> - creará MapperFactoryBeans que pueden ser autowired. Pero si usas más de un DataSource - el autowire puede que no te funcione. En este caso puedes usar las propiedades factory-ref or - template-ref para indicar los beans correctos a utilizar. -

- -

- <mybatis:scan/> soporta el filtrado de mappers mediante una interfaz marcador o una anotación. - La propiedad annotation especifica la anotación que se debe buscar. - La propiedad marker-interface especifica la interfaz a buscar. - Si se indican ambas se añadirán todos los mappers que cumplan cualquier criterio. - Por defecto ambas propiedades son null asi que todos los interfaces de los paquetes base serán cargados como mappers. -

- -

- Los mappers descubiertos serán nombrados usando la estratégia de nombres por defecto de Spring para los componentes - autodetectados (see the Spring reference document(Core Technologies -Naming autodetected components-)). - Es decir, si no se encuentra ninguna anotación, se usará el nombre no cualificado sin capitalizar del mapper. - Pero si se encuentra una anotación @Component o JSR-330 @Named se obtendrá el nombre de dicha anotación. - Fíjate que puedes usar como valor de la annotation el valor org.springframework.stereotype.Component, - javax.inject.Named (if you have JSE 6) o una anotación propia - (que debe ser a su vez anotada) de forma que la anotación hará las veces de localizador y de proveedor de nombre. -

- -

- NOTE <context:component-scan/> - no puede encontrar y registrar mappers. Los mappers son interfaces y, para poderlos registrar en Spring, - el scanner deben conocer cómo crear un MapperFactoryBean para cada interfaz encontrado. -

- -

@MapperScan

- -

- Si usas la Java Configuration de Spring (@Configuration) posiblemente prefieras usar - @MapperScan en lugar de <mybatis:scan/>. -

- -

La anotación @MapperScan se usa de la siguiente forma:

- - - -

- La anotación fucniona exactamente igual que <mybatis:scan/> que hemos visto en la sección anterior. - También te permite especificar un interfaz marcador o una anotación mediante sus propiedades - markerInterface y annotationClass. - Tambien puedes indicar una SqlSessionFactory o un SqlSessionTemplate específicos - mediante las propiedades sqlSessionFactory y sqlSessionTemplate. -

- -

MapperScannerConfigurer

- -

- MapperScannerConfigurer es un BeanDefinitionRegistryPostProcessor - que se puede incluir como un bean normal en el fichero clásico XML de configuración de Spring. - Para configurar un MapperScannerConfigurer añade lo siguiente al fichero de configuración de Spring: -

- - -]]> - -

- Si quieres indicar un sqlSessionFactory o un sqlSessionTemplate - observa que se requeiren los nombres de los beans y no sus referencias - por ello se usa el atributo value en lugar del habitual ref: -

- ]]> - -
-
- -
\ No newline at end of file diff --git a/src/site/es/xdoc/sample.xml b/src/site/es/xdoc/sample.xml deleted file mode 100644 index 263092304b..0000000000 --- a/src/site/es/xdoc/sample.xml +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - MyBatis-Spring | Sample Code - Eduardo Macarron - - - -
- -

- NOTE - See JPetstore 6 demo to know about how to use Spring with a full web application server. -

- -

- You can check out sample code from the MyBatis-Spring repo: -

-

- Any of the samples can be run with JUnit 5. -

-

- The sample code shows a typical design where a transactional service gets domain objects from a data access layer. -

-

- FooService.java acts as the service: -

- -

- It is a transactional bean, so when the method is called, the transaction is started - and the transaction is committed when the method ends without throwing an uncaught exception. - Notice that transactional behaviour is configured with the - @Transactional - attribute. This is not required; any other way provided by Spring can be used to demarcate - your transactions. -

-

- This service calls a data access layer built with MyBatis. This layer - consists on a just an interface UserMapper.java - that will be used with a dynamic proxy built by MyBatis at - runtime and injected into the service by Spring. -

- -

- Note that, for the sake of simplicity we used the interface UserMapper.java for the DAO scenario - where a DAO is built with an interface and a implementation though in this case it would have been more - adequate to use an interface called UserDao.java instead. -

-

- We will see different ways to find the mapper interface, register it to Spring and inject it into the service bean: -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Scenarios
Sample testDescription
- SampleMapperTest.java - - Shows you the base configuration based on a MapperFactoryBean - that will dynamically build an implementation for UserMapper -
- SampleScannerTest.java - - Shows how to use the MapperScannerConfigurer so all the mappers in a project are autodiscovered. -
- SampleSqlSessionTest.java - - Shows how to hand code a DAO using a Spring managed SqlSession - and providing your own implementation UserDaoImpl.java. -
- SampleEnableTest - - Shows how to use Spring's @Configuration with the @MapperScann annotation so - mappers are autodiscovered. -
- SampleNamespaceTest - - Shows how to use the custom MyBatis XML namespace. -
- SampleJavaConfigTest.java - - Shows how to use Spring's @Configuration to create MyBatis beans manually. -
- SampleJobJavaConfigTest.java - - Shows how to use ItemReader and ItemWriter on Spring Batch using Java Configuration. -
- SampleJobXmlConfigTest.java - - Shows how to use ItemReader and ItemWriter on Spring Batch using XML Configuration. -
-

- Please take a look at the different applicationContext.xml files to see MyBatis-Spring in action. -

- -
- -
- diff --git a/src/site/es/xdoc/sqlsession.xml b/src/site/es/xdoc/sqlsession.xml deleted file mode 100644 index e82132fa9f..0000000000 --- a/src/site/es/xdoc/sqlsession.xml +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - MyBatis-Spring | Utilizar un SqlSession - Hunter Presnall - Eduardo Macarron - - - -
-

- En MyBatis usas un SqlSessionFactory para crear un SqlSession. - Una vez tienes una sesión, la usas para ejecutar tus mapped statements, hacer commit o rollback y finalmente - cuando no la necesitas la cierras. Con MyBatis-Spring no es necesario que utilices la SqlSessionFactory - directamente porque puedes inyectar en tus beans una SqlSession thread safe (reentrante) - que hará de forma automática el commit, rollback y se cerrará conforme a la configuración de la transacción en Spring. -

- - -

- El SqlSessionTemplate is el corazón de MyBatis-Spring. - Implementa SqlSession y está pensado para que sea directamente reemplazable por cualquier código - que actualmente use SqlSession. - SqlSessionTemplate es thread safe y se puede compartir por más de un DAO. -

- -

- Cuando se invoca a cualquier método SQL, incluyendo cualquier método de un mapper devuelto por - getMapper(), el SqlSessionTemplate - se asegurará de que la SqlSession utilizada es la asociada con la transacción Spring en curso. - Adicionalmente, se encarga de gestionar su ciclo de vida, incluyendo su cierre, commit o rollback de la sesión si fuera necesario. -

- -

- Se debe usar siempre un SqlSessionTemplate en lugar de la implementación de sesión - por default MyBatis: DefaultSqlSession - porque el template puede participar en transacciones Spring y es thread safe con lo que puede ser inyectado - en múltiples mappers (proxies). Cambiar de uno a otro puede crear problemas de integridad de datos. -

- -

- El SqlSessionTemplate puede construirse usando un SqlSessionFactory como argumento de su constructor. -

- - -]]> - - - -

- Este bean puede ser inyectado directamente en tus DAOs. Necesitarás una propiedad - SqlSession en tu bean como se muestra a continuación: -

- -

- E inyectar el SqlSessionTemplate de la siguiente forma: -

- - -]]> - -

- El SqlSessionTemplate tiene también un constructor que recibe como parámetro - un ExecutorType. Esto permite construir, por ejemplo, - una SqlSession batch utilizando la siguiente configuracíon de Spring: -

- - - -]]> - - - -

- Ahora todos tus statements se ejecutarán en batch de forma que puedes programar lo siguiente: -

- users) { - for (User user : users) { - sqlSession.insert("org.mybatis.spring.sample.mapper.UserMapper.insertUser", user); - } -}]]> - -

- Fijate que esta configuración si el método de ejecución deseado es distinto del establecido por defecto - en el SqlSessionFactory. -

- -

- La contrapartida de esto es que no es posible cambiar el méodo de ejecución dentro de una transacción. - Por tanto asegurate que o bien todas las llamadas a un SqlSessionTemplates con distinto método de ejecución - se ejecutan en una transacción diferente (p.ej: with PROPAGATION_REQUIRES_NEW) o completamente fuera de la transacción. -

-
- - -

- SqlSessionDaoSupport es una clase de soporte abstracta que proporciona un SqlSession. - Llamando a getSqlSession() obtiene un SqlSessionTemplate - que puedes utilizar para ejecutar métodos SQL, como sigue: -

- -

- Normalmente es preferible usar un MapperFactoryBean a esta clase dao que no requeire código extra. - Pero esta clase es de utilidad cuando es necesario hacer algún otro tipo de trabajo no-MyBatis en el DAO y se necesitan - clases concretas. -

- -

- SqlSessionDaoSupport que se informe la propiedad - sqlSessionFactory o la sqlSessionTemplate. - Si se informan ambas propiedades, la sqlSessionFactory se ignora. -

- -

- Asumiendo una clase UserDaoImpl que extiende - SqlSessionDaoSupport, se puede configurar Spring de la siguiente forma: -

- - -]]> -
-
- -
diff --git a/src/site/es/xdoc/transactions.xml b/src/site/es/xdoc/transactions.xml deleted file mode 100644 index a3f2302cf2..0000000000 --- a/src/site/es/xdoc/transactions.xml +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - MyBatis-Spring | Transacciones - Hunter Presnall - Eduardo Macarron - - - -
-

- Uno de los principales motivos para usar MyBatis-Spring es que permite que MyBatis participe en transacciones - de Spring. En lugar de haber creado un TransactionManager especifico para MyBatis, MyBatis-Spring aprovecha - el existente DataSourceTransactionManager de Spring. -

-

- Una vez que has configurado un TransactionManager in Spring puedes configurar tus transacciones en Spring como siempre. - Tanto las anotaciones @Transactional como las configuraciones de tipo AOP se soportan. - Se creará una sola instancia de SqlSession para toda la transacción. - Se hará commit o rollback de esta sesión cuando la transacción finalice. -

-

- MyBatis-Spring se encargará de gestionar las transacciones de forma transparente una vez se hayan configurado. No es necesario - incluir código adicional en tus clases. -

- - -

- Para habilitar las transacciones Spring, simplemente crea un DataSourceTransactionManager en tu fichero de configuración: -

- - -]]> - - - -

- El DataSource especificado puede ser cualquier DataSource JDBC que usarías normalmente con Spring. - Esto incluye connection pools y DataSources obtenidos mediante JNDI. -

-

- Fijate que el DataSource especificado en el transaction manager debe ser el mismo que el que se use para - crear el SqlSessionFactoryBean o la gestión de transacciones no funcionará. -

-
- - -

- Si estás usando un contenedor JEE y quiere que spring participe en las transacciones gestionadas por contenedor (CMT), entonces debes - configurar un JtaTransactionManager en Spring o alguna de sus clases específicas de cada contenedor. - Lo más sencillo es utilizar el namespace tx de Spring or the JtaTransactionManagerFactoryBean: -

- ]]> - - - -

- Con esta configuración, MyBatis se comportará como cualquier otro recurso configurado con CMT. - Spring utilizará cualquier transacción CMT existente y asociará la SqlSession a ella. - Si no no hay ninguna transacción previa pero se necesita una en base a la configuración de la transacción, Spring creará - una transacción gestionada por contenedor nueva. -

-

- Fijate que si quieres usar transacciones CMT pero no quieres utilizar la gestión de transacciones de Spring - no debes configurar ningun transaction manager en Spring y debes - configurar el SqlSessionFactoryBean para que use la clase ManagedTransactionFactory de MyBatis de la siguiente forma: -

- - - - - -]]> - - - -
- - -

- La interfaz SqlSession proporciona métodos especificos para gestionar la transacción. - Pero al usar MyBatis-Spring en tus beans se inyectará una SqlSession o un mapper gestionados por Spring. - Esto significa que Spring siempre gestionará tus transacciones. -

-

- No puedes llamar a los métodos SqlSession.commit(), SqlSession.rollback() - o SqlSession.close() en una SqlSession gestionada por Spring. - Si lo intentas obtendrás una excepción UnsupportedOperationException. - Además, estos métodos no se exponen en los mapper interfaces. -

-

- Independientemente de el estado del autocommit de la conexión JDBC cualquier llamada - a un metodo SQL de SqlSession fuera de una transacción Spring será automaticamente commitada. -

-

- Si quieres controlar las transacciones programaticamente consulta el the Spring reference document(Data Access -Programmatic transaction management-). - Este código muetra como controlar una transacción manualmente usando el PlatformTransactionManager. -

- - -

- You can omit to call the commit and rollback method using the TransactionTemplate. -

- - { - userMapper.insertUser(user); - return null; -});]]> - -

- Este código usa un mapper pero también funcionaría con SqlSession. -

-
-
- -
diff --git a/src/site/es/xdoc/using-api.xml b/src/site/es/xdoc/using-api.xml deleted file mode 100644 index f364552212..0000000000 --- a/src/site/es/xdoc/using-api.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - MyBatis-Spring | Using the MyBatis API - Hunter Presnall - Eduardo Macarron - - - -
-

- With MyBatis-Spring, you can continue to directly use the MyBatis API. - Simply create an SqlSessionFactory in Spring using - SqlSessionFactoryBean and use the factory in your code. -

- - -

- Use this option with care because wrong usage may produce runtime errors or - worse, data integrity problems. Be aware of the following caveats with direct API usage: -

-
    -
  • -

    - It will not participate in any Spring transactions. -

    -
  • -
  • -

    - If the SqlSession is using a DataSource - that is also being used by a Spring transaction manager and there is currently - a transaction in progress, this code will throw an exception. -

    -
  • -
  • -

    - MyBatis' DefaultSqlSession is not thread safe. If you - inject it in your beans you will get errors. -

    -
  • -
  • -

    - Mappers created using DefaultSqlSession are not thread safe either. - If you inject them it in your beans you will get errors. -

    -
  • -
  • -

    - You must make sure that your SqlSessions - are always closed in a finally block. -

    -
  • -
-
- -
diff --git a/src/site/ja/markdown/README.md b/src/site/ja/markdown/README.md new file mode 100644 index 0000000000..3280241e0c --- /dev/null +++ b/src/site/ja/markdown/README.md @@ -0,0 +1,18 @@ +# 目次 + +このページはGitHub上でドキュメントの目次を表示するため用意したものです。 + +> **NOTE:** +> +> リンクはmaven-site-pluginでHTMLに変換することを前提に指定されているため、GitHubでのレンダリングではリンク切れになっているものがあります。 + +* [イントロダクション](./index.md) +* [スタートガイド](./getting-started.md) +* [SqlSessionFactoryBean](./factorybean.md) +* [トランザクション](./transactions.md) +* [SqlSessionの利用](./sqlsession.md) +* [Mapperの注入](./mappers.md) +* [Spring Boot](./boot.md) +* [MyBatis APIの利用](./using-api.md) +* [Spring Batch](./batch.md) +* [サンプルコード](./sample.md) diff --git a/src/site/ja/markdown/batch.md b/src/site/ja/markdown/batch.md new file mode 100644 index 0000000000..9c0fcd60af --- /dev/null +++ b/src/site/ja/markdown/batch.md @@ -0,0 +1,344 @@ + +# Spring Batch + +MyBatis-Spring 1.1.0 以降では、 Spring Batch を構築するための Bean として `MyBatisPagingItemReader` 、 `MyBatisCursorItemReader` 、 `MyBatisBatchItemWriter` が用意されています。 +また、2.0.0 以降では、Java Configuration をサポートするための Builder クラスとして `MyBatisPagingItemReaderBuilder` 、 `MyBatisCursorItemReaderBuilder` 、 `MyBatisBatchItemWriterBuilder` が用意されています。 + +NOTE +ここで扱うのは [Spring Batch](http://static.springsource.org/spring-batch/) を使ったバッチ処理で、MyBatis の [`SqlSession`](sqlsession.html) を利用したバッチ処理ではありません。 + +## MyBatisPagingItemReader + +この Bean は、MyBatis を利用してデータベースからページ単位でレコードを読み出す `ItemReader` です。 + +結果を取得する際は `setQueryId` プロパティで指定したクエリが実行されます。1ページあたりの件数は setPageSize プロパティで指定することができます。 +`read()` メソッドが呼び出されると、必要に応じて追加のページを取得するクエリが実行されます。 +実行されるクエリでは、Reader によって提供されるページング処理を行う際に必要となるパラメーターを使って期待される結果を返す SQL 文を記述することになります(実際の SQL 文は方言依存です)。 +提供されるパラメーターは次の通りです。 + +* `_page`: 取得対象のページ番号(最初のページは0 +* `_pagesize`: 1ページあたりの件数 +* `_skiprows`: `_page` と `_pagesize` の積 + +これらのパラメーターは、例えば次のように SELECT 文中で指定することができます。 + +```xml + +``` + +設定例: + +```xml + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisPagingItemReader reader() { + return new MyBatisPagingItemReaderBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") + .build(); + } +} +``` + +**さらに複雑な例:** + +```xml + +``` +```xml + + + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @StepScope + @Bean + public MyBatisPagingItemReader dateBasedCriteriaReader( + @Value("#{@datesParameters}") Map datesParameters) throws Exception { + return new MyBatisPagingItemReaderBuilder() + .sqlSessionFactory(batchReadingSessionFactory()) + .queryId("com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot") + .parameterValues(datesParameters) + .pageSize(200) + .build(); + } + + @StepScope + @Bean + public Map datesParameters( + @Value("#{jobExecutionContext['EXTRACTION_START_DATE']}") LocalDate yesterday, + @Value("#{jobExecutionContext['TODAY_DATE']}") LocalDate today, + @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}") LocalDate firstDayOfTheMonth, + @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}") LocalDate firstDayOfThePreviousMonth) { + Map map = new HashMap<>(); + map.put("yesterday", yesterday); + map.put("today", today); + map.put("first_day_of_the_month", firstDayOfTheMonth); + map.put("first_day_of_the_previous_month", firstDayOfThePreviousMonth); + return map; + } +} +``` + +The previous example makes use of a few different things: + +* `sqlSessionFactory`: あなた自身の sessionFactory を reader に指定することができます。複数のデータベースから読み取る場合は有用かも知れません。 +* `queryId`: レコード取得時に実行されるクエリの ID を指定します。異なるネームスペースに属するクエリを指定する場合はネームスペースの指定を忘れないようにしてください。 +* `parameterValues`: クエリ実行時に使用する追加のパラメーターを Map 形式で渡すことができます。上の例では Spring が `jobExecutionContext` から SpEL 式を使って取得した値をもとに構築した `Map` を指定しています。 + MyBatis の Mapper ファイルでは `Map` のキーをパラメーター名として指定します(例: *yesterday* を指定する場合は `#{yesterday,jdbcType=TIMESTAMP}` のように指定します)。 + `jobExecutionContext` と Spring EL 式を利用するため、`Map` および `Reader` はどちらも `step` スコープ内で構築されているという点に注意してください。 + また、MyBatis の `TypeHandler` が正しく設定されていれば、この例のように JodaTime のようなカスタムのインスタンスを引数として渡すこともできます。 +* `pageSize`: バッチ処理のチャンクサイズを指定します。 + +## MyBatisCursorItemReader + +This bean is an `ItemReader` that reads records from a database using a cursor. + +NOTE +To use this bean you need at least MyBatis 3.4.0 or a newer version. + +It executes the query specified as the `setQueryId` property to retrieve requested data by using the method `selectCursor()`. +Each time a `read()` method is called it will return the next element of the cursor until no more elements are left. + +The reader will use a separate connection so the select statement does no participate in any transactions created as part of the step processing. + +When using the cursor you can just execute a regular query: + +```xml + +``` + +Follows below a sample configuration snippet: + +```xml + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisCursorItemReader reader() { + return new MyBatisCursorItemReaderBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") + .build(); + } +} +``` + +## MyBatisBatchItemWriter + +`SqlSessionTemplate` のバッチ機能を使って渡された一連のアイテムを処理する `ItemWriter` です。 `SqlSessionFactory` は `ExecutorType.BATCH` を使って設定する必要があります。 + +`write()` の呼び出し時に実行するステートメントの ID を指定しておく必要があります。 `write()` はトランザクション内で呼び出されることを前提としています。 + +設定例: + +```xml + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisBatchItemWriter writer() { + return new MyBatisBatchItemWriterBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .statementId("com.my.name.space.batch.EmployeeMapper.updateEmployee") + .build(); + } +} +``` + +**`ItemReader`を使用して読み込んだアイテムを任意のパラメータオブジェクトへ変換する** + +デフォルトの動作では、`MyBatisBatchItemWriter` は `ItemReader` を使用して読みこんだアイテム (または `ItemProcessor` によって変換したアイテム)を、そのままMyBatis(`SqlSession` の `update` メソッド)のパラメーターオブジェクトとして渡します。 +もしMyBatisへ渡すパラメーターオブジェクトをカスタマイズしたい場合は、`itemToParameterConverter` オプションを利用することで実現するすることができます。 +たとえば、`itemToParameterConverter` オプションを使用すると、 アイテムオブジェクト以外のオブジェクトをMyBatisへ渡すことができます。 +以下にサンプルを示します。 + +まず、任意のパラメータオブジェクトに変換するためのコンバータクラス(またはファクトリメソッド)を作成します。以下のサンプルではファクトリメソッドを使用します。 + +```java +public class ItemToParameterMapConverters { + public static Converter> createItemToParameterMapConverter(String operationBy, LocalDateTime operationAt) { + return item -> { + Map parameter = new HashMap<>(); + parameter.put("item", item); + parameter.put("operationBy", operationBy); + parameter.put("operationAt", operationAt); + return parameter; + }; + } +} +``` + +つぎに, SQLマッピングを書きます。 + +```xml + +``` + +さいごに, `MyBatisBatchItemWriter` の設定を行います。 + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisBatchItemWriter writer() throws Exception { + return new MyBatisBatchItemWriterBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .statementId("org.mybatis.spring.sample.mapper.PersonMapper.createPerson") + .itemToParameterConverter(createItemToParameterMapConverter("batch_java_config_user", LocalDateTime.now())) + .build(); + } +} +``` + +```xml + + + + + + + + + + +``` + +**Composite Writer を使って複数のテーブルに書き込む(注意事項あり)** + +このテクニックを使うには MyBatis 3.2 以降が必要です。それ以前のバージョンには [問題](http://code.google.com/p/mybatis/issues/detail?id=741) があるため、Writer が期待通りに動作しません。 + +まず、*Interaction* と1対1の関係にある *InteractionMetadata* と、これらとは独立した *VisitorInteraction* および *CustomerInteraction* を保持する Item クラスを用意します。 + +```java +public class InteractionRecordToWriteInMultipleTables { + private final VisitorInteraction visitorInteraction; + private final CustomerInteraction customerInteraction; + private final Interaction interaction; + // ... +} +``` +```java +public class Interaction { + private final InteractionMetadata interactionMetadata; +} +``` + +`CompositeItemWriter` の設定では、それぞれのオブジェクトの writer を順番に呼び出すように設定します。 +この例では *Interaction* をアップデートするためのキーを取得するため、*InteractionMetadata* を先に書き込む必要があります。 + +```xml + + + + + + + + + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public CompositeItemWriter interactionsItemWriter() { + CompositeItemWriter compositeItemWriter = new CompositeItemWriter(); + List> writers = new ArrayList<>(4); + writers.add(visitorInteractionsWriter()); + writers.add(customerInteractionsWriter()); + writers.add(interactionMetadataWriter()); + writers.add(interactionWriter()); + compositeItemWriter.setDelegates(writers); + return compositeItemWriter; + } +} +``` + +それぞれの writer を必要に応じて設定します。例えば *Interaction* と *InteractionMetadata* の設定は次のようになります。 + +```xml + +``` +```xml + +``` + +reader の場合と同様、 `statementId` はネームスペースを含むステートメント ID です。 + +Mapper ファイルにステートメントを定義します。 + +```xml + + + +``` +```xml + + + +``` + +はじめに `insertInteractionMetadata` が呼ばれ、その際に取得した主キーを使って親となる `Interaction` を `insertInteraction` を使って書き込むことができます。 + +***JDBC ドライバによって動作が異なるので注意が必要です。例えば MySQL の JDBC ドライバは作成された全ての行の ID を返しますが、H2 バージョン 1.3.168 ではバッチモードでも最後に作成された行の ID のみが返されます。*** diff --git a/src/site/ja/markdown/boot.md b/src/site/ja/markdown/boot.md new file mode 100644 index 0000000000..0243b87200 --- /dev/null +++ b/src/site/ja/markdown/boot.md @@ -0,0 +1,4 @@ + +# Using Spring Boot + +詳細は [MyBatis Spring-boot-starter](http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure) のドキュメントを参照してください。 diff --git a/src/site/ja/markdown/factorybean.md b/src/site/ja/markdown/factorybean.md new file mode 100644 index 0000000000..00808c2d50 --- /dev/null +++ b/src/site/ja/markdown/factorybean.md @@ -0,0 +1,180 @@ + +# SqlSessionFactoryBean + +基となる MyBatis では、`SqlSessionFactory` をビルドする際 `SqlSessionFactoryBuilder` を使いましたが、MyBatis-Spring では、`SqlSessionFactoryBean` を使います。 + +## 設定 + +Spring の XML 設定ファイルに次の Bean を定義することで Factory Bean を生成することができます。 + +```xml + + + +``` + +`SqlSessionFactoryBean` は Spring の `FactoryBean` インターフェイス([the Spring documentation(Core Technologies -Customizing instantiation logic with a FactoryBean-](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-extension-factorybean) を参照してください)を実装しています。 +これはつまり、最終的に Spring が生成するのは `SqlSessionFactoryBean` ではなく、Factory の `getObject()` メソッドによって返されるオブジェクトであるということです。 +上記の設定では、Spring は `SqlSessionFactory` を生成し、`sqlSessionFactory` という名前の Bean として登録します。 +これに相当する Java のコードは下記のようになるでしょう。 + +```java +@Configuration +public class MyBatisConfig { + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + return factoryBean.getObject(); + } +} +``` + +通常 MyBatis-Spring を使う場合、`SqlSessionFactoryBean` や対応する `SqlSessionFactory` を直接利用する必要はありません。 +`SqlSessionFactory` は `MapperFactoryBean` や `SqlSessionDaoSupport` を継承した他の DAO にインジェクト(注入)されます。 + +## プロパティ + +`SqlSessionFactory` で必須のプロパティは JDBC の `DataSource` のみです。 どのような `DataSource` でも構いません。Spring でデータベース接続を定義する通常の手順で定義してください。 + +`configLocation` は、MyBatis の XML 設定ファイルの場所を指定する際に使用します。これは、例えば基になる MyBatis の設定の一部を変更したい場合などに必要となります。 +よくあるのは `` や `` などの設定です。 + +ここで指定する設定ファイルは、完全な MyBatis 設定ファイルである必要はありません。 環境、データソース、MyBatis のトランザクションマネージャーに関する設定は**無視されます**。 +`SqlSessionFactoryBean` は、独自にカスタマイズした MyBatis `Environment` を生成し、必要に応じてこれらの値を設定するようになっています。 + +設定ファイルの指定が必要とされるもう一つの例は、MyBatis の Mapper XML ファイルが Mapper クラスとは別のクラスパスに存在する場合です。 +このような構成にする場合、次のどちらかの方法で設定することができます。最初の方法は、MyBatis の設定ファイルの `` で各 XML ファイルのクラスパスを指定する方法です。 +そしてもう一つは、Factory Bean の `mapperLocations` を使った方法です。 + +`mapperLocations` プロパティは Resource Location のリストを取り、ここで MyBatis の XML Mapper ファイルの場所を指定することができます。 +Ant スタイルのパターン文字列を使って特定のディレクトリ内の全ファイルを指定したり、内包するディレクトリを再帰的に検索対象にすることもできます。次の例を見てください。 + +```xml + + + + +``` + +Javaでは、同等のコードは次のようになります。 + +```java +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sample/config/mappers/**/*.xml")); + return factoryBean.getObject(); +} +``` + +このように指定すると、クラスパス内の `sample.config.mappers` パッケージと、そのサブパッケージに含まれる全ての MyBatis Mapper XML ファイルがロードされます。 + +Container-Managed トランザクションを利用する環境では、`transactionFactoryClass` プロパティが必須となります。「トランザクション」章の該当する節を参照してください。 + +複数のデータベースを使用する場合は、`databaseIdProvider` プロパティを設定する必要があります。 + +```xml + + + + sqlserver + db2 + oracle + mysql + + + +``` +```xml + + + + + +``` + +Javaでは、同等のコードは次のようになります。 + +```java +@Bean +public VendorDatabaseIdProvider databaseIdProvider() { + VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); + Properties properties = new Properties(); + properties.setProperty("SQL Server", "sqlserver"); + properties.setProperty("DB2", "db2"); + properties.setProperty("Oracle", "oracle"); + properties.setProperty("MySQL", "mysql"); + databaseIdProvider.setProperties(properties); + return databaseIdProvider; +} + +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource, DatabaseIdProvider databaseIdProvider) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + factoryBean.setDatabaseIdProvider(databaseIdProvider); + return factoryBean.getObject(); +} +``` + +NOTE +1.3.0より、`configuration` プロパティが追加されました。このプロパティには、MyBatisのXML設定ファイルを使わずに`Configuration`インスタンスを直接指定することができます。 +次の例を見てください。 + +```xml + + + + + + + + +``` + +Javaでは、同等のコードは次のようになります。 + +```java +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + + return factoryBean.getObject(); +} +``` + +## Java Configurationサンプル + +上記で説明したプロパティを組み合わせた設定クラスの完全な例は以下の通りです。 + +```java +@Configuration +public class MyBatisConfig { + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + + // Setting mapper locations + factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sample/config/mappers/**/*.xml")); + + // Setting configuration property + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + + return factoryBean.getObject(); + } +} +``` + +NOTE +この設定クラスは、Springコンテナによってスキャンされるパッケージ内に配置する必要があります(例:メインアプリケーションパッケージ内)。クラス名自体(例: `MyBatisConfig` )は任意で、必要なのは `@Configuration` アノテーションだけです。 diff --git a/src/site/ja/markdown/getting-started.md b/src/site/ja/markdown/getting-started.md new file mode 100644 index 0000000000..0fd4ba6b8c --- /dev/null +++ b/src/site/ja/markdown/getting-started.md @@ -0,0 +1,100 @@ + +# スタートガイド + +この章では、MyBatis-Spring のインストール・設定手順と、トランザクション処理を含むシンプルなアプリケーションの構築する方法について説明します。 + +## インストール + +MyBatis-Spring を使うためには、 `mybatis-spring-${project.version}.jar` と依存するライブラリをクラスパスに追加するだけで OK です。 + +Maven をお使いの場合は、 pom.xml に次の dependency を追加してください。 + +```xml + + org.mybatis + mybatis-spring + ${project.version} + +``` + +## クイックセットアップ + +MyBatis と Spring を組み合わせて使う場合、Spring の Application Context 内に少なくとも `SqlSessionFactory` と一つ以上の Mapper インターフェイスを定義する必要があります。 + +MyBatis-Spring では `SqlSessionFactory` の生成に `SqlSessionFactoryBean` を使います。この Factory Bean を設定するため、Spring の 設定ファイルに次の Bean を追加してください。 + +```xml + + + +``` + +```java +@Configuration +public class MyBatisConfig { + @Bean + public SqlSessionFactory sqlSessionFactory() throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource()); + return factoryBean.getObject(); + } +} +``` + +`SqlSessionFactory` が `DataSource` を必要としている点に注意してください。どのような `DataSource` でも構いません。通常の手順で設定してください。 + +Mapper インターフェイスが次のように定義されている場合... + +```java +public interface UserMapper { + @Select("SELECT * FROM users WHERE id = #{userId}") + User getUser(@Param("userId") String userId); +} +``` + +`MapperFactoryBean` を使ってこのインターフェイスを Spring に登録する場合、以下のように設定します。 + +```xml + + + + +``` + +ここで指定した Mapper は、実装クラスではなく **インターフェイス** である必要がありますので注意してください。 +この例では、アノテーションを使って SQL を指定していますが、Mapper XML ファイルを使うこともできます。 + +上記のように設定しておけば、あとは他の Spring Bean と同様にビジネス/サービス層のオブジェクトにインジェクト(注入)することができます。 +`MapperFactoryBean` は `SqlSession` の生成とクローズを行います。 +もし Spring のトランザクション内で実行された場合は、トランザクション終了時にセッションがコミットあるいはロールバックされます。 +最後にもう一点、全ての例外は Spring の `DataAccessException` に変換されます。 + +Java Configurationを使用する場合: + +```java +@Configuration +public class MyBatisConfig { + @Bean + public UserMapper userMapper() throws Exception { + SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory()); + return sqlSessionTemplate.getMapper(UserMapper.class); + } +} +``` + +MyBatis のデータメソッドは、一行だけで実行可能となります。 + +```java +public class FooServiceImpl implements FooService { + + private final UserMapper userMapper; + + public FooServiceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public User doSomeBusinessStuff(String userId) { + return this.userMapper.getUser(userId); + } +} +``` diff --git a/src/site/ja/markdown/index.md b/src/site/ja/markdown/index.md new file mode 100644 index 0000000000..e893f7800f --- /dev/null +++ b/src/site/ja/markdown/index.md @@ -0,0 +1,55 @@ + +# イントロダクション + +## MyBatis-Spring とは? + +MyBatis-Spring によって MyBatis と Spring をシームレスに連携させることができます。このライブラリを使えば、MyBatis のステートメントを Spring のトランザクション内で実行することもできますし、Mapper や `SqlSession` の生成、他の Bean への注入、MyBatis の例外から Spring の `DataAccessException` への変換、さらには MyBatis や Spring, MyBatis-Spring に依存しないコードでアプリケーションを構築することも可能になります。 + +## 動機 + +Spring バージョン 2 は iBATIS バージョン 2 しかサポートしていません。Spring 3 の開発時に MyBatis 3 への対応が検討されました(こちらの [チケット](https://jira.springsource.org/browse/SPR-5991) 参照)が、Spring 3 が MyBatis 3 よりも前に正式リリースを迎えたため、残念ながら実装は見送られました。Spring 開発陣としては、未リリースの MyBatis 3 に合わせたコードをリリースしたくなかったという事情があり、Spring 側での正式対応は保留となっていました。MyBatis コミュニティの中で Spring 対応への要望が強かったため、有志によって Spring との連携を行う MyBatis のサブプロジェクトが立ち上げられました。 + +## 動作条件 + +MyBatis-Spring を利用するためには、MyBatis と Spring の用語について理解しておくことが重要です。このドキュメントには MyBatis や Spring についての説明や基本設定といった情報は含まれていません。 + +MyBatis-Spring は以下のバージョンを必要とします。 + +| MyBatis-Spring | MyBatis | Spring Framework | Spring Batch | Java | +|----------------| --- |------------------|--------------| --- | +| **4.0** | 3.5+ | 7.0+ | 6.0+ | Java 17+ | +| **3.0** | 3.5+ | 6.x | 5.x | Java 17+ | +| **2.1** | 3.5+ | 5.x | 4.x | Java 8+ | +| ~~**2.0**~~ | ~~3.5+~~ | ~~5.x~~ | ~~4.x~~ | ~~Java 8+~~ | +| ~~**1.3**~~ | ~~3.4+~~ | ~~3.2.2+~~ | ~~2.1+~~ | ~~Java 6+~~ | + +## 謝辞 + +このプロジェクトの実現にご協力頂いた次の方々に感謝します(アルファベット順): +Eduardo Macarron, Hunter Presnall, Putthiphong Boonphong(コーディング、テスト、ドキュメント作成); +Andrius Juozapaitis, Giovanni Cuccu, Mike Lanyon, Raj Nagappan, Tomas Pinos(コントリビューション); +Simone Tripodi(メンバーを集め、MyBatis のサブプロジェクトとしてまとめてくれました) +このプロジェクトは彼らの協力なしには実現できなかったでしょう。 + +## このドキュメントの改善にご協力ください... + +このドキュメントの中で誤りや特定の機能に関する記述が抜けていることに気づいたら、詳しく調べてドキュメントを更新して頂けると助かります。 + +このマニュアルのソースは markdown 形式で、[プロジェクトの Git リポジトリ](https://github.com/mybatis/spring/tree/master/src/site) で配布されています。 +リポジトリをフォーク、それらを更新します、とプルリクエストを送信します。 + +このドキュメントを必要としている人、つまりあなたこそが最高の著者なのです! + +## Translations + +MyBatis-Spring は以下の言語の翻訳を用意しています。 + + + +母国語でMyBatis Springのリファレンスを読んでみませんか? ぜひドキュメントを母国語へ翻訳するためのIssue(パッチ)を作成してください! diff --git a/src/site/ja/markdown/mappers.md b/src/site/ja/markdown/mappers.md new file mode 100644 index 0000000000..9d9a7f09a6 --- /dev/null +++ b/src/site/ja/markdown/mappers.md @@ -0,0 +1,188 @@ + +# Mapper の注入 + +MyBatis-Spring がスレッドセーフな Mapper を生成してくれるので、`SqlSessionDaoSupport` や `SqlSessionTemplate` を使って手動で DAO オブジェクトを生成するコードは不要となります。 +生成された Mapper は他の Bean に注入することができます。 + +```xml + + + +``` + +アプリケーション側の処理では、注入された Mapper のメソッドを呼び出すだけです。 + +```java +public class FooServiceImpl implements FooService { + + private final UserMapper userMapper; + + public FooServiceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public User doSomeBusinessStuff(String userId) { + return this.userMapper.getUser(userId); + } +} +``` + +このコードには `SqlSession` や MyBatis への参照が含まれていない点に注目してください。また、セッションの生成やオープン、クローズも MyBatis-Spring が処理してくれるため不要となります。 + + +## Mapper の登録 + +Mapper を Bean として登録する方法は、Spring の設定を XML ファイルを使って行う場合と Spring 3.0 以降で導入された Java Config (= `@Configuration`) を使う場合で異なります。 + +### XML で設定する場合 + +XML ファイルを使って Spring を設定する場合、次のように `MapperFactoryBean` のエントリーを追加することで Mapper を Spring Bean として登録することができます。 + +```xml + + + + +``` + +ここで指定した UserMapper のインターフェイスと同じクラスパスに MyBatis の XML Mapper ファイルが配置されている場合は自動的にロードされます。 +XML Mapper が異なるクラスパスに配置されている場合を除けば、MyBatis の設定ファイルでこの Mapper を指定する必要はありません。 +詳しくは `SqlSessionFactoryBean` の [`configLocation`](factorybean.html) プロパティの説明を参照してください。 + +`MapperFactoryBean` を登録する際は `SqlSessionFactory` あるいは `SqlSessionTemplate` のどちらかを指定する必要があります。 +指定対象のプロパティは、それぞれ `sqlSessionFactory` と `sqlSessionTemplate` です。 +両方が指定された場合、 `SqlSessionFactory` の指定は無視され、`SqlSessionTemplate` の登録時に指定した Session Factory が使われます。 + +### Java Config で設定する場合 + +```java +@Configuration +public class MyBatisConfig { + @Bean + public MapperFactoryBean userMapper() throws Exception { + MapperFactoryBean factoryBean = new MapperFactoryBean<>(UserMapper.class); + factoryBean.setSqlSessionFactory(sqlSessionFactory()); + return factoryBean; + } +} +``` + + +## Mapper の自動検出 + +上で説明した方法では Mapper を個別に指定していましたが、MyBatis-Spring では特定のクラスパスに含まれる Mapper を自動検出させることもできます。 + +これには3通りの方法があります。 + +* `` 要素を使う。 +* `@MapperScan` アノテーションを使う。 +* Spring の XML 設定ファイルに `MapperScannerConfigurer` のエントリーを追加する。 + +`` または `@MapperScan` を使う場合は MyBatis-Spring 1.2.0 以降が必要です。また `@MapperScan` を使う場合は Spring 3.1 以降が必要となります。 + +2.0.2以降では、Mapperの自動検出機能は、Mapper Beanの遅延初期化の有効/無効を制御するオプション(`lazy-initialization`)をサポートします。 +このオプションを追加する動機は、Spring Boot 2.2でサポートされた遅延初期化を制御する機能をサポートすることです。このオプションのデフォルトは`false`です(遅延初期化を使用しません)。 +開発者がMapper Beanを遅延初期化したい場合は、明示的にこのオプションを`true`に設定する必要があります。 + +重要 +遅延初期化機能を使用する場合は、開発者は以下の制限を理解しておく必要があります。以下の条件のいずれかに一致する場合、通常あなたのアプリケーションで遅延初期化機能を使用することはできません。 + +* ``(`@One`) and ``(`@Many`)を利用して、**他のMapperの**ステートメントを参照している場合 +* ``を利用して、**他のMapperの**フラグメントをインクルードしている場合 +* ``(`@CacheNamespaceRef`)を利用して、**他のMapperの**キャッシュを参照している場合 +* `]]> - -

設定例:

- - - - -
]]> - - reader() { - return new MyBatisPagingItemReaderBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") - .build(); -}]]> - -

さらに複雑な例:

- - - - - - - - -]]> - - dateBasedCriteriaReader( - @Value("#{@datesParameters}") Map datesParameters) throws Exception { - return new MyBatisPagingItemReaderBuilder() - .sqlSessionFactory(batchReadingSessionFactory()) - .queryId("com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot") - .parameterValues(datesParameters) - .pageSize(200) - .build(); -} - -@StepScope -@Bean -public Map datesParameters( - @Value("#{jobExecutionContext['EXTRACTION_START_DATE']}") LocalDate yesterday, - @Value("#{jobExecutionContext['TODAY_DATE']}") LocalDate today, - @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}") LocalDate firstDayOfTheMonth, - @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}") LocalDate firstDayOfThePreviousMonth) { - Map map = new HashMap<>(); - map.put("yesterday", yesterday); - map.put("today", today); - map.put("first_day_of_the_month", firstDayOfTheMonth); - map.put("first_day_of_the_previous_month", firstDayOfThePreviousMonth); - return map; -}]]> - -

- The previous example makes use of a few different things: -

- -
    -
  • sqlSessionFactory: あなた自身の sessionFactory を reader に指定することができます。複数のデータベースから読み取る場合は有用かも知れません。
  • -
  • queryId: レコード取得時に実行されるクエリの ID を指定します。異なるネームスペースに属するクエリを指定する場合はネームスペースの指定を忘れないようにしてください。
  • -
  • parameterValues: クエリ実行時に使用する追加のパラメーターを Map 形式で渡すことができます。上の例では Spring がjobExecutionContext から SpEL 式を使って取得した値をもとに構築した Map を指定しています。 - MyBatis の Mapper ファイルでは Map のキーをパラメーター名として指定します(例: yesterday を指定する場合は #{yesterday,jdbcType=TIMESTAMP} のように指定します)。 - jobExecutionContext と Spring EL 式を利用するため、Map および reader はどちらも step スコープ内で構築されているという点に注意してください。 - また、MyBatis の Type Handler が正しく設定されていれば、この例のように JodaTime のようなカスタムのインスタンスを引数として渡すこともできます。
  • -
  • pageSize: バッチ処理のチャンクサイズを指定します。
  • -
- - - - -

- This bean is an ItemReader that reads records from a database using a cursor. -

- -

- NOTE To use this bean you need at least MyBatis 3.4.0 or a newer version. -

- -

- It executes the query specified as the setQueryId property to retrieve requested data - by using the method selectCursor(). - Each time a read() method is called it will return the next element of the cursor until no more - elements are left. -

- -

- The reader will use a separate connection so the select statement does no participate in any transactions created - as part of the step processing. -

- -

When using the cursor you can just execute a regular query:

- - SELECT id, name, job FROM employees ORDER BY id ASC -]]> - -

Follows below a sample configuration snippet:

- - - - -
]]> - - reader() { - return new MyBatisCursorItemReaderBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") - .build(); -}]]> - - - - - -

- SqlSessionTemplate のバッチ機能を使って渡された一連のアイテムを処理する ItemWriter です。 - SqlSessionFactoryExecutorType.BATCH を使って設定する必要があります。 -

- -

- write() の呼び出し時に実行するステートメントの ID を指定しておく必要があります。 - write() はトランザクション内で呼び出されることを前提としています。 -

- -

設定例:

- - - - -
]]> - - writer() { - return new MyBatisBatchItemWriterBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .statementId("com.my.name.space.batch.EmployeeMapper.updateEmployee") - .build(); -}]]> - -

Composite Writer を使って複数のテーブルに書き込む(注意事項あり)

- -

このテクニックを使うには MyBatis 3.2 以降が必要です。それ以前のバージョンには 問題 があるため、Writer が期待通りに動作しません。 -

- -

まず、Interaction と1対1の関係にある InteractionMetadata と、これらとは独立した VisitorInteraction および CustomerInteraction を保持する Item クラスを用意します。 -

- - - -

CompositeItemWriter の設定では、それぞれのオブジェクトの writer を順番に呼び出すように設定します。 - この例では Interaction をアップデートするためのキーを取得するため、InteractionMetadata を先に書き込む必要があります。 -

- - - - - - - - - - - - -]]> - - interactionsItemWriter() { - CompositeItemWriter compositeItemWriter = new CompositeItemWriter(); - List> writers = new ArrayList<>(4); - writers.add(visitorInteractionsWriter()); - writers.add(customerInteractionsWriter()); - writers.add(interactionMetadataWriter()); - writers.add(interactionWriter()); - compositeItemWriter.setDelegates(writers); - return compositeItemWriter; -}]]> - -

それぞれの writer を必要に応じて設定します。例えば InteractionInteractionMetadata の設定は次のようになります。 -

- - -]]> - -

reader の場合と同様、 statementId はネームスペースを含むステートメント ID です。

- -

Mapper ファイルにステートメントを定義します。

- - - - - - -]]> - -

はじめに insertInteractionMetadata が呼ばれ、その際に取得した主キーを使って親となる InteractioninsertInteraction を使って書き込むことができます。 -

- -

JDBC ドライバによって動作が異なるので注意が必要です。例えば MySQL の JDBC ドライバは作成された全ての行の ID を返しますが、H2 バージョン 1.3.168 ではバッチモードでも最後に作成された行の ID のみが返されます。 -

- - - - \ No newline at end of file diff --git a/src/site/ja/xdoc/boot.xml b/src/site/ja/xdoc/boot.xml deleted file mode 100644 index 75d6ef922b..0000000000 --- a/src/site/ja/xdoc/boot.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - MyBatis-Spring | Spring Boot - Eduardo Macarron - - - -
-

- 詳細は MyBatis Spring-boot-stater のドキュメントを参照してください。 -

-
- -
diff --git a/src/site/ja/xdoc/factorybean.xml b/src/site/ja/xdoc/factorybean.xml deleted file mode 100644 index f7911f6440..0000000000 --- a/src/site/ja/xdoc/factorybean.xml +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - MyBatis-Spring | SqlSessionFactoryBean - Hunter Presnall - Eduardo Macarron - Iwao AVE! - - - -
-

- 基となる MyBatis では、SqlSessionFactory をビルドする際 SqlSessionFactoryBuilder を使いましたが、MyBatis-Spring では、SqlSessionFactoryBean を使います。 -

- - -

- Spring の XML 設定ファイルに次の Bean を定義することで Factory Bean を生成することができます。 -

- - -]]> -

- SqlSessionFactoryBean は Spring の FactoryBean インターフェイス(the Spring documentation(Core Technologies -Customizing instantiation logic with a FactoryBean-) を参照してください)を実装しています。 - これはつまり、最終的に Spring が生成するのは SqlSessionFactoryBean ではなく、Factory の getObject() メソッドによって返されるオブジェクトであるということです。
- 上記の設定では、Spring は SqlSessionFactory を生成し、sqlSessionFactory という名前の Bean として登録します。 - これに相当する Java のコードは下記のようになるでしょう。 -

- - - -

- 通常 MyBatis-Spring を使う場合、SqlSessionFactoryBean や対応する SqlSessionFactory を直接利用する必要はありません。 - sqlSessionFactory は MapperFactoryBeanSqlSessionDaoSupport を継承した他の DAO にインジェクト(注入)されます。 -

-
- - - -

- SqlSessionFactory で必須のプロパティは JDBC の DataSource のみです。 - どのような DataSource でも構いません。Spring でデータベース接続を定義する通常の手順で定義してください。 -

- -

- configLocation は、MyBatis の XML 設定ファイルの場所を指定する際に使用します。
- これは、例えば基になる MyBatis の設定の一部を変更したい場合などに必要となります。 - よくあるのは <settings><typeAliases> などの設定です。 -

- -

- ここで指定する設定ファイルは、完全な MyBatis 設定ファイルである必要はありません。 - 環境、データソース、MyBatis のトランザクションマネージャーに関する設定は無視されます。 - SqlSessionFactoryBean は、独自にカスタマイズした MyBatis Environment を生成し、必要に応じてこれらの値を設定するようになっています。 -

- -

- 設定ファイルの指定が必要とされるもう一つの例は、MyBatis の Mapper XML ファイルが Mapper クラスとは別のクラスパスに存在する場合です。 - このような構成にする場合、次のどちらかの方法で設定することができます。 - 最初の方法は、MyBatis の設定ファイルの <mappers> で各 XML ファイルのクラスパスを指定する方法です。 - そしてもう一つは、Factory Bean の mapperLocations を使った方法です。 -

- -

- mapperLocations プロパティは Resource Location のリストを取り、ここで MyBatis の XML Mapper ファイルの場所を指定することができます。 - Ant スタイルのパターン文字列を使って特定のディレクトリ内の全ファイルを指定したり、内包するディレクトリを再帰的に検索対象にすることもできます。次の例を見てください。 -

- - - - -]]> - -

- このように指定すると、クラスパス内の sample.config.mappers パッケージと、そのサブパッケージに含まれる全ての MyBatis Mapper XML ファイルがロードされます。 -

- -

- Container-Managed トランザクションを利用する環境では、transactionFactoryClass プロパティが必須となります。 - 「トランザクション」章の該当する節を参照してください。 -

- -

- 複数のデータベースを使用する場合は、databaseIdProviderプロパティを設定する必要があります。 -

- - - - - sqlserver - db2 - oracle - mysql - - - - - - - - -]]> - -

- NOTE - 1.3.0より、configurationプロパティが追加されました。 - このプロパティには、MyBatisのXML設定ファイルを使わずにConfigurationインスタンスを直接指定することができます。 - 次の例を見てください。 -

- - - - - - - - -]]> - -
-
- -
\ No newline at end of file diff --git a/src/site/ja/xdoc/getting-started.xml.vm b/src/site/ja/xdoc/getting-started.xml.vm deleted file mode 100644 index 058ee57435..0000000000 --- a/src/site/ja/xdoc/getting-started.xml.vm +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - MyBatis-Spring | スタートガイド - Hunter Presnall - Eduardo Macarron - Iwao AVE! - - - -
-

- この章では、MyBatis-Spring のインストール・設定手順と、トランザクション処理を含むシンプルなアプリケーションの構築する方法について説明します。 -

- - -

- MyBatis-Spring を使うためには、 - mybatis-spring-${project.version}.jar と依存するライブラリをクラスパスに追加するだけで OK です。 -

-

- Maven をお使いの場合は、 - pom.xml に次の dependency を追加してください。 -

- - org.mybatis - mybatis-spring - ${project.version} -]]> -
- - -

- MyBatis と Spring を組み合わせて使う場合、Spring の Application Context 内に少なくとも - SqlSessionFactory と一つ以上の Mapper インターフェイスを定義する必要があります。 -

- -

- MyBatis-Spring では - SqlSessionFactory の生成に SqlSessionFactoryBean を使います。この Factory Bean を設定するため、Spring の 設定ファイルに次の Bean を追加してください。 -

- - - -]]> - - - -

- SqlSessionFactoryDataSource を必要としている点に注意してください。 - どのような DataSource でも構いません。通常の手順で設定してください。 -

- -

- Mapper インターフェイスが次のように定義されている場合... -

- - -

- MapperFactoryBean を使ってこのインターフェイスを Spring に登録する場合、以下のように設定します。 -

- - - -]]> - -

- ここで指定した Mapper は、実装クラスではなく インターフェイス である必要がありますので注意してください。
- この例では、アノテーションを使って SQL を指定していますが、Mapper XML ファイルを使うこともできます。 -

- -

- 上記のように設定しておけば、あとは他の Spring Bean と同様にビジネス/サービス層のオブジェクトにインジェクト(注入)することができます。 - MapperFactoryBeanSqlSession の生成とクローズを行います。
- もし Spring のトランザクション内で実行された場合は、トランザクション終了時にセッションがコミットあるいはロールバックされます。 - 最後にもう一点、全ての例外は Spring の DataAccessException に変換されます。 -

- -

- Java Configurationを使用する場合: -

- - - -

- MyBatis のデータメソッドは、一行だけで実行可能となります。 -

- -
-
- -
diff --git a/src/site/ja/xdoc/index.xml b/src/site/ja/xdoc/index.xml deleted file mode 100644 index 5eb6ca49d3..0000000000 --- a/src/site/ja/xdoc/index.xml +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - MyBatis-Spring | イントロダクション - Hunter Presnall - Eduardo Macarron - Iwao AVE! - - - -
- -

- MyBatis-Spring によって MyBatis と Spring をシームレスに連携させることができます。このライブラリを使えば、MyBatis のステートメントを Spring のトランザクション内で実行することもできますし、Mapper や SqlSession の生成、他の Bean への注入、MyBatis の例外から Spring の DataAccessException への変換、さらには MyBatis や Spring, MyBatis-Spring に依存しないコードでアプリケーションを構築することも可能になります。 -

-
- - -

- Spring バージョン 2 は iBATIS バージョン 2 しかサポートしていません。Spring 3 の開発時に MyBatis 3 への対応が検討されました(こちらの チケット 参照)が、Spring 3 が MyBatis 3 よりも前に正式リリースを迎えたため、残念ながら実装は見送られました。Spring 開発陣としては、未リリースの MyBatis 3 に合わせたコードをリリースしたくなかったという事情があり、Spring 側での正式対応は保留となっていました。MyBatis コミュニティの中で Spring 対応への要望が強かったため、有志によって Spring との連携を行う MyBatis のサブプロジェクトが立ち上げられました。 -

-
- - -

- MyBatis-Spring を利用するためには、MyBatis と Spring の用語について理解しておくことが重要です。 - このドキュメントには MyBatis や Spring についての説明や基本設定といった情報は含まれていません。 -

-

- MyBatis-Spring は以下のバージョンを必要とします。 -

- - - - - - - - - - - - - - - - - - - - - - - - - - -
- MyBatis-Spring - - MyBatis - - Spring Framework - - Spring Batch - - Java -
- 2.0 - - 3.4+ - - 5.0+ - - 4.0+ - - Java 8+ -
- 1.3 - - 3.4+ - - 3.2.2+ - - 2.1+ - - Java 6+ -
-
- - -

- このプロジェクトの実現にご協力頂いた次の方々に感謝します(アルファベット順): - Eduardo Macarron, Hunter Presnall, Putthiphong Boonphong(コーディング、テスト、ドキュメント作成); - Andrius Juozapaitis, Giovanni Cuccu, Mike Lanyon, Raj Nagappan, Tomas Pinos(コントリビューション); - Simone Tripodi(メンバーを集め、MyBatis のサブプロジェクトとしてまとめてくれました)
- このプロジェクトは彼らの協力なしには実現できなかったでしょう。 -

-
- - -

- このドキュメントの中で誤りや特定の機能に関する記述が抜けていることに気づいたら、詳しく調べてドキュメントを更新して頂けると助かります。 -

-

- このマニュアルのソースは xdoc 形式で、プロジェクトの Git リポジトリで配布されています。
- リポジトリをフォーク、それらを更新します、とプルリクエストを送信します。 -

-

- このドキュメントを必要としている人、つまりあなたこそが最高の著者なのです! -

-
- - -

MyBatis-Spring は以下の言語の翻訳を用意しています。

- -

母国語でMyBatis Springのリファレンスを読んでみませんか? ぜひドキュメントを母国語へ翻訳するためのIssue(パッチ)を作成してください!

-
-
- - -
diff --git a/src/site/ja/xdoc/mappers.xml b/src/site/ja/xdoc/mappers.xml deleted file mode 100644 index 1b788a385f..0000000000 --- a/src/site/ja/xdoc/mappers.xml +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - MyBatis-Spring | Mapper をインジェクト(注入)する - Hunter Presnall - Eduardo Macarron - Iwao AVE! - - - -
-

- MyBatis-Spring がスレッドセーフな Mapper を生成してくれるので、SqlSessionDaoSupportSqlSessionTemplate を使って手動で DAO オブジェクトを生成するコードは不要となります。 - 生成された Mapper は他の Bean に注入することができます。 -

- - - -]]> - -

- アプリケーション側の処理では、注入された Mapper のメソッドを呼び出すだけです。 -

- -

- このコードには SqlSession や MyBatis への参照が含まれていない点に注目してください。 - また、セッションの生成やオープン、クローズも MyBatis-Spring が処理してくれるため不要となります。 -

- - -

- Mapper を Bean として登録する方法は、Spring の設定を XML ファイルを使って行う場合と Spring 3.0 以降で導入された Java Config (= @Configuration) を使う場合で異なります。

- -

XML で設定する場合

- -

- XML ファイルを使って Spring を設定する場合、次のように MapperFactoryBean のエントリーを追加することで Mapper を Spring Bean として登録することができます。 -

- - - -]]> - -

- ここで指定した UserMapper のインターフェイスと同じクラスパスに MyBatis の XML Mapper ファイルが配置されている場合は自動的にロードされます。 - XML Mapper が異なるクラスパスに配置されている場合を除けば、MyBatis の設定ファイルでこの Mapper を指定する必要はありません。 - 詳しくは SqlSessionFactoryBeanconfigLocation プロパティの説明を参照してください。 -

- -

- MapperFactoryBean を登録する際は SqlSessionFactory あるいは SqlSessionTemplate のどちらかを指定する必要があります。 - 指定対象のプロパティは、それぞれ sqlSessionFactorysqlSessionTemplate です。 - 両方が指定された場合、 SqlSessionFactory の指定は無視され、SqlSessionTemplate の登録時に指定した Session Factory が使われます。 -

- -

Java Config で設定する場合

- -

- Java Config を使って Spring を設定する場合は、次のようにして SqlSessionTemplate から直接 Mapper を取得することができます。 -

- - - -

- ここで、MyBatis のデフォルトの SqlSession から取得した Mapper を返してはいけません。 - なぜなら、この Mapper はスレッドセーフではなく、取得元の SqlSession がクローズされると破棄されてしまうからです。 - 上記の例のように、必ず SqlSessionTemplate を使って Mapper を取得するようにしてください。 -

-
- - -

- 上で説明した方法では Mapper を個別に指定していましたが、MyBatis-Spring では特定のクラスパスに含まれる Mapper を自動検出させることもできます。 -

- -

- これには3通りの方法があります。 -

-
    -
  • <mybatis:scan/> 要素を使う。
  • -
  • @MapperScan アノテーションを使う。
  • -
  • Spring の XML 設定ファイルに MapperScannerConfigurer のエントリーを追加する。
  • -
- -

<mybatis:scan/> または @MapperScan を使う場合は MyBatis-Spring 1.2.0 以降が必要です。また @MapperScan を使う場合は Spring 3.1 以降が必要となります。

- -

<mybatis:scan/>

- -

- <mybatis:scan/> は、Spring の <context:component-scan/> が Bean を検索するのと良く似た方法で Mapper を検出します。 -

- -

XML 設定の例:

- - - - - - - -]]> - -

- base-package 属性で Mapper ファイルを含むパッケージを指定します。 - セミコロンまたはカンマ区切りで複数のパッケージを指定することもできます。 - また、指定されたパッケージが内包するパッケージも検索対象となります。 -

- -

- ここでは <mybatis:scan/>SqlSessionFactorySqlSessionTemplate を指定していませんが、この場合は Autowired 可能な MapperFactoryBean が自動的に生成されます。 - ただし、複数の DataSource を利用する場合は Autowired に頼ることができないので、 factory-ref または template-ref 属性を使って適切な Bean を指定する必要があります。 -

- -

- <mybatis:scan/> を使う場合、マーカーインターフェイスまたはアノテーションを指定して Mapper をフィルタリングすることができます。 - 検出対象のアノテーションを指定するには annotation 属性を使います。 - 検出対象の Mapper が実装するインターフェイスを指定する場合は marker-interface 属性を使います。 - 両方の属性が指定された場合、どちらかの条件を満たすインターフェイスが Mapper として登録されます。 - デフォルトではどちらも null となっており、base-package で指定したパッケージに含まれるすべてのインターフェイスが Mapper としてロードされます。 -

- -

- 検出された Mapper は、Spring の自動検出コンポーネントに対するデフォルト命名規則によって Bean 名が決められます(the Spring reference document(Core Technologies -Naming autodetected components-) を参照してください)。 - アノテーションによる指定がない場合はクラス名の先頭を小文字にした文字列が Bean 名となりますが、@Component あるいは JSR-330 の @Named アノテーションを使って Bean 名を明示的に指定することもできます。 - 先に説明した annotation 属性で org.springframework.stereotype.Componentjavax.inject.Named (Java 6 以降を利用している場合のみ)を指定すれば、検出時のマーカーと Bean 名の指定を1つのアノテーションで兼ねることができます。 - 同じ目的で独自に定義したアノテーションを使うこともできますが、このアノテーション自体に @Component か @Named を付加しておく必要があります。 -

- -

- NOTE Spring 標準の <context:component-scan/> を使って Mapper を検出することはできません。 - Mapper はインターフェイスなので、各 Mapper に対する MapperFactoryBean の生成方法が分かっていないと Spring Bean として登録することができないのです。 -

- -

@MapperScan

- -

- Java Config を使って Spring を設定しているのなら、<mybatis:scan/> よりも @MapperScan を使う方が気に入ると思います。 -

- -

@MapperScan アノテーションは次のように使用します。

- - - -

- このアノテーションは前章で説明した <mybatis:scan/> と全く同じ要領で Mapper の検出を行います。 - 引数 markerInterface, annotationClass を使えば検出対象のマーカーインターフェイスとアノテーションを指定することもできますし、sqlSessionFactory, sqlSessionTemplateSqlSessionFactorySqlSessionTemplate を指定することができます。 -

- -

MapperScannerConfigurer

- -

- MapperScannerConfigurerBeanDefinitionRegistryPostProcessor として定義されているので、従来の XML による設定で通常の Bean として登録することができます。 - MapperScannerConfigurer の登録は次のように行います。 -

- - -]]> - -

- 特定の sqlSessionFactory または sqlSessionTemplate を指定する場合は、 Bean を参照ではなく 名前で 指定する必要があるので、ref ではなく value を使います。 -

- ]]> - -

- NOTE MyBatis-Spring 1.0.2 までは有効なプロパティは sqlSessionFactoryBeansqlSessionTemplateBean のみでしたが、 MapperScannerConfigurerPropertyPlaceholderConfigurer よりも先に読み込まれるためエラーの原因となっていました。 - この問題を回避するため、これらのプロパティの使用は非推奨となり、新たに追加された sqlSessionFactoryBeanNamesqlSessionTemplateBeanName を使うことが推奨されています。 -

-
-
- -
diff --git a/src/site/ja/xdoc/sample.xml b/src/site/ja/xdoc/sample.xml deleted file mode 100644 index 263092304b..0000000000 --- a/src/site/ja/xdoc/sample.xml +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - MyBatis-Spring | Sample Code - Eduardo Macarron - - - -
- -

- NOTE - See JPetstore 6 demo to know about how to use Spring with a full web application server. -

- -

- You can check out sample code from the MyBatis-Spring repo: -

-

- Any of the samples can be run with JUnit 5. -

-

- The sample code shows a typical design where a transactional service gets domain objects from a data access layer. -

-

- FooService.java acts as the service: -

- -

- It is a transactional bean, so when the method is called, the transaction is started - and the transaction is committed when the method ends without throwing an uncaught exception. - Notice that transactional behaviour is configured with the - @Transactional - attribute. This is not required; any other way provided by Spring can be used to demarcate - your transactions. -

-

- This service calls a data access layer built with MyBatis. This layer - consists on a just an interface UserMapper.java - that will be used with a dynamic proxy built by MyBatis at - runtime and injected into the service by Spring. -

- -

- Note that, for the sake of simplicity we used the interface UserMapper.java for the DAO scenario - where a DAO is built with an interface and a implementation though in this case it would have been more - adequate to use an interface called UserDao.java instead. -

-

- We will see different ways to find the mapper interface, register it to Spring and inject it into the service bean: -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Scenarios
Sample testDescription
- SampleMapperTest.java - - Shows you the base configuration based on a MapperFactoryBean - that will dynamically build an implementation for UserMapper -
- SampleScannerTest.java - - Shows how to use the MapperScannerConfigurer so all the mappers in a project are autodiscovered. -
- SampleSqlSessionTest.java - - Shows how to hand code a DAO using a Spring managed SqlSession - and providing your own implementation UserDaoImpl.java. -
- SampleEnableTest - - Shows how to use Spring's @Configuration with the @MapperScann annotation so - mappers are autodiscovered. -
- SampleNamespaceTest - - Shows how to use the custom MyBatis XML namespace. -
- SampleJavaConfigTest.java - - Shows how to use Spring's @Configuration to create MyBatis beans manually. -
- SampleJobJavaConfigTest.java - - Shows how to use ItemReader and ItemWriter on Spring Batch using Java Configuration. -
- SampleJobXmlConfigTest.java - - Shows how to use ItemReader and ItemWriter on Spring Batch using XML Configuration. -
-

- Please take a look at the different applicationContext.xml files to see MyBatis-Spring in action. -

- -
- -
- diff --git a/src/site/ja/xdoc/sqlsession.xml b/src/site/ja/xdoc/sqlsession.xml deleted file mode 100644 index c0e4bfa776..0000000000 --- a/src/site/ja/xdoc/sqlsession.xml +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - MyBatis-Spring | Using an SqlSession - Hunter Presnall - Eduardo Macarron - Iwao AVE! - - - -
-

- MyBatis では SqlSessionFactory を使って SqlSession を生成しました。 - そして取得したセッションを使って Mapped Statement を実行し、接続をコミットあるいはロールバックした後、最終的に不要となったセッションをクローズする、というのが一連の流れでした。 - MyBatis-Spring では、SPring のトランザクション設定に基づいて自動的にコミット、ロールバック、クローズされるスレッドセーフな SqlSession が注入されるので、直接 SqlSessionFactory を使う必要はありません。 -

- - -

- SqlSessionTemplate は MyBatis-Spring で最も重要なクラスです。 - このクラスが MyBatis の SqlSession を管理して、SQL メソッドの実行や例外の変換を行なっています。 - このクラスは既存のコードで使用している SqlSession の代わりに使うことを前提に SqlSession インターフェイスを実装しています。 - SqlSessionTemplate はスレッドセーフで、複数の DAO, Mapper 間で共有することができます。 -

- -

- getMapper() から返された Mapper のメソッドも含めて、SQL メソッドを呼び出す場合、確実に現在の Spring トランザクションに付加された SqlSession が使われるようにするのも SqlSessionTemplate の仕事です。 - それ以外にも、セッションのクローズや状況に応じたコミットあるいはロールバックといったライフサイクルの管理、更には MyBatis の例外を Spring の DataAccessException に変換する処理も行います。 -

- -

- SqlSessionTemplate は Spring が管理するトランザクション内で実行され、また Spring によってインジェクトされる複数の Mapper から呼び出すことができるようにスレッドセーフとなっているので、常にこのクラスを MyBatis のデフォルト実装である DefaultSqlSession の代わりに使用するべきです。 - 同一アプリケーション内でこれら2つのクラスを混在させて使用するとデータの不整合などの問題が発生する可能性があります。 -

- -

- SqlSessionTemplate を生成する際は、SqlSessionFactory をコンストラクタ引数として渡します。 -

- - -]]> - - - -

- この Bean は、直接あなたの DAO Bean にインジェクト(注入)することができます。 - 注入対象の Bean には SqlSession プロパティを定義しておく必要があります。 -

- -

- そして、以下のようにして SqlSessionTemplate を注入します。 -

- - -]]> - -

- SqlSessionTemplate には、ExecutorType を引数に取るコンストラクタも定義されています。 - このコンストラクタを使うと、例えばバッチ処理を行う SqlSession を取得することができます。 -

- - - -]]> - - - -

- これで実行されるステートメントは全てバッチ処理の対象となります。 - DAO クラス中では、例えば次のように書くことができます。 -

- users) { - for (User user : users) { - sqlSession.insert("org.mybatis.spring.sample.mapper.UserMapper.insertUser", user); - } -}]]> - -

- デフォルト以外の ExecutorType を使用する場合にのみ SqlSessionFactory の Bean を定義する際に2つの引数を指定する必要があります。 -

- -

- この初期化方式を使用する場合の注意点として、このメソッドが呼び出される時点で異なる ExecutorType で実行されているトランザクションが存在していてはならない、という制限があります。 - そのためには、異なる ExecutorType が指定された SqlSessionTemplate への呼び出しを、それぞれが独立したトランザクション内で実行する(例えば PROPAGATION_REQUIRES_NEW を指定しておく)か、あるいは完全にトランザクションの外で実行するようにしてください。 -

-
- - -

- SqlSessionDaoSupportSqlSession を提供する抽象クラスです。 - getSqlSession() を呼び出すことで、SQL メソッドを実行するための SqlSessionTemplate を取得することができます。 -

- -

- 普通は MapperFactoryBean を使った方がコード量が少なくて済みますが、DAO の中で MyBatis 以外の処理を行うため実装クラスが必要となる場合には便利なクラスです。 -

- -

- SqlSessionDaoSupport を使用する際は、sqlSessionFactory または sqlSessionTemplate をセットする必要があります。 - もし両方のプロパティがセットされた場合、sqlSessionFactory は無視されます。 -

- -

- SqlSessionDaoSupport のサブクラスである UserDaoImpl を Spring Bean として定義する例を挙げておきます。 -

- - -]]> -
-
- -
diff --git a/src/site/ja/xdoc/transactions.xml b/src/site/ja/xdoc/transactions.xml deleted file mode 100644 index 50c8408836..0000000000 --- a/src/site/ja/xdoc/transactions.xml +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - MyBatis-Spring | トランザクション - Hunter Presnall - Eduardo Macarron - Iwao AVE! - - - -
-

- これは MyBatis-Spring を使う主な理由の一つでもありますが、MyBatis-Spring を使うと MyBatis の処理を Spring が管理するトランザクションの一部として実行できるようになります。 - MyBatis-Spring は、MyBatis のために新しいトランザクションマネージャーを生成するのではなく、Spring が生成した DataSourceTransactionManager を利用します。 -

-

- Spring のトランザクションマネージャーが定義されていれば、通常の手順で Spring のトランザクションを利用することができます。 - @Transactional アノテーションと AOP 形式での指定、どちらも利用可能です。 - トランザクション内では SqlSession が一つ生成され、トランザクションの生存期間中はこの SqlSession が使用されます。 - このセッションは、トランザクション完了時にコミットあるいはロールバックされます。 -

-

- MyBatis-Spring ではトランザクションは透過的に管理されるので、あなたの DAO クラスにコードを追加する必要はありません。 -

- - -

- Spring の 設定ファイルで DataSourceTransactionManager を生成するだけで、Spring のトランザクション処理が有効となります。 -

- - -]]> - - - -

- ここで指定する DataSource は、通常 Spring で利用される JDBC DataSource であればどのようなデータソースでも構いません。 - 例えば、コネクションプールや JNDI 経由で取得した DataSource などです。 -

-

- ただし、トランザクションマネージャーに対して指定する DataSource は、SqlSessionFactoryBean に対して指定したものと同じでなくてはなりません。もし別のデータソースを指定した場合、トランザクション機能は正しく動作しません。 -

-
- - -

- JEEコンテナを利用していて、Spring の処理を CMT (Container Managed Transaction) の一部として利用したい場合、JtaTransactionManager あるいはそのコンテナ固有のサブクラスを使って Spring を設定する必要があります。 - 最も簡単なのは、Spring のトランザクション名前空間 又は JtaTransactionManagerFactoryBean を使う方法です。 -

- ]]> - - - -

- このように設定しておくと MyBatis は、CMT を使うように設定された他の Spring リソースと同じように動作します。 - Spring は、既存のコンテナ管理されたトランザクションがあれば、そのトランザクションに SqlSession を付加して利用します。 - もしトランザクションを要求する処理が呼び出された時点で開始されたトランザクションがなければ、Spring が新しいコンテナ管理されたトランザクションを開始します。 -

-

- CMT は使いたいが、Spring のトランザクション管理は利用したくないという場合、Spring のトランザクションマネージャーを定義してはいけません。 - またこの場合、MyBatis 側で生成された ManagedTransactionFactory を使うように SqlSessionFactoryBean を設定する必要があります。 -

- - - - - -]]> - - - -
- - -

- MyBatis の SqlSession では、トランザクションをプログラム的に制御するためのメソッドが用意されています。 - しかし、MyBatis-Spring では、あなたの Bean にインジェクト(注入)されるのは Spring が管理する SqlSession あるいは Mapper です。 - つまり、トランザクションを制御するのは常に Spring でなくてはなりません。 -

-

- Spring が管理している SqlSession に対して SqlSession.commit(), SqlSession.rollback(), SqlSession.close() を呼び出すことはできません。 - もしこれらのメソッドを呼び出した場合、UnsupportedOperationException がスローされます。 - あなたの Bean に注入される Mapper クラスでは、これらのメソッドは隠蔽されています。 -

-

- Spring が管理するトランザクションの外側で SqlSession のデータメソッドあるいは Mapper メソッドを呼び出した場合、JDBC 接続に対する auto-commit の設定に関わらず、変更は直ちにコミットされます。 -

-

- もしあなたがトランザクションをプログラム的に制御したいのであれば、the Spring reference document(Data Access -Programmatic transaction management-) を参照してください。 - 以下のコードは、PlatformTransactionManager を使ってトランザクションを手動で制御する例です。 -

- - -

- TransactionTemplate を使用して commitrollback メソッドを省略することもできます。 -

- - { - userMapper.insertUser(user); - return null; -});]]> - -

- ここでは Mapper を使っていますが、SqlSession を使うこともできます。 -

-
-
- -
diff --git a/src/site/ja/xdoc/using-api.xml b/src/site/ja/xdoc/using-api.xml deleted file mode 100644 index 8a0715ecf4..0000000000 --- a/src/site/ja/xdoc/using-api.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - MyBatis-Spring | MyBatis API の使用 - Hunter Presnall - Eduardo Macarron - Iwao AVE! - - - -
-

- MyBatis-Spring を使っている場合でも、直接 MyBatis API を呼び出すことができます。 - Spring の設定で SqlSessionFactoryBean を使って SqlSessionFactory を生成すれば、コード内で使用することができます。 -

- - -

- この方法を使う場合は注意が必要です。 - なぜなら、誤った使い方をすると実行時エラーや、最悪の場合データの不整合といった問題を生じる可能性があるからです。 - MyBatis API を直接使用する場合、次のような点に注意してください。 -

-
    -
  • -

    - API の呼び出しは Spring で管理されているトランザクション内では実行されません。 -

    -
  • -
  • -

    - SqlSession が Spring のトランザクションマネージャーが使っているのと同じ DataSource を使っていて、既に進行中のトランザクションが存在している場合、このコードは例外を投げます。 -

    -
  • -
  • -

    - MyBatis の DefaultSqlSession はスレッドセーフではありません。 - もしあなたの Bean に注入した場合、エラーが発生します。 -

    -
  • -
  • -

    - DefaultSqlSession を使って生成した Mapper もスレッドセーフとはなりません。 - もしあなたの Bean に注入した場合、エラーが発生します。 -

    -
  • -
  • -

    - SqlSession は常に finally ブロックでクローズする必要があります。 -

    -
  • -
-
- -
diff --git a/src/site/ko/markdown/README.md b/src/site/ko/markdown/README.md new file mode 100644 index 0000000000..03cf1d3fa2 --- /dev/null +++ b/src/site/ko/markdown/README.md @@ -0,0 +1,18 @@ +# 목차 + +이 페이지는 GitHub에서 인덱스를 렌더링하기 위한 것입니다. + +> **NOTE:** +> +> 링크 대상은 maven-site-plugin을 사용하여 html로 변환된다는 가정하에 지정되므로 GitHub의 렌더링에서 끊어진 앵커가 있습니다. + +* [소개](./index.md) +* [시작하기](./getting-started.md) +* [SqlSessionFactoryBean](./factorybean.md) +* [트랜잭션](./transactions.md) +* [SqlSession 사용](./sqlsession.md) +* [매퍼 주입](./mappers.md) +* [Spring Boot](./boot.md) +* [MyBatis API 사용](./using-api.md) +* [Spring Batch](./batch.md) +* [샘플 코드](./sample.md) diff --git a/src/site/ko/markdown/batch.md b/src/site/ko/markdown/batch.md new file mode 100644 index 0000000000..26911daef1 --- /dev/null +++ b/src/site/ko/markdown/batch.md @@ -0,0 +1,353 @@ + +# Spring Batch + +마이바티스 스프링 연동 모듈의 1.1.0버전에서는 스프링 배치 애플리케이션을 만들기 위해 세 개의 빈을 제공한다. +세 개의 빈은 `MyBatisPagingItemReader` 와 `MyBatisCursorItemReader` 와 MyBatisBatchItemWriter이다. + +또한 2.0.0 버전에서는 Java Configuration 을 지원하는 다음의 세 가지 Builder class 를 제공한다. +`MyBatisPagingItemReaderBuilder`, `MyBatisCursorItemReaderBuilder` 그리고 `MyBatisBatchItemWriterBuilder` 이다. + +중요 +이 문서는 [스프링 배치](http://static.springsource.org/spring-batch/) 에 대한 것으로 마이바티스 배치 `SqlSession` 을 다루지는 않는다. +배치 세션에 대해서는 [SqlSession 사용](sqlsession.html) 에서 좀 더 다루었다. + +# MyBatisPagingItemReader + +이 빈은 마이바티스로 페이지를 처리하는 형태로 데이터베이스 데이터를 읽어오는 `ItemReader`이다. + +요청된 데이터를 가져오기 위해 `setQueryId` 프로퍼티에 명시된 쿼리를 실행한다. +쿼리는 `setPageSize` 프로퍼티에 명시된 크기만큼 데이터를 가져오도록 실행된다. +`read()` 메서드를 사용하면 필요할 때 현재 위치에서 정해진 수만큼 더 추가 데이터를 가져온다. +reader는 몇 가지의 표준적인 쿼리 파라미터를 제공하고 명명된 쿼리의 SQL은 요청된 크기만큼의 데이터를 만들기 위해 파라미터의 일부 혹은 모두 사용한다. +여기서 사용 가능한 파라미터이다. + +* `_page`: 읽을 페이지 수(0부터 시작) +* `_pagesize`: 페이지의 크기, 이를테면 리턴하는 로우 수 +* `_skiprows`: `_page` 와 `_pagesize`를 곱한 결과 + +각각의 파라미터는 selet구문에서 다음처럼 매핑 될 수 있다. + +```xml + +``` + +다음의 코드는 샘플 설정이다. + +```xml + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisPagingItemReader reader() { + return new MyBatisPagingItemReaderBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") + .build(); + } +} +``` + +**좀 더 복잡한 예제를 보자.** + +```xml + +``` +```xml + + + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @StepScope + @Bean + public MyBatisPagingItemReader dateBasedCriteriaReader( + @Value("#{@datesParameters}") Map datesParameters) throws Exception { + return new MyBatisPagingItemReaderBuilder() + .sqlSessionFactory(batchReadingSessionFactory()) + .queryId("com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot") + .parameterValues(datesParameters) + .pageSize(200) + .build(); + } + + @StepScope + @Bean + public Map datesParameters( + @Value("#{jobExecutionContext['EXTRACTION_START_DATE']}") LocalDate yesterday, + @Value("#{jobExecutionContext['TODAY_DATE']}") LocalDate today, + @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}") LocalDate firstDayOfTheMonth, + @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}") LocalDate firstDayOfThePreviousMonth) { + Map map = new HashMap<>(); + map.put("yesterday", yesterday); + map.put("today", today); + map.put("first_day_of_the_month", firstDayOfTheMonth); + map.put("first_day_of_the_previous_month", firstDayOfThePreviousMonth); + return map; + } +} +``` + +앞의 예제와는 몇 가지 차이점이 있다. + +* `sqlSessionFactory`: reader에 별도로 구현한 sessionFactory를 지정할 수 있다. 이 옵션은 다양한 데이터베이스에서 데이터를 읽을 때 유용하다. +* `queryId`: 여러 개의 테이블에서 데이터를 읽어야 하고 서로 다른 쿼리를 사용한다면 서로 다른 네임스페이스를 가진 매퍼 파일을 사용하는 게 좋을 수 있다. 쿼리를 알아볼때, 매퍼 파일의 네임스페이스를 잊지 말아야 한다. +* `parameterValues`: 이 맵을 사용해서 추가로 파라미터를 전달할 수 있다. 위 예제는 `jobExecutionContext`에서 값들을 가져오는 SpEL표현식을 사용하는 맵을 사용하고 있다. + 맵의 키는 매퍼파일에서 마이바티스가 사용할 것이다. (예: *yesterday* 는 `#{yesterday,jdbcType=TIMESTAMP}` 로 사용될 수 있다.). + 맵과 reader 모두 `jobExecutionContext`에서 SpEL표현식을 사용하기 위해 `step` 스코프를 사용한다. 마이바티스의 타입핸들러가 제대로 설정이 되었다면 JodaTime날짜를 맵을 사용해서 파라미터로 넘길 수 있다. +* `pageSize`: 배치가 청크 크기가 지정된 형태로 처리가 되면 reader에 이 값을 전달하는게 적절하다. + +## MyBatisCursorItemReader + +이 빈은 cursor 를 사용하여 데이터베이스에서 레코드를 읽는 `ItemReader` 이다. + +중요 +이 빈을 사용하려면 최소한 MyBatis 3.4.0 이나 그 이상이어야 한다. + +`setQueryId` 속성으로 지정된 쿼리를 실행하여 `selectCursor()` 메서드를 사용하여 요청 된 데이터를 검색한다. `read()` 메서드가 호출 될 때마다 요소가 더 이상 남아 있지 않을 때까지 cursor 의 다음 요소를 반환한다. + +reader 는 별도의 connection 을 사용하므로 select 문은 step processing 일부로 생성된 트랜잭션에 속하지 않는다. + +cursor 를 사용할 때 다음과 같이 일반 쿼리를 실행할 수 있다. + +```xml + +``` + +아래는 샘플 설정이다. + +```xml + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisCursorItemReader reader() { + return new MyBatisCursorItemReaderBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") + .build(); + } +} +``` + +# MyBatisBatchItemWriter + +모든 아이템을 배치로 구문을 일괄 실행하기 위해 `SqlSessionTemplate`에서 배치로 작업을 처리하는 `ItemWriter`이다. `SqlSessionFactory`는 `BATCH` 실행자로 설정할 필요가 있다. + +사용자는 `write()` 메서드가 호출될 때 실행될 매핑구문 아이디를 제공해야 한다. `write()` 메서드는 트랜잭션 내에서 호출되는 것으로 예상된다. + +다음의 코드는 샘플 설정이다. + +```xml + + + + +``` +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisBatchItemWriter writer() { + return new MyBatisBatchItemWriterBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .statementId("com.my.name.space.batch.EmployeeMapper.updateEmployee") + .build(); + } +} +``` + + +**Converting a item that read using ItemReader to an any parameter object:** + +By default behavior, the `MyBatisBatchItemWriter` passes a item that read using `ItemReader` (or convert by `ItemProcessor`) to the MyBatis(`SqlSession#update()`) as the parameter object. +If you want to customize a parameter object that passes to the MyBatis, you can realize to use the `itemToParameterConverter` option. +For example using `itemToParameterConverter` option, you can passes any objects other than the item object to the MyBatis. +Follows below a sample: + +At first, you create a custom converter class (or factory method). The following sample uses a factory method. + +```java +public class ItemToParameterMapConverters { + public static Converter> createItemToParameterMapConverter(String operationBy, LocalDateTime operationAt) { + return item -> { + Map parameter = new HashMap<>(); + parameter.put("item", item); + parameter.put("operationBy", operationBy); + parameter.put("operationAt", operationAt); + return parameter; + }; + } +} +``` + +At next, you write a sql mapping. + +```xml + +``` + +At last, you configure the `MyBatisBatchItemWriter`. + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisBatchItemWriter writer() throws Exception { + return new MyBatisBatchItemWriterBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .statementId("org.mybatis.spring.sample.mapper.PersonMapper.createPerson") + .itemToParameterConverter(createItemToParameterMapConverter("batch_java_config_user", LocalDateTime.now())) + .build(); + } +} +``` + +```xml + + + + + + + + + + +``` + +**여러 개의 테이블에 데이터를 쓰려면 한꺼번에 처리할 수 있도록 만든 writer(몇 가지 규칙을 가지고)를 사용하자.** + +이 기능은 마이바티스 3.2이상에서만 사용할 수 있다. 이전의 버전에서는 예상과 다르게 동작하는데 그 내용은 [이슈](http://code.google.com/p/mybatis/issues/detail?id=741)를 참고하면 된다. + +배치가 관계를 가지는 데이터나 여러 개의 데이터베이스를 다루는 것처럼 복잡한 데이터를 작성할 때 필요하다면 insert구문이 한 개에 테이블에만 데이터를 넣을 수 있다는 사실만 피하면 가능하기도 하다. +이런 복잡한 데이터를 처리하기 위해 writer가 작성하는 아이템(Item)을 준비해야 한다. 다음의 기술을 사용하면 단순한 관계를 가진 데이터나 관계가 없는 테이블을 처리하는 아이템에서 사용할 수 있다. + +이러한 방법으로 스프링 배치 아이템은 모든 레코드를 *다룰 것이다*. +여기에는 1:1 *관계*를 가지는 *InteractionMetadata*, 관계가 없는 두 개의 로우는 *VisitorInteraction* 와 *CustomerInteraction*이 있다. +이 각각의 객체는 다음과 같이 볼 수 있다. + +```java +public class InteractionRecordToWriteInMultipleTables { + private final VisitorInteraction visitorInteraction; + private final CustomerInteraction customerInteraction; + private final Interaction interaction; + // ... +} +``` +```java +public class Interaction { + private final InteractionMetadata interactionMetadata; +} +``` + +그리고 스프링 설정에는 각각의 레코드를 처리하기 위해 특별히 설정된 전용(delegates) writer를 사용하는 `CompositeItemWriter`가 있다. + +```xml + + + + + + + + + + + + +``` +```java +@Bean +@Configuration +public class BatchAppConfig { + public CompositeItemWriter interactionsItemWriter() { + CompositeItemWriter compositeItemWriter = new CompositeItemWriter(); + List> writers = new ArrayList<>(4); + writers.add(visitorInteractionsWriter()); + writers.add(customerInteractionsWriter()); + writers.add(interactionMetadataWriter()); + writers.add(interactionWriter()); + compositeItemWriter.setDelegates(writers); + return compositeItemWriter; + } +} +``` + +각각의 전용(delegate) writer는 필요할 만큼 설정할 수 있다. 예를 들면 *Interaction* 과 *InteractionMetadata*를 위한 writer가 있다. + +```xml + +``` +```xml + +``` + +reader와 동일하게 `statementId`는 네임스페이스를 가진 구문을 가리킬 수 있다. + +매퍼 파일에서 구문은 다음과 같은 방법으로 각각의 레코드를 위해 만들어져있다. + +```xml + + + +``` +```xml + + + +``` + +먼저 `insertInteractionMetadata`가 호출될 것이고 update구문은 jdbc드라이버에 의해(`keyProperty` 와 `keyColumn`) 생성된 아이디들을 리턴하기 위해 설정되었다. +`InteractionMetadata` 객체가 이 쿼리에 의해 업데이트되면 다음의 쿼리는 `insertInteraction`를 통해 상위객체인 `Interaction`를 작성하기 위해 사용될 수 있다. + +***방금 언급한 내용에 관련하여 JDBC드라이버가 똑같이 동작하지 않을 수 있다. +이 글을 쓰는 시점에 H2 드라이버 1.3.168버전(`org.h2.jdbc.JdbcStatement#getGeneratedKeys`를 보라)만 배치모드에서 마지막 인덱스를 리턴한다. +반면에 MySQL 드라이버는 기대한 것과 동일하게 동작하고 모든 아이디를 리턴한다.*** diff --git a/src/site/ko/markdown/boot.md b/src/site/ko/markdown/boot.md new file mode 100644 index 0000000000..7e932a7fa6 --- /dev/null +++ b/src/site/ko/markdown/boot.md @@ -0,0 +1,4 @@ + +# 스프링 부트 사용하기 + +자세한 내용은 [MyBatis Spring-boot-starter](http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure) 하위 프로젝트 문서를 참조하십시오. diff --git a/src/site/ko/markdown/factorybean.md b/src/site/ko/markdown/factorybean.md new file mode 100644 index 0000000000..282cb8b2ec --- /dev/null +++ b/src/site/ko/markdown/factorybean.md @@ -0,0 +1,178 @@ + +# SqlSessionFactoryBean + +마이바티스만 사용하면, `SqlSessionFactory`는 `SqlSessionFactoryBuilder`를 사용해서 생성한다. +마이바티스 스프링 연동 모듈에서는, `SqlSessionFactoryBean`가 대신 사용된다. + +## 설정 + +팩토리 빈을 생성하기 위해, 스프링 XML 설정 파일에 다음 설정을 추가하자. + +```xml + + + +``` + +`SqlSessionFactoryBean` 은 스프링의 `FactoryBean` 인터페이스를 구현(see [the Spring documentation(Core Technologies -Customizing instantiation logic with a FactoryBean-)](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-extension-factorybean))한다는 점을 알아야 한다. +이 설정은 스프링이 `SqlSessionFactoryBean` 자체를 생성하는 것이 **아니라** 팩토리에서 `getObject()` 메서드를 호출한 결과를 리턴한다는 것을 의미한다. +이 경우, 스프링은 애플리케이션 시작 시점에 `SqlSessionFactory`를 빌드하고 `sqlSessionFactory` 라는 이름으로 저장한다. 자바에서 코드로 표현하면 아래와 같다. + +```java +@Configuration +public class MyBatisConfig { + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + return factoryBean.getObject(); + } +} +``` + +일반적인 마이바티스 스프링 사용법에서는, `SqlSessionFactoryBean`이나 관련된 `SqlSessionFactory`를 직접 사용할 필요가 없다. +대신 세션 팩토리가 `MapperFactoryBean`나 `SqlSessionDaoSupport`를 확장하는 다른 DAO에 주입될 것이다. + +## 속성 + +`SqlSessionFactory`는 JDBC `DataSource`의 필수 프로퍼티가 필요하다. 어떤 `DataSource`라도 상관없고 다른 스프링 데이터베이스 연결처럼 설정되어야만 한다. + +하나의 공통적인 프로퍼티는 마이바티스 XML 설정 파일의 위치를 지정하기 위해 사용되는 `configLocation`이다. 이 프로퍼티를 설정하는 것은 디폴트 설정을 가진 마이바티스 설정을 변경해야 할 경우 뿐이다. +대개는 ``과 `` 섹션을 변경하는 경우이다. + +설정 파일이 마이바티스 설정을 완전히 다룰 필요는 없다. 어떤 환경, 어떤 데이터소스 그리고 마이바티스 트랜잭션 관리자가 **무시**될 수도 있다. +`SqlSessionFactoryBean` 는 필요에 따라 이 값들을 설정하여 자체적인 MyBatis `Environment` 를 만든다. + +설정 파일이 필요한 다른 이유는 마이바티스 XML파일이 매퍼 클래스와 동일한 클래스패스에 있지 않은 경우이다. 이 설정을 사용하면 두 가지 옵션이 있다. +첫 번째는 마이바티스 설정파일에 `` 섹션을 사용해서 XML파일의 클래스패스를 지정하는 것이다. 두 번째는 팩토리 빈의 `mapperLocations` 프로퍼티를 사용하는 것이다. + +`mapperLocations` 프로퍼티는 매퍼에 관련된 자원의 위치를 나열한다. 이 프로퍼티는 마이바티스의 XML매퍼 파일들의 위치를 지정하기 위해 사용될 수 있다. +디렉터리 아래 모든 파일을 로드하기 위해 앤트(Ant) 스타일의 패턴을 사용할 수도 있고 가장 상위 위치를 지정하는 것으로 재귀적으로 하위 경로를 찾도록 할 수도 있다. 예를 들어보면 다음과 같다. + +```xml + + + + +``` + +In Java, the equivalent code would be: + +```java +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sample/config/mappers/**/*.xml")); + return factoryBean.getObject(); +} +``` + +이 설정은 `sample.config.mappers` 패키지 아래와 그 하위 패키지를 모두 검색해서 마이바티스 매퍼 XML파일을 모두 로드할 것이다. + +컨테이너 관리 트랜잭션을 사용하는 환경에서 필요한 하나의 프로퍼티는 `transactionFactoryClass` 이다. 이에 관련해서는 트랜잭션을 다루는 장에서 볼 수 있다. + +만약 multi-db 기능을 사용한다면 다음과 같이 `databaseIdProvider` 속성을 설정해야 한다. + +```xml + + + + sqlserver + db2 + oracle + mysql + + + +``` +````xml + + + + + +```` + +In Java, the equivalent code would be: + +```java +@Bean +public VendorDatabaseIdProvider databaseIdProvider() { + VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); + Properties properties = new Properties(); + properties.setProperty("SQL Server", "sqlserver"); + properties.setProperty("DB2", "db2"); + properties.setProperty("Oracle", "oracle"); + properties.setProperty("MySQL", "mysql"); + databaseIdProvider.setProperties(properties); + return databaseIdProvider; +} + +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource, DatabaseIdProvider databaseIdProvider) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + factoryBean.setDatabaseIdProvider(databaseIdProvider); + return factoryBean.getObject(); +} +``` + +NOTE +1.3.0 버전부터 `configuration` 속성이 추가되었다. 다음과 같이 MyBatis XML 설정 파일 없이 `Configuration` 인스턴스를 직접 지정할 수 있습니다. + +```xml + + + + + + + + +``` + +In Java, the equivalent code would be: + +```java +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + + return factoryBean.getObject(); +} +``` + +## Java Configuration Example + +Here is a complete example of a configuration class that combines the properties described above. + +```java +@Configuration +public class MyBatisConfig { + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + + // Setting mapper locations + factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sample/config/mappers/**/*.xml")); + + // Setting configuration property + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + + return factoryBean.getObject(); + } +} +``` + +NOTE +This configuration class must be located within a package scanned by the Spring container (e.g., within the main application package). The class name itself (e.g., `MyBatisConfig`) is arbitrary; only the `@Configuration` annotation is required. diff --git a/src/site/ko/markdown/getting-started.md b/src/site/ko/markdown/getting-started.md new file mode 100644 index 0000000000..553dda2ba5 --- /dev/null +++ b/src/site/ko/markdown/getting-started.md @@ -0,0 +1,98 @@ + +# 시작하기 + +이 장은 마이바티스 스프링 연동 모듈을 설치하고 셋팅하는 방법에 대해 간단히 보여준다. 그리고 트랜잭션을 사용하는 간단한 애플리케이션을 만드는 방법까지 다룰 것이다. + +## 설치 + +마이바티스 스프링 연동 모듈을 사용하기 위해서, 클래스패스에 `mybatis-spring-${project.version}.jar`를 포함시켜야 한다. + +메이븐을 사용하고 있다면 pom.xml에 다음처럼 의존성을 추가하면 된다. + +```xml + + org.mybatis + mybatis-spring + ${project.version} + +``` + +## 빠른 설정 + +마이바티스를 스프링과 함께 사용하려면 스프링의 애플리케이션 컨텍스트에 적어도 두 개를 정의해 줄 필요가 있다. +두 가지는 `SqlSessionFactory`와 한 개 이상의 매퍼 인터페이스이다. + +마이바티스 스프링 연동 모듈에서, `SqlSessionFactoryBean`은 `SqlSessionFactory`를 만들기 위해 사용된다. 팩토리 빈을 설정하기 위해, 스프링 설정 파일에 다음 설정을 추가하자. + +```xml + + + +``` + +```java +@Configuration +public class MyBatisConfig { + @Bean + public SqlSessionFactory sqlSessionFactory() throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource()); + return factoryBean.getObject(); + } +} +``` + +`SqlSessionFactory`는 `DataSource`를 필요로 하는 것을 알아둘 필요가 있다. 어떤 `DataSource`도 상관없지만 다른 스프링의 데이터베이스 연결과 동일하게 설정되어야 한다. + +매퍼 인터페이스가 다음처럼 정의되었다고 가정해보자. + +```java +public interface UserMapper { + @Select("SELECT * FROM users WHERE id = #{userId}") + User getUser(@Param("userId") String userId); +} +``` + +UserMapper인터페이스는 다음처럼 `MapperFactoryBean`을 사용해서 스프링에 추가된다. + +```xml + + + + +``` + +매퍼는 **반드시** 구현체 클래스가 아닌 인터페이스로 정의되어야 한다. 예를 들어, 애노테이션이 SQL을 명시하기 위해 사용되지만 마이바티스 매퍼 XML파일 또한 사용될 수 있다. + +한 번만 설정하면, 다른 스프링 빈에 주입하는 같은 방법으로 비즈니스/서비스 객체에 매퍼를 직접 주입할 수 있다. `MapperFactoryBean`은 `SqlSession`을 생성하고 닫는 작업을 잘 다룬다. +실행 중인 스프링 트랜잭션이 있다면, 트랜잭션이 완료되는 시점에 커밋이나 롤백이 될 것이다. 마지막으로 예외가 발생하면 스프링의 `DataAccessException`예외가 발생한다. + +자바로 설정하면 다음과 같다. + +```java +@Configuration +public class MyBatisConfig { + @Bean + public UserMapper userMapper() throws Exception { + SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory()); + return sqlSessionTemplate.getMapper(UserMapper.class); + } +} +``` + +마이바티스의 데이터 관련 메서드는 호출하는 것은 한줄이면 된다. + +```java +public class FooServiceImpl implements FooService { + + private final UserMapper userMapper; + + public FooServiceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public User doSomeBusinessStuff(String userId) { + return this.userMapper.getUser(userId); + } +} +``` diff --git a/src/site/ko/markdown/index.md b/src/site/ko/markdown/index.md new file mode 100644 index 0000000000..8a064f14e2 --- /dev/null +++ b/src/site/ko/markdown/index.md @@ -0,0 +1,57 @@ + +# 소개 + +## MyBatis-Spring 은 무엇일까? + +마이바티스 스프링 연동 모듈은 마이바티스와 스프링을 편하고 간단하게 연동한다. 이 모듈은 마이바티스로 하여금 스프링 트랜잭션에 쉽게 연동되도록 처리한다. 게다가 마이바티스 매퍼와 `SqlSession`을 다루고 다른 빈에 주입시켜준다. +마이바티스 예외를 스프링의 `DataAccessException`로 변환하기도 하고 마이바티스, 스프링 또는 마이바티스 스프링 연동 모듈에 의존성을 없애기도 한다. + +## 동기 부여 + +스프링 2.x은 아이바티스 2.x만을 지원한다. 스프링 3.x에서 마이바티스 3.x를 지원하기 위한 시도가 진행 중이다. (스프링의 이슈관리 시스템인 [이슈](https://jira.springsource.org/browse/SPR-5991) 를 보라.) +불행하게도 스프링 3의 개발이 마이바티스 3.0의 정식 릴리즈 전에 개발이 완료되었다. 그래서 스프링팀은 릴리즈가 안 된 마이바티스 코드를 함께 릴리즈하는 것을 원하지 않았고 실제적인 스프링 지원을 기다릴 수밖에 없었다. +스프링의 마이바티스 지원에 대한 관심으로 인해, 마이바티스 커뮤니티는 재결합하는 형태로 결정을 내고 대신 마이바티스의 하위 프로젝트 형태로 스프링 연동 프로젝트를 추가한다. + +## 필요 조건 + +마이바티스 스프링 연동을 시작하기 전에, 마이바티스와 스프링의 용어를 맞추는 일이 굉장히 중요했다. 이 문서는 배경지식이나 기본적인 셋업방법 그리고 마이바티스와 스프링의 설정에 대한 튜토리얼 등은 제공하지 않는다. + +MyBatis-Spring requires following versions: + +| MyBatis-Spring | MyBatis | Spring Framework | Spring Batch | Java | +|----------------| --- |------------------|--------------| --- | +| **4.0** | 3.5+ | 7.0+ | 6.0+ | Java 17+ | +| **3.0** | 3.5+ | 6.x | 5.x | Java 17+ | +| **2.1** | 3.5+ | 5.x | 4.x | Java 8+ | +| ~~**2.0**~~ | ~~3.5+~~ | ~~5.x~~ | ~~4.x~~ | ~~Java 8+~~ | +| ~~**1.3**~~ | ~~3.4+~~ | ~~3.2.2+~~ | ~~2.1+~~ | ~~Java 6+~~ | + +## 감사 인사 + +이 프로젝트가 실제로 만들어지게 도와준 모든 특별한 분들에게 정말 감사한다. +알파벳 순서로 보면, 코딩 및 테스트 그리고 문서화를 담당했던 Eduardo Macarron, Hunter Presnall, Putthiphong Boonphong; +그외 다양한 프로젝트 기여자인 Andrius Juozapaitis, Giovanni Cuccu, Mike Lanyon, Raj Nagappan, Tomas Pinos; +그리고 마이바티스에 하위 프로젝트로 가져올 수 있도록 많은 것을 찾아준 Simone Tripodi 에게 감사한다. ;) +이들이 없었다면 이 프로젝트는 존재하지 않았을 것이다. + +## 이 문서가 더 나아지도록 도와주세요… + +만약에 어떤 방법으로든 이 문서의 취약점이 발견되거나 기능에 대한 문서화가 빠진 부분이 보인다면, 가장 좋은 방법은 먼저 공부해서 자신만의 문서를 작성하는 것이다. + +이 문서의 원본은 markdown 포맷이며 [프로젝트의 Git](https://github.com/mybatis/spring/tree/master/src/site)에서 찾을 수 있다. repository 를 fork 하고, 업데이트하고 pull request 를 보내주십시오. + +당신처럼 이 문서를 읽는 사람들에게 이 문서의 최고의 저자가 될 수 있다! + +## 번역 + +사용자들은 다음의 번역 문서별로 마이바티스 스프링 연동 모듈에 대해 알 수 있다. + + + +위 번역문서에는 없지만 자국의 언어로 문서로 보고 싶다면, 자국의 언어로 된 문서를 만들어서 우리에게 보내달라. diff --git a/src/site/ko/markdown/mappers.md b/src/site/ko/markdown/mappers.md new file mode 100644 index 0000000000..d537db1e65 --- /dev/null +++ b/src/site/ko/markdown/mappers.md @@ -0,0 +1,184 @@ + +# 매퍼 주입 + +`SqlSessionDaoSupport` 나 `SqlSessionTemplate` 를 직접적으로 사용하는 데이터 접근 객체(DAO)를 생성하기보다, 마이바티스 스프링 연동 모듈은 다른 빈에 직접 주입할 수 있는 쓰레드에 안전한 매퍼를 생성할 수 있다. + +```xml + + + +``` + +한번 주입하고 나면 매퍼는 애플리케이션 로직에서 사용할 수 있는 준비가 된다. + +```java +public class FooServiceImpl implements FooService { + + private final UserMapper userMapper; + + public FooServiceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public User doSomeBusinessStuff(String userId) { + return this.userMapper.getUser(userId); + } +} +``` + +이 코드를 보면 `SqlSession`이나 마이바티스 객체가 보이지 않는다. 게다가 세션을 생성하거나 열고 닫을 필요도 없어 보인다. 마이바티스 스프링 연동 모듈이 알아서 처리할 것이다. + + +## 매퍼 등록하기 + +매퍼를 등록하는 방법은 기존의 전통적인 XML설정법을 사용하거나 새로운 3.0 이후의 자바설정(일명 `@Configuration`)을 사용하느냐에 따라 다르다. + +### XML설정 사용 + +매퍼는 다음처럼 XML설정 파일에 `MapperFactoryBean`을 두는 것으로 스프링에 등록된다. + +```xml + + + + +``` + +UserMapper가 매퍼 인터페이스와 같은 경로의 클래스패스에 마이바티스 XML매퍼 파일을 가지고 있다면 `MapperFactoryBean`이 자동으로 파싱할것이다. +매퍼 XML파일을 다른 클래스패스에 두는게 아니라면 마이바티스 설정 파일에 매퍼를 지정할 필요가 없다. 좀 더 세부적인 정보는 `SqlSessionFactoryBean`의 [`configLocation`](factorybean.html) 프로퍼티를 살펴보자. + +`MapperFactoryBean`은 `SqlSessionFactory` 나 `SqlSessionTemplate`가 필요하다. `sqlSessionFactory` 와 `sqlSessionTemplate` 프로퍼티를 셋팅하면 된다. +둘 다 셋팅하면 `SqlSessionFactory`가 무시된다. 세션 팩토리 셋은 `SqlSessionTemplate`이 필요하고 `MapperFactoryBean`는 팩토리를 사용할 것이다. + +### 자바설정 사용 + +```java +@Configuration +public class MyBatisConfig { + @Bean + public MapperFactoryBean userMapper() throws Exception { + MapperFactoryBean factoryBean = new MapperFactoryBean<>(UserMapper.class); + factoryBean.setSqlSessionFactory(sqlSessionFactory()); + return factoryBean; + } +} +``` + + +## 매퍼 스캔 + +하나씩 매퍼를 모두 등록할 필요가 없다. 대신 클래스패스를 지정해서 마이바티스 스프링 연동 모듈의 자동 스캔 기능을 사용할 수 있다. + +자동 스캔을 사용하는 데는 3가지 방법이 있다. + +* `` 엘리먼트 사용 +* `@MapperScan` 애노테이션 사용 +* 스프링 XML파일을 사용해서 `MapperScannerConfigurer`를 등록 + +`` 와 `@MapperScan` 모두 마이바티스 스프링 연동 모듈 1.2.0에서 추가된 기능이다. `@MapperScan` 은 스프링 버전이 3.1이상이어야 한다. + +Since 2.0.2, mapper scanning feature support a option (`lazy-initialization`) that control lazy initialization enabled/disabled of mapper bean. +The motivation for adding this option is supporting a lazy initialization control feature supported by Spring Boot 2.2. +The default of this option is `false` (= not use lazy initialization). If developer want to use lazy initialization for mapper bean, it should be set to the `true` expressly. + +IMPORTANT +If use the lazy initialization feature, the developer need to understand following limitations. If any of following conditions are matches, usually the lazy initialization feature cannot use on your application. + +* When refers to the statement of **other mapper** using ``(`@One`) and ``(`@Many`) +* When includes to the fragment of **other mapper** using `` +* When refers to the cache of **other mapper** using ``(`@CacheNamespaceRef`) +* When refers to the result mapping of **other mapper** using `]]> - -

다음의 코드는 샘플 설정이다.

- - - - -
]]> - - reader() { - return new MyBatisPagingItemReaderBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") - .build(); -}]]> - -

좀더 복잡한 예제를 보자.

- - - - - - - - -]]> - - dateBasedCriteriaReader( - @Value("#{@datesParameters}") Map datesParameters) throws Exception { - return new MyBatisPagingItemReaderBuilder() - .sqlSessionFactory(batchReadingSessionFactory()) - .queryId("com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot") - .parameterValues(datesParameters) - .pageSize(200) - .build(); -} - -@StepScope -@Bean -public Map datesParameters( - @Value("#{jobExecutionContext['EXTRACTION_START_DATE']}") LocalDate yesterday, - @Value("#{jobExecutionContext['TODAY_DATE']}") LocalDate today, - @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}") LocalDate firstDayOfTheMonth, - @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}") LocalDate firstDayOfThePreviousMonth) { - Map map = new HashMap<>(); - map.put("yesterday", yesterday); - map.put("today", today); - map.put("first_day_of_the_month", firstDayOfTheMonth); - map.put("first_day_of_the_previous_month", firstDayOfThePreviousMonth); - return map; -}]]> - -

앞의 예제와는 몇가지 차이점이 있다.

- -
    -
  • sqlSessionFactory: reader에 별도로 구현한 sessionFactory를 지정할 수 있다. - 이 옵션은 다양한 데이터베이스에서 데이터를 읽을때 유용하다.
  • -
  • queryId: 여러개의 테이블에서 데이터를 읽어야 하고 서로 다른 쿼리를 사용한다면 - 서로다른 네임스페이스를 가진 매퍼 파일을 사용하는게 좋을수 있다. - 쿼리를 알아볼때, 매퍼 파일의 네임스페이스를 잊지 말아야 한다.
  • -
  • parameterValues: 이 맵을 사용해서 추가로 파라미터를 전달할 수 있다. - 위 예제는 jobExecutionContext에서 값들을 가져오는 SpEL표현식을 사용하는 맵을 사용하고 있다. - 맵의 키는 매퍼파일에서 마이바티스가 사용할 것이다. (예: yesterday#{yesterday,jdbcType=TIMESTAMP} 로 사용될수 있다.). - 맵과 reader 모두 jobExecutionContext에서 SpEL표현식을 사용하기 위해 step 스코프를 사용한다. - 마이바티스의 타입핸들러가 제대로 설정이 되었다면 JodaTime날짜를 맵을 사용해서 파라미터로 넘길수 있다.
  • -
  • pageSize: 배치가 청크크기가 지정된 형태로 처리가 되면 reader에 이 값을 전달하는게 적절하다.
  • -
- - - - -

- 이 빈은 cursor 를 사용하여 데이터베이스에서 레코드를 읽는 ItemReader 이다. -

- -

- 중요 이 빈을 사용하려면 최소한 MyBatis 3.4.0 이나 그 이상이어야 한다. -

- -

- setQueryId 속성으로 지정된 쿼리를 실행하여 selectCursor() 메서드를 사용하여 요청 된 데이터를 검색한다. - read() 메서드가 호출 될 때마다 요소가 더 이상 남아 있지 않을 때까지 cursor 의 다음 요소를 반환한다. -

- -

- reader 는 별도의 connection 을 사용하므로 select 문은 step processing 일부로 생성된 트랜잭션에 속하지 않는다. -

- -

cursor 를 사용할 때 다음과 같이 일반 쿼리를 실행할 수 있다.

- - SELECT id, name, job FROM employees ORDER BY id ASC -]]> - -

아래는 샘플 설정이다.

- - - - -
]]> - - reader() { - return new MyBatisCursorItemReaderBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") - .build(); -}]]> - - - - - -

모든 아이템을 배치로 구문을 일괄실행하기 위해 SqlSessionTemplate에서 배치로 작업을 처리하는 ItemWriter이다. - SqlSessionFactoryBATCH 실행자로 설정할 필요가 있다.

- -

사용자는 write() 메서드가 호출될때 실행될 매핑구문 아이디를 제공해야 한다. - write() 메서드는 트랜잭션내에서 호출되는 것으로 예상된다.

- -

다음의 코드는 샘플설정이다.

- - - - -
]]> - - writer() { - return new MyBatisBatchItemWriterBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .statementId("com.my.name.space.batch.EmployeeMapper.updateEmployee") - .build(); -}]]> - -

여러개의 테이블에 데이터를 쓰려면 한꺼번에 처리할 수 있도록 만든 writer(몇가지 규칙을 가지고)를 사용하자.

- -

이 기능은 마이바티스 3.2이상에서만 사용할 수 있다. 이전의 버전에서는 예상과 다르게 동작하는데 그 내용은 - 이슈를 참고하면 된다.

- -

배치가 관계를 가지는 데이터나 여러개의 데이터베이스를 다루는 것처럼 복잡한 데이터를 작성할때 필요하다면 - insert구문이 한개에 테이블에만 데이터를 넣을수 있다는 사실만 피하면 가능하기도 하다. - 이런 복잡한 데이터를 처리하기 위해 writer가 작성하는 아이템(Item)을 준비해야 한다. - 다음의 기술을 사용하면 단순한 관계를 가진 데이터나 관계가 없는 테이블을 처리하는 아이템에서 사용할 수 있다.

- -

이러한 방법으로 스프링 배치 아이템은 모든 레코드를 다룰것이다. - 여기에는 1:1 관계를 가지는 InteractionMetadata, - 관계가 없는 두개의 로우는 VisitorInteractionCustomerInteraction이 있다. - 이 각각의 객체는 다음과 같이 볼수 있다.

- - - -

그리고 스프링 설정에는 각각의 레코드를 처리하기위해 특별히 설정된 - 전용(delegates) writer를 사용하는 CompositeItemWriter가 있다.

- - - - - - - - - - - - -
]]> - - interactionsItemWriter() { - CompositeItemWriter compositeItemWriter = new CompositeItemWriter(); - List> writers = new ArrayList<>(4); - writers.add(visitorInteractionsWriter()); - writers.add(customerInteractionsWriter()); - writers.add(interactionMetadataWriter()); - writers.add(interactionWriter()); - compositeItemWriter.setDelegates(writers); - return compositeItemWriter; -}]]> - -

각각의 전용(delegate) writer는 필요할 만큼 설정할 수 있다. - 예를들면 InteractionInteractionMetadata를 위한 writer가 있다.

- - -]]> - -

reader와 동일하게 statementId는 네임스페이스를 가진 구문을 가리킬수 있다.

- -

매퍼 파일에서 구문은 다음과 같은 방법으로 각각의 레코드를 위해 만들어져있다.

- - - - - - -]]> - -

먼저 insertInteractionMetadata가 호출될것이고 - update구문은 jdbc드라이버에 의해(keyPropertykeyColumn) 생성된 아이디들을 리턴하기 위해 설정되었다. - InteractionMetadata 객체가 이 쿼리에 의해 업데이트되면 다음의 쿼리는 - insertInteraction를 통해 상위객체인 Interaction를 작성하기 위해 사용될수 있다.

- -

방금 언급한 내용에 관련하여 JDBC드라이버가 똑같이 동작하지 않을수 있다. - 이 글을 쓰는 시점에 H2 드라이버 1.3.168버전(org.h2.jdbc.JdbcStatement#getGeneratedKeys를 보라)만 배치모드에서 마지막 인덱스를 리턴한다. - 반면에 MySQL 드라이버는 기대한 것과 동일하게 동작하고 모든 아이디를 리턴한다.

- - - - \ No newline at end of file diff --git a/src/site/ko/xdoc/boot.xml b/src/site/ko/xdoc/boot.xml deleted file mode 100644 index 55aad08d5a..0000000000 --- a/src/site/ko/xdoc/boot.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - MyBatis-Spring | Spring Boot - Eduardo Macarron - - - -
-

- 자세한 내용은 MyBatis Spring-boot-stater - 하위 프로젝트 문서를 참조하십시오. -

-
- -
diff --git a/src/site/ko/xdoc/factorybean.xml b/src/site/ko/xdoc/factorybean.xml deleted file mode 100644 index 2a936670a2..0000000000 --- a/src/site/ko/xdoc/factorybean.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - 마이바티스 스프링 연동모듈 | SqlSessionFactoryBean - Hunter Presnall - Eduardo Macarron - 이동국(한국어 번역) - - - -
-

마이바티스만 사용하면, SqlSessionFactorySqlSessionFactoryBuilder를 사용해서 생성한다. - 마이바티스 스프링 연동모듈에서는, SqlSessionFactoryBean가 대신 사용된다.

- - -

팩토리 빈을 생성하기 위해, 스프링 XML설정파일에 다음설정을 추가하자.

- - -]]> -

SqlSessionFactoryBean 은 스프링의 FactoryBean 인터페이스를 구현(see the Spring documentation(Core Technologies -Customizing instantiation logic with a FactoryBean-))한다는 점을 알아야 한다. - 이 설정은 스프링이 SqlSessionFactoryBean 자체를 생성하는 것이 아니라 팩토리에서 getObject() 메서드를 호출한 결과를 리턴한다는 것을 의미한다. - 이 경우, 스프링은 애플리케이션 시작 시점에 SqlSessionFactory를 빌드하고 sqlSessionFactory 라는 이름으로 저장한다. - 자바에서 코드로 표현하면 아래와 같다.

- - - -

일반적인 마이바티스 스프링 사용법에서는, SqlSessionFactoryBean이나 관련된 SqlSessionFactory를 직접 사용할 필요가 없다. - 대신 세션 팩토리가 MapperFactoryBeanSqlSessionDaoSupport를 확장하는 다른 DAO에 주입될것이다.

-
- - - -

SqlSessionFactory는 JDBC DataSource의 필수 프로퍼티가 필요하다. - 어떤 DataSource라도 상관없고 다른 스프링 데이터베이스 연결처럼 설정되어야만 한다.

- -

하나의 공통적인 프로퍼티는 마이바티스 XML설정파일의 위치를 지정하기 위해 사용되는 configLocation이다. - 이 프로퍼티를 설정하는 것은 디폴트 설정을 가진 마이바티스 설정을 변경해야 할 경우 뿐이다. - 대개는 <settings><typeAliases> 섹션을 변경하는 경우이다.

- -

설정파일이 마이바티스 설정을 완전히 다룰 필요는 없다. - 어떤 환경, 어떤 데이터소스 그리고 마이바티스 트랜잭션 관리자가 무시될수도 있다. - SqlSessionFactoryBean 는 필요에 따라 이 값들을 설정하여 자체적인 MyBatis Environment 를 만든다.

- -

설정파일이 필요한 다른 이유는 마이바티스 XML파일이 매퍼 클래스와 동일한 클래스패스에 있지 않은 경우이다. - 이 설정을 사용하면 두가지 옵션이 있다. - 첫번째는 마이바티스 설정파일에 <mappers> 섹션을 사용해서 XML파일의 클래스패스를 지정하는 것이다. - 두번째는 팩토리 빈의 mapperLocations 프로퍼티를 사용하는 것이다.

- -

mapperLocations 프로퍼티는 매퍼에 관련된 자원의 위치를 나열한다. - 이 프로퍼티는 마이바티스의 XML매퍼 파일들의 위치를 지정하기 위해 사용될 수 있다. - 디렉터리 아래 모든 파일을 로드하기 위해 앤트(Ant) 스타일의 패턴을 사용할수도 있고 가장 상위 위치를 지정하는 것으로 재귀적으로 하위 경로를 찾도록 할수도 있다. - 예를 들어보면 다음과 같다.

- - - - -]]> - -

이 설정은 sample.config.mappers 패키지 아래와 그 하위 패키지를 모두 검색해서 마이바티스 매퍼 XML파일을 모두 로드할 것이다.

- -

컨테이너 관리 트랜잭션을 사용하는 환경에서 필요한 하나의 프로퍼티는 transactionFactoryClass 이다. - 이에 관련해서는 트랜잭션을 다루는 장에서 볼수 있다.

- -

- 만약 multi-db 기능을 사용한다면 다음과 같이 databaseIdProvider 속성을 설정해야 한다. -

- - - - - sqlserver - db2 - oracle - mysql - - - - - - - - -]]> - -

- NOTE - 1.3.0 버전 부터 configuration 속성이 추가되었다. - 다음과 같이 MyBatis XML 설정 파일없이 Configuration 인스턴스를 직접 지정할 수 있습니다. -

- - - - - - - - -]]> - -
-
- -
\ No newline at end of file diff --git a/src/site/ko/xdoc/getting-started.xml.vm b/src/site/ko/xdoc/getting-started.xml.vm deleted file mode 100644 index 15551f723f..0000000000 --- a/src/site/ko/xdoc/getting-started.xml.vm +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - 마이바티스 스프링 연동모듈 | 시작하기 - Hunter Presnall - Eduardo Macarron - 이동국(한국어 번역) - - - -
-

이 장은 마이바티스 스프링 연동모듈을 설치하고 셋팅하는 방법에 대해 간단히 보여준다. - 그리고 트랜잭션을 사용하는 간단한 애플리케이션을 만드는 방법까지 다룰 것이다.

- - -

마이바티스 스프링 연동모듈을 사용하기 위해서, 클래스패스에 mybatis-spring-${project.version}.jar를 포함시켜야 한다.

-

메이븐을 사용하고 있다면 pom.xml에 다음처럼 의존성을 추가하면 된다.

- - org.mybatis - mybatis-spring - ${project.version} -]]> -
- - -

마이바티스를 스프링과 함께 사용하려면 스프링의 애플리케이션 컨텍스트에 적어도 두개를 정의해줄 필요가 있다. - 두가지는 SqlSessionFactory와 한개 이상의 매퍼 인터페이스이다.

- -

마이바티스 스프링 연동모듈에서, SqlSessionFactoryBeanSqlSessionFactory를 만들기 위해 사용된다. - 팩토리 빈을 설정하기 위해, 스프링 설정파일에 다음 설정을 추가하자. -

- - - -]]> - - - -

SqlSessionFactoryDataSource를 필요로 하는 것을 알아둘 필요가 있다. - 어떤 DataSource도 상관없지만 다른 스프링의 데이터베이스 연결과 동일하게 설정되어야 한다.

- -

매퍼 인터페이스가 다음처럼 정의되었다고 가정해보자.

- - -

UserMapper인터페이스는 다음처럼 MapperFactoryBean을 사용해서 스프링에 추가된다.

- - - -]]> - -

매퍼는 반드시 구현체 클래스가 아닌 인터페이스로 정의되어야 한다. - 예를들어, 애노테이션이 SQL을 명시하기 위해 사용되지만 마이바티스 매퍼 XML파일 또한 사용될 수 있다.

- -

한번만 설정하면, 다른 스프링 빈에 주입하는 같은 방법으로 비즈니스/서비스 객체에 매퍼를 직접 주입할 수 있다. - MapperFactoryBeanSqlSession을 생성하고 닫는 작업을 잘 다룬다. - 실행중인 스프링 트랜잭션이 있다면, 트랜잭션이 완료되는 시점에 커밋이나 롤백이 될 것이다. - 마지막으로 예외가 발생하면 스프링의 DataAccessException예외가 발생한다.

- -

- 자바로 설정하면 다음과 같다. -

- - - -

마이바티스의 데이터 관련 메서드는 호출하는 것은 한줄이면 된다.

- -
-
- -
diff --git a/src/site/ko/xdoc/index.xml b/src/site/ko/xdoc/index.xml deleted file mode 100644 index ace2c84004..0000000000 --- a/src/site/ko/xdoc/index.xml +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - 마이바티스 스프링 연동모듈 | 소개 - Hunter Presnall - Eduardo Macarron - 이동국(한국어 번역) - - - -
- -

마이바티스 스프링 연동모듈은 마이바티스와 스프링을 편하고 간단하게 연동한다. - 이 모듈은 마이바티스로 하여금 스프링 트랜잭션에 쉽게 연동되도록 처리한다. - 게다가 마이바티스 매퍼와 SqlSession을 다루고 다른 빈에 주입시켜준다. - 마이바티스 예외를 스프링의 DataAccessException로 변환하기도 하고 마이바티스, 스프링 또는 마이바티스 스프링 연동모듈에 의존성을 없애기도 한다.

-
- - -

스프링 2.x은 아이바티스 2.x만을 지원한다. - 스프링 3.x에서 마이바티스 3.x를 지원하기 위한 시도가 진행중이다. - (스프링의 이슈관리 시스템인 이슈 를 보라.) - 불행하게도 스프링 3의 개발이 마이바티스 3.0의 정식릴리즈전에 개발이 완료되었다. - 그래서 스프링팀은 릴리즈가 안된 마이바티스 코드를 함께 릴리즈하는 것을 원하지 않았고 실제적인 스프링 지원을 기다릴수밖에 없었다. - 스프링의 마이바티스 지원에 대한 관심으로 인해, - 마이바티스 커뮤니티는 재결합하는 형태로 결정을 내고 대신 마이바티스의 하위 프로젝트 형태로 스프링 연동 프로젝트를 추가한다.

-
- - -

마이바티스 스프링 연동을 시작하기 전에, 마이바티스와 스프링의 용어를 맞추는 일이 굉장히 중요했다. - 이 문서는 배경지식이나 기본적인 셋업방법 그리고 마이바티스와 스프링의 설정에 대한 튜토리얼등은 제공하지 않는다.

-

- MyBatis-Spring requires following versions: -

- - - - - - - - - - - - - - - - - - - - - - - - - - -
- MyBatis-Spring - - MyBatis - - Spring Framework - - Spring Batch - - Java -
- 2.0 - - 3.4+ - - 5.0+ - - 4.0+ - - Java 8+ -
- 1.3 - - 3.4+ - - 3.2.2+ - - 2.1+ - - Java 6+ -
-
- - -

이 프로젝트가 실제로 만들어지게 도와준 모든 특별한 분들에게 정말 감사한다. - 알파벳 순서로 보면, 코딩및 테스트 그리고 문서화를 담당했던 Eduardo Macarron, Hunter Presnall, Putthiphong Boonphong; - 그외 다양한 프로젝트 기여자인 Andrius Juozapaitis, Giovanni Cuccu, Mike Lanyon, Raj Nagappan, Tomas Pinos; - 그리고 마이바티스에 하위 프로젝트로 가져올수 있도록 많은 것을 찾아준 Simone Tripodi 에게 감사한다. ;) - 이들이 없었다면 이 프로젝트는 존재하지 않았을 것이다.

-
- - -

만약에 어떤 방법으로든 이 문서의 취약점이 발견되거나 기능에 대한 문서화가 빠진 부분이 보인다면, - 가장 좋은 방법은 먼저 공부해서 자신만의 문서를 작성하는 것이다. -

-

이 문서의 원본은 xdoc포맷이며 프로젝트의 Git에서 찾을 수 있다. - repository 를 fork 하고, 업데이트하고 pull request 를 보내주십시오. -

-

당신처럼 이 문서를 읽는 사람들에게 이 문서의 최고의 저자가 될수 있다!

-
- - -

사용자들은 다음의 번역문서별로 마이바티스 스프링 연동모듈에 대해 알수 있다.

- -

위 번역문서에는 없지만 자국의 언어로 문서로 보고 싶다면, 자국의 언어로 된 문서를 만들어서 우리에게 보내달라.

-
- - -

번역자 : 이동국(fromm0@gmail.com, http://ldg.pe.kr, https://www.facebook.com/dongguk.lee.3)

-
-
- - -
diff --git a/src/site/ko/xdoc/mappers.xml b/src/site/ko/xdoc/mappers.xml deleted file mode 100644 index 31ee155bcc..0000000000 --- a/src/site/ko/xdoc/mappers.xml +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - 마이바티스 스프링 연동모듈 | 매퍼 주입 - Hunter Presnall - Eduardo Macarron - 이동국(한국어 번역) - - - -
-

데이터 접근 객체인 DAO를 만든것보다 직접 SqlSessionDaoSupportSqlSessionTemplate 를 사용하자. - 마이바티스 스프링 연동모듈은 다른 빈에 직접 주입할 수 있는 쓰레드에 안전한 매퍼를 생성할 수 있다.

- - - -]]> - -

한번 주입하고나면 매퍼는 애플리케이션 로직에서 사용할수 있는 준비가 된다.

- -

이 코드를 보면 SqlSession이나 마이바티스 객체가 보이지 않는다. - 게다가 세션을 생성하거나 열고 닫을필요도 없어보인다. - 마이바티스 스프링 연동모듈이 알아서 처리할 것이다.

- - -

매퍼를 등록하는 방법은 기존의 전통적인 XML설정법을 사용하거나 새로운 3.0 이후의 자바설정(일명 @Configuration)을 사용하느냐에 따라 다르다.

- -

XML설정 사용

- -

매퍼는 다음처럼 XML설정파일에 MapperFactoryBean을 두는 것으로 스프링에 등록된다.

- - - -]]> - -

UserMapper가 매퍼 인터페이스와 같은 경로의 클래스패스에 마이바티스 XML매퍼 파일을 가지고 있다면 MapperFactoryBean이 자동으로 파싱할것이다. - 매퍼 XML파일을 다른 클래스패스에 두는게 아니라면 마이바티스 설정파일에 매퍼를 지정할 필요가 없다. - 좀더 세부적인 정보는 SqlSessionFactoryBeanconfigLocation 프로퍼티를 살펴보자.

- -

MapperFactoryBeanSqlSessionFactorySqlSessionTemplate가 필요하다. - sqlSessionFactorysqlSessionTemplate 프로퍼티를 셋팅하면 된다. - 둘다 셋팅하면 SqlSessionFactory가 무시된다. - 세션 팩토리 셋은 SqlSessionTemplate이 필요하고 MapperFactoryBean는 팩토리를 사용할것이다.

- -

자바설정 사용

- -

스프링의 Java Config기능을 사용하면 다음처럼 SqlSessionTemplate 에서 직접 매퍼를 얻을 수 있다.

- - - -

마이바티스의 디폴트 SqlSession에서 매퍼를 리턴받을수 없다. - 디폴트 SqlSession은 쓰레드에 안전하지 않고 생성된 SqlSession이 닫힐때까지만 살아있기 때문이다. - 대신 샘플코드에서 보여주는 것처럼 SqlSessionTemplate 를 사용해야만 한다.

-
- - -

하나씩 매퍼를 모두 등록할 필요가 없다. - 대신 클래스패스를 지정해서 마이바티스 스프링 연동모듈의 자동스캔기능을 사용할 수 있다.

- -

자동스캔을 사용하는데는 3가지 방법이 있다.

-
    -
  • <mybatis:scan/> 엘리먼트 사용
  • -
  • @MapperScan 애노테이션 사용
  • -
  • 스프링 XML파일을 사용해서 MapperScannerConfigurer를 등록
  • -
- -

<mybatis:scan/>@MapperScan 모두 마이바티스 스프링 연동모듈 1.2.0에서 추가된 기능이다. - @MapperScan 은 스프링 버전이 3.1이상이어야 한다.

- -

<mybatis:scan/>

- -

<mybatis:scan/> XML엘리먼트는 스프링에서 제공하는 <context:component-scan/> 엘리먼트와 매우 유사한 방법으로 매퍼를 검색할 것이다.

- -

샘플 XML설정을 아래에서 볼수 있다.

- - - - - - - -]]> - -

base-package 속성은 매퍼 인터페이스 파일이 있는 가장 상위 패키지를 지정하면 된다. - 세미콜론이나 콤마를 구분자로 사용해서 한개 이상의 패키지를 셋팅할 수 있다. - 매퍼는 지정된 패키지에서 재귀적으로 하위 패키지를 모두 검색할 것이다.

- -

<mybatis:scan/>이 자동으로 주입할 수 있는 MapperFactoryBean를 생성하기 때문에 - SqlSessionFactorySqlSessionTemplate 를 명시할 필요가 없다. - 하지만 한개 이상의 DataSource를 사용한다면 자동주입이 생각한데로 동작하지 않을수도 있다. - 이 경우 사용할 빈 이름을 지정하기 위해 factory-reftemplate-ref 속성을 사용할수 있다.

- -

<mybatis:scan/>은 마커(marker) 인터페이스나 애노테이션을 명시해서 생성되는 매퍼를 필터링할수도 있다. - annotation 프로퍼티는 검색할 애노테이션을 지정한다. - marker-interface 프로퍼티는 검색할 상위 인터페이스를 지정한다. - 이 두개의 프로퍼티를 모두 지정하면, 매퍼는 두 조건을 모두 만족하는 인터페이스만을 추가한다. - 디폴트로 이 두가지 프로퍼티는 모두 null이다. - 그래서 base-package프로퍼티에 설정된 패키지 아래 모든 인터페이스가 매퍼로 로드될 것이다.

- -

발견된 매퍼는 자동검색된 컴포넌트를 위한 스프링의 디폴트 명명규칙 전략(see the Spring reference document(Core Technologies -Naming autodetected components-))을 사용해서 빈이름이 명명된다. - 빈 이름을 정하는 애노테이션이 없다면 매퍼의 이름에서 첫글자를 소문자로 변환한 형태로 빈 이름을 사용할 것이다. - @Component 나 JSR-330의 @Named 애노테이션이 있다면 애노테이션에 정의한 이름을 그대로 사용할 것이다. - annotation 프로퍼티를 org.springframework.stereotype.Component, - javax.inject.Named(자바SE 1.6을 사용한다면) 또는 개발자가 스스로 작성한 애노테이션으로 셋팅할 수 있다. - 그러면 애노테이션은 마커와 이름을 제공하는 역할로 동작할 것이다.

- -

- 중요 <context:component-scan/> 가 매퍼를 검색해서 등록을 하지 못할수도 있다. - 매퍼는 인터페이스고 스프링에 빈으로 등록하기 위해서는 각각의 인터페이스를 찾기 위해 스캐너가 MapperFactoryBean 를 생성하는 방법을 알아야만 한다.

- -

@MapperScan

- -

@Configuration 라고 불리는 스프링의 자바설정을 사용한다면 <mybatis:scan/>보다는 - @MapperScan를 사용하길 선호할것이다.

- -

@MapperScan 애노테이션은 다음처럼 사용된다.

- - - -

애노테이션은 앞서 본 <mybatis:scan/> 에서 설명하는 것과 동일하게 동작한다. - markerInterfaceannotationClass 프로퍼티를 사용해서 마커 인터페이스와 애노테이션 클래스를 명시하게 한다. - sqlSessionFactorysqlSessionTemplate 프로퍼티를 사용해서 - SqlSessionFactorySqlSessionTemplate을 제공할 수도 있다.

- -

MapperScannerConfigurer

- -

MapperScannerConfigurer는 평범한 빈처럼 XML애플리케이션 컨텍스트에 포함된 BeanDefinitionRegistryPostProcessor 이다. - MapperScannerConfigurer를 셋업하기 위해 다음의 스프링 설정을 추가하자.

- - -]]> - -

sqlSessionFactorysqlSessionTemplate를 지정할 필요가 있다면 빈참조가 아닌 빈이름이 필요하다. - value 프로퍼티는 빈 이름을 지정하고 ref 는 빈 참조를 지정하기 때문에 value 프로퍼티를 사용하자.

- ]]> - -

중요 sqlSessionFactoryBeansqlSessionTemplateBean 프로퍼티는 - 마이바티스 스프링 연동모듈 1.0.2 버전 이상에서만 사용이 가능하다. 하지만 MapperScannerConfigurer는 잦은 에러를 발생시키는 - PropertyPlaceholderConfigurer보다 앞서 실행되기 때문에 이 프로퍼티들은 사용하지 말길 바란다(deprecated). - 대신 새롭게 추가된 프로퍼티인 sqlSessionFactoryBeanNamesqlSessionTemplateBeanName 를 사용하도록 권한다.

-
-
- -
diff --git a/src/site/ko/xdoc/sample.xml b/src/site/ko/xdoc/sample.xml deleted file mode 100644 index aa1e476e03..0000000000 --- a/src/site/ko/xdoc/sample.xml +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - MyBatis-Spring | Sample Code - Eduardo Macarron - - - -
- -

- 중요 - 전체 웹 애플리케이션 서버에서 Spring을 사용하는 방법을 알고 싶다면 JPetstore 6 demo 를 참조하십시오. -

- -

- MyBatis-Spring repository 에서 샘플 코드를 확인할 수 있다. -

-

- 모든 샘플은 JUnit5 에서 실행할 수 있다. -

-

- 샘플 코드는 트랜잭션 서비스가 data access layer 에서 도메인 개체를 가져 오는 일반적인 디자인을 보여준다. -

-

- 다음 FooService.java 는 서비스처럼 작동한다. -

- -

- 이것은 트랜잭션 빈이다. 따라서 어떤 메서드든 실행이 되면 트랜잭션이 시작되고 예외가 발생하지 않았을 때 커밋된다. - 트랜잭션은 @Transactional annotation 을 통해 설정할 수 있다. - 이것은 필수가 아니다. Spring이 제공하는 다른 방법을 사용하여 트랜잭션을 구분할 수 있다. -

-

- 이 서비스는 MyBatis로 이루어진 DAO layer 를 호출한다. - 이 layer는 런타임시 MyBatis에 의해 작성되고 Spring에 의해 서비스에 주입되는 동적 프록시와 함께 사용되는 UserMapper.java 인터페이스로 구성된다. -

- -

- 단순함을 위해서 DAO가 인터페이스와 그 구현체로 만들어진 DAO 시나리오를 위해 UserMapper.java 인터페이스를 사용했지만, - 이 경우 대신 UserDao.java라는 인터페이스를 사용하는 것이 더 적절하다. -

-

- 매퍼 인터페이스를 찾고 Spring에 등록하고 서비스 빈에 주입하는 여러 가지 방법을 살펴본다. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
시나리오
샘플 테스트설명
- SampleMapperTest.java - - UserMapper 구현체를 동적으로 빌드 할 MapperFactoryBean에 기반한 기본 구성을 보여준다. -
- SampleScannerTest.java - - MapperScannerConfigurer 를 사용하여 어떻게 프로젝트의 모든 매퍼들을 자동으로 검색되도록 하는 방법을 보여준다. -
- SampleSqlSessionTest.java - - Spring에서 관리하는 SqlSession을 사용하여 DAO를 코딩하고 자체적인 구현체인 UserDaoImpl.java 를 제공하는 방법을 보여준다. -
- SampleEnableTest - - 스프링의 @Configuration@MapperScann annotation을 함께 사용하여 매퍼를 자동으로 검색하는 방법을 보여준다. -
- SampleNamespaceTest - - 커스텀 MyBatis XML 네임스페이스를 사용하는 방법을 보여준다. -
- SampleJavaConfigTest.java - - 스프링의 @Configuration을 사용하여 MyBatis 빈들을 수동적으로 생성하는 방법을 보여준다. -
- SampleJobJavaConfigTest.java - - Java Configuration을 이용하여 Spring Batch에서 어떻게 ItemReaderItemWriter를 사용하는지 보여준다. -
- SampleJobXmlConfigTest.java - - XML Configuration을 이용하여 Spring Batch에서 어떻게 ItemReaderItemWriter를 사용하는지 보여준다. -
-

- MyBatis-Spring이 실제로 어떻게 다르게 작동하는지 보려면 applicationContext.xml 파일을 살펴보십시오. -

- -
- -
- diff --git a/src/site/ko/xdoc/sqlsession.xml b/src/site/ko/xdoc/sqlsession.xml deleted file mode 100644 index 466bf76c51..0000000000 --- a/src/site/ko/xdoc/sqlsession.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - 마이바티스 스프링 연동모듈 | SqlSession 사용 - Hunter Presnall - Eduardo Macarron - 이동국(한국어 번역) - - - -
-

마이바티스에서는 SqlSession를 생성하기 위해 SqlSessionFactory를 사용한다. - 세션을 한번 생성하면 매핑구문을 실행하거나 커밋 또는 롤백을 하기 위해 세션을 사용할수 있다. - 마지막으로 더 이상 필요하지 않은 상태가 되면 세션을 닫는다. - 마이바티스 스프링 연동모듈을 사용하면 SqlSessionFactory를 직접 사용할 필요가 없다. - 왜냐하면, 스프링 트랜잭션 설정에 따라 자동으로 커밋 혹은 롤백을 수행하고 닫혀지는, 쓰레드에 안전한 SqlSession 개체가 스프링 빈에 주입될 수 있기 때문이다.

- - -

SqlSessionTemplate은 마이바티스 스프링 연동모듈의 핵심이다. - SqlSessionTemplateSqlSession을 구현하고 코드에서 SqlSession를 대체하는 역할을 한다. - SqlSessionTemplate 은 쓰레드에 안전하고 여러개의 DAO나 매퍼에서 공유할수 있다.

- -

getMapper()에 의해 리턴된 매퍼가 가진 메서드를 포함해서 SQL을 처리하는 마이바티스 메서드를 호출할때 - SqlSessionTemplateSqlSession이 현재의 스프링 트랜잭션에서 사용될수 있도록 보장한다. - 추가적으로 SqlSessionTemplate은 필요한 시점에 세션을 닫고, 커밋하거나 롤백하는 것을 포함한 세션의 생명주기를 관리한다. - 또한 마이바티스 예외를 스프링의 DataAccessException로 변환하는 작업또한 처리한다.

- -

SqlSessionTemplate은 마이바티스의 디폴트 구현체인 DefaultSqlSession 대신 항상 사용된다. - 왜냐하면 템플릿은 스프링 트랜잭션의 일부처럼 사용될 수 있고 여러개 주입된 매퍼 클래스에 의해 사용되도록 쓰레드에 안전하다. - 동일한 애플리케이션에서 두개의 클래스간의 전환은 데이터 무결성 이슈를 야기할수 있다.

- -

SqlSessionTemplate은 생성자 인자로 SqlSessionFactory를 사용해서 생성될 수 있다.

- - -]]> - - - -

이 빈은 DAO빈에 직접 주입될 수 있다. 다음처럼 빈 설정에서 SqlSession 프로퍼티를 설정하면 된다.

- -

그리고 다음처럼 SqlSessionTemplate 를 주입하자.

- - -]]> - -

SqlSessionTemplate은 인자로 ExecutorType를 가지는 생성자를 가지고 있다. - 이 인자는 예를들면 스프링 설정 XML을 다음처럼 설정해서 배치형태의 SqlSession를 만들수도 있다.

- - - -]]> - - - -

DAO의 코드를 다음처럼 작성했다면 모든 구문은 배치형태로 실행이 될 것이다.

- users) { - for (User user : users) { - sqlSession.insert("org.mybatis.spring.sample.mapper.UserMapper.insertUser", user); - } -}]]> - -

이러한 설정형태는 SqlSessionFactory의 디폴트 형태가 아닌 다른형태로 메서드를 실행해야 할때만 사용할 필요가 있다.

- -

이러한 형태에 대해 굳이 경로를 하자면 메서드를 호출할때 ExecutorType이 다르면 이미 시작된 트랜잭션을 사용하지 못할것이다. - 다른 실행자(executor) 타입을 사용할때는 SqlSessionTemplate의 메서드를 구분된 트랜잭션(PROPAGATION_REQUIRES_NEW를 사용하는)이나 트랜잭션 외부에서 호출하는지 확실히해야 한다.

-
- - -

SqlSessionDaoSupportSqlSession을 제공하는 추상클래스이다. - getSqlSession()메서드를 호출해서 다음처럼 SQL을 처리하는 마이바티스 메서드를 호출하기 위해 사용할 SqlSessionTemplate을 얻을 수 있다.

- -

대개 MapperFactoryBean은 추가적인 코드가 필요없기 때문에 이 클래스를 선호한다. - 하지만 DAO에서 마이바티스가 필요하지 않고 구현된 클래스가 필요하지 않을때만 유용하다.

- -

SqlSessionDaoSupportsqlSessionFactorysqlSessionTemplate 프로퍼티를 셋팅할 필요가 있다. - 두개의 프로퍼티를 모두 셋팅하면 sqlSessionFactory는 무시된다.

- -

SqlSessionDaoSupport의 하위클래스인 UserDaoImpl가 있다고 하면 스프링에서는 다음처럼 설정될 수 있다.

- - -]]> -
-
- -
diff --git a/src/site/ko/xdoc/transactions.xml b/src/site/ko/xdoc/transactions.xml deleted file mode 100644 index ca797244ef..0000000000 --- a/src/site/ko/xdoc/transactions.xml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - 마이바티스 스프링 연동모듈 | 트랜잭션 - Hunter Presnall - Eduardo Macarron - 이동국(한국어 번역) - - - -
-

마이바티스 스프링 연동모듈을 사용하는 중요한 이유중 하나는 마이바티스가 스프링 트랜잭션에 자연스럽게 연동될수 있다는 것이다. - 마이바티스에 종속되는 새로운 트랜잭션 관리를 만드는 것보다는 마이바티스 스프링 연동모듈이 스프링의 DataSourceTransactionManager과 융합되는 것이 좋다.

-

스프링 트랜잭션 관리자를 한번 설정하면, 대개의 경우처럼 스프링에서 트랜잭션을 설정할 수 있다. - @Transactional 애노테이션과 AOP스타일의 설정 모두 지원한다. - 하나의 SqlSession객체가 생성되고 트랜잭션이 동작하는 동안 지속적으로 사용될것이다. - 세션은 트랜잭션이 완료되면 적절히 커밋이 되거나 롤백될것이다.

-

마이바티스 스프링 연동모듈은 한번 셋업되면 트랜잭션을 투명하게 관리한다. - DAO클래스에 어떠한 추가적인 코드를 넣을 필요가 없다.

- - -

스프링 트랜잭션을 가능하게 하려면, 스프링 설정파일에 DataSourceTransactionManager를 생성하자.

- - -]]> - - - -

명시된 DataSource는 스프링을 사용할때 일반적으로 사용한다면 어떠한 JDBC DataSource도 될수 있다. - JNDI룩업을 통해 얻어진 DataSource뿐 아니라 커넥션 풀링 기능도 포함한다.

-

트랜잭션 관리자에 명시된 DataSourceSqlSessionFactoryBean을 생성할때 사용된 것과 반드시 동일한 것이어야 하는 것만 꼭 기억하자. - 그렇지 않으면 트랜잭션 관리가 제대로 되지 않을것이다.

-
- - -

만약에 JEE컨테이너를 사용하고 스프링을 컨테이너 관리 트랜잭션(container managed transactions, CMT)에 두려한다면, - 스프링은 JtaTransactionManager나 그 하위 클래스로 설정되어야 한다. - 이러한 설정을 가장 쉽게 하는 방법은 스프링의 트랜잭션 네임스페이스 or JtaTransactionManagerFactoryBean 를 사용하는 것이다.

- - ]]> - - - -

이 설정에서, 마이바티스는 CMT와 함께 설정된 스프링 트랜잭션 리소스처럼 동작할 것이다. - 스프링은 이미 설정된 트랜잭션을 사용해서 SqlSession을 이미 동작중인 트랜잭션에 넣을 것이다. - 시작된 트랜잭션이 없고 트랜잭션이 필요한 경우라면 스프링은 새로운 컨테이너 관리 트랜잭션을 시작할 것이다.

-

CMT는 사용하지만 스프링 트랜잭션 관리를 원하지 않는다면 어떠한 스프링 트랜잭션 관리자를 설정해서도 안되고 - 마이바티스 ManagedTransactionFactory를 사용하기 위해 SqlSessionFactoryBean를 설정해야만 한다.

- - - - - -]]> - - - -
- - -

마이바티스 SqlSession은 트랜잭션을 제어하는 메서드를 제공한다. - 하지만 마이바티스 스프링 연동모듈은 빈을 스프링이 관리하는 SqlSession이나 스프링이 관리하는 매퍼에 주입한다. - 이 말은 스프링이 항상 트랜잭션을 관리한다는 뜻이다.

-

스프링이 관리하는 SqlSession에서는 SqlSession.commit(), SqlSession.rollback() - 또는 SqlSession.close() 메서드를 호출할수가 없다. - 그럼에도 불구하고 이 메서드들을 사용하면 UnsupportedOperationException 예외가 발생한다. - 이러한 메서드는 주입된 매퍼 클래스에서는 사용할 수 없다.

-

JDBC연결의 자동커밋 설정을 어떻게 하더라도 스프링 트랜잭션 밖의 SqlSession 데이터 메서드나 매퍼 메서드의 실행은 자동으로 커밋된다.

-

트래잭션을 수동으로 제어하고자 한다면 the Spring reference document(Data Access -Programmatic transaction management-) 을 참고하자. - 다음의 코드는 스프링 레퍼런스 에서 언급된 내용으로 PlatformTransactionManager를 사용해서 수동으로 트랜잭션을 다루는 방법을 보여준다.

- - - -

- You can omit to call the commit and rollback method using the TransactionTemplate. -

- - { - userMapper.insertUser(user); - return null; -});]]> - -

이 코드는 매퍼를 사용하지만 SqlSession를 사용해도 잘 동작한다.

-
-
- -
diff --git a/src/site/ko/xdoc/using-api.xml b/src/site/ko/xdoc/using-api.xml deleted file mode 100644 index c301e93750..0000000000 --- a/src/site/ko/xdoc/using-api.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - 마이바티스 스프링 연동모듈 | 마이바티스 API 사용 - Hunter Presnall - Eduardo Macarron - 이동국(한국어 번역) - - - -
-

MyBatis-Spring 연동 모듈을 사용해도 계속해서 MyBatis API를 직접 사용할 수 있다. - SqlSessionFactoryBean을 사용해서 스프링에서 SqlSessionFactory를 생성하고 팩토리를 사용하면 된다.

- - -

이 방법은 신중히 사용하자. 왜냐하면 잘못사용하면 런타임 에러나 데이터 문제등을 야기할수 있기 때문이다. - API를 직접 사용할때는 다음의 규칙들에 유의해야 한다.

-
    -
  • -

    스프링 트랜잭션에 속하지 않고 별도의 트랜잭션에서 동작한다.

    -
  • -
  • -

    SqlSession이 스프링 트랜잭션 관리자가 사용하는 DataSource를 사용하고 - 이미 트랜잭션이 동작하고 있다면 이 코드는 예외를 발생시킬 것이다.

    -
  • -
  • -

    마이바티스의 DefaultSqlSession은 쓰레드에 안전하지 않다. - 빈에 이 객체를 주입하면 아마도 에러를 발생시킬 수 있다.

    -
  • -
  • -

    DefaultSqlSession을 사용해서 생성한 매퍼 또한 쓰레드에 안전하지 않다. - 이렇게 만든 매퍼를 빈에 주입하면 에러를 발생시킬 수 있다.

    -
  • -
  • -

    SqlSession항상 마지막에 close() 메서드를 호출해야 한다.

    -
  • -
-
- -
diff --git a/src/site/markdown/README.md b/src/site/markdown/README.md new file mode 100644 index 0000000000..74036dbd21 --- /dev/null +++ b/src/site/markdown/README.md @@ -0,0 +1,18 @@ +# Table of contents + +This page is for rendering index on GitHub. + +> **NOTE:** +> +> Since the link destination is specified on the assumption that it is converted to html with maven-site-plugin, there is an anchor that is broken in the rendering on GitHub. + +* [Introduction](./index.md) +* [Getting Started](./getting-started.md) +* [SqlSessionFactoryBean](./factorybean.md) +* [Transactions](./transactions.md) +* [Using an SqlSession](./sqlsession.md) +* [Injecting Mappers](./mappers.md) +* [Spring Boot](./boot.md) +* [Using the MyBatis API](./using-api.md) +* [Spring Batch](./batch.md) +* [Sample Code](./sample.md) diff --git a/src/site/markdown/batch.md b/src/site/markdown/batch.md new file mode 100644 index 0000000000..aba658ef21 --- /dev/null +++ b/src/site/markdown/batch.md @@ -0,0 +1,359 @@ + +# Spring Batch + +As of version 1.1.0 MyBatis-Spring provides three beans for building Spring Batch applications: the `MyBatisPagingItemReader`, the `MyBatisCursorItemReader` and the `MyBatisBatchItemWriter`. +Also, As of version 2.0.0 provides three builder classes for supporting the Java Configuration: the `MyBatisPagingItemReaderBuilder`, the `MyBatisCursorItemReaderBuilder` and the `MyBatisBatchItemWriterBuilder`. + +NOTE +This is about [Spring Batch](http://static.springsource.org/spring-batch/) and not about MyBatis batch SqlSessions. For information about batch sessions go to section [Using an SqlSession](sqlsession.html). + +## MyBatisPagingItemReader + +This bean is an `ItemReader` that reads records from a database in a paging fashion. + +It executes the query specified as the `setQueryId` property to retrieve requested data. +The query is executed using paged requests of a size specified in `setPageSize` property. +Additional pages are requested when needed as `read()` method is called, returning an object corresponding to current position. + +Some standard query parameters are provided by the reader and the SQL in the named query must use some or all of these parameters (depending on the SQL variant) to construct a result set of the required size. +The parameters are: + +* `_page`: the page number to be read (starting at 0) +* `_pagesize`: the size of the pages, i.e. the number of rows to return +* `_skiprows`: the product of `_page` and `_pagesize` + +And they could be mapped as the follow in a select statement: + +```xml + +``` + +Follows below a sample configuration snippet: + +```xml + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisPagingItemReader reader() { + return new MyBatisPagingItemReaderBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") + .build(); + } +} +``` + +**Explaining a more complex example:** + +```xml + +``` +```xml + + + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @StepScope + @Bean + public MyBatisPagingItemReader dateBasedCriteriaReader( + @Value("#{@datesParameters}") Map datesParameters) throws Exception { + return new MyBatisPagingItemReaderBuilder() + .sqlSessionFactory(batchReadingSessionFactory()) + .queryId("com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot") + .parameterValues(datesParameters) + .pageSize(200) + .build(); + } + + @StepScope + @Bean + public Map datesParameters( + @Value("#{jobExecutionContext['EXTRACTION_START_DATE']}") LocalDate yesterday, + @Value("#{jobExecutionContext['TODAY_DATE']}") LocalDate today, + @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}") LocalDate firstDayOfTheMonth, + @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}") LocalDate firstDayOfThePreviousMonth) { + Map map = new HashMap<>(); + map.put("yesterday", yesterday); + map.put("today", today); + map.put("first_day_of_the_month", firstDayOfTheMonth); + map.put("first_day_of_the_previous_month", firstDayOfThePreviousMonth); + return map; + } +} +``` + +The previous example makes use of a few different things: + +* `sqlSessionFactory`: You can specify your own sessionFactory to the reader, it might be useful if you want to read from several databases. + +* `queryId`: If the base code have several tables or databases to read from, and that you have different queries, it might be interesting to use different mapper files with different namespaces. + so when referring to the query, don't forget about the namespace of the mapper file. + +* `parameterValues`: You can pass additional parameters via this map, the example above uses a map that is build by spring using a SpEL expression taking values from the `jobExecutionContext`. + The keys of the map will be used by MyBatis in the mapper file (ex: *yesterday* could be used as `#{yesterday,jdbcType=TIMESTAMP}`). + Note that the map and the reader are both built in the `step` scope in order to be able to use the Spring EL expression with the `jobExecutionContext`. + Also if MyBatis type handlers are correctly configured you can pass custom instances like the parameters of this map that are JodaTime dates. + +* `pageSize`: If the batch flow is configured with chunk size, it is relevant to pass this information to the reader as well, which is done via this property. + +## MyBatisCursorItemReader + +This bean is an `ItemReader` that reads records from a database using a cursor. + +NOTE +To use this bean you need at least MyBatis 3.4.0 or a newer version. + +It executes the query specified as the `setQueryId` property to retrieve requested data by using the method `selectCursor()`. +Each time a `read()` method is called it will return the next element of the cursor until no more elements are left. + +The reader will use a separate connection so the select statement does no participate in any transactions created as part of the step processing. + +When using the cursor you can just execute a regular query: + +```xml + +``` + +Follows below a sample configuration snippet: + +```xml + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisCursorItemReader reader() { + return new MyBatisCursorItemReaderBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") + .build(); + } +} +``` + +## MyBatisBatchItemWriter + +It is an `ItemWriter` that uses the batching features from `SqlSessionTemplate` to execute a batch of statements for all items provided. +The `SqlSessionFactory` needs to be configured with a `BATCH` executor. + +When `write()` is called it executes the mapped statement indicated in the property `statementId`. It is expected that `write()` is called inside a transaction. + +Follows below a sample configuration snippet: + +```xml + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisBatchItemWriter writer() { + return new MyBatisBatchItemWriterBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .statementId("com.my.name.space.batch.EmployeeMapper.updateEmployee") + .build(); + } +} +``` + +**Converting a item that read using ItemReader to an any parameter object:** + +By default behavior, the `MyBatisBatchItemWriter` passes a item that read using `ItemReader` (or convert by `ItemProcessor`) to the MyBatis(`SqlSession#update()`) as the parameter object. +If you want to customize a parameter object that passes to the MyBatis, you can realize to use the `itemToParameterConverter` option. +For example using `itemToParameterConverter` option, you can passes any objects other than the item object to the MyBatis. +Follows below a sample: + +At first, you create a custom converter class (or factory method). The following sample uses a factory method. + +```java +public class ItemToParameterMapConverters { + public static Converter> createItemToParameterMapConverter(String operationBy, LocalDateTime operationAt) { + return item -> { + Map parameter = new HashMap<>(); + parameter.put("item", item); + parameter.put("operationBy", operationBy); + parameter.put("operationAt", operationAt); + return parameter; + }; + } +} +``` + +At next, you write a sql mapping. + +```xml + +``` + +At last, you configure the MyBatisBatchItemWriter. + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisBatchItemWriter writer() throws Exception { + return new MyBatisBatchItemWriterBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .statementId("org.mybatis.spring.sample.mapper.PersonMapper.createPerson") + .itemToParameterConverter(createItemToParameterMapConverter("batch_java_config_user", LocalDateTime.now())) + .build(); + } +} +``` + +```xml + + + + + + + + + + +``` + +**Writing to different tables using composite writers (with some caveats):** + +This technique can only be used with MyBatis 3.2+, as there was an [issue](http://code.google.com/p/mybatis/issues/detail?id=741) in previous versions that made the writer misbehave. + +If the batch needs to write complex data, like records with associations, or even to different databases, then it is possible to work around the fact that insert statements only insert in one table. +In order to make it happen the batch have to prepare the *Item* to be written by the writer. +However depending on the constraints, opportunities or insight on the processed data it might be interesting to use the following technique. +The following trick can work on items with simple associations or just with unrelated tables. + +In a processor craft the Spring Batch Item in such way it will *hold* all the different records. +Suppose for each Item there is an *Interaction* that have one association *InteractionMetadata*, +and two non associated rows *VisitorInteraction* and *CustomerInteraction*, the holder object will look like: + +```java +public class InteractionRecordToWriteInMultipleTables { + private final VisitorInteraction visitorInteraction; + private final CustomerInteraction customerInteraction; + private final Interaction interaction; + // ... +} +``` +```java +public class Interaction { + private final InteractionMetadata interactionMetadata; +} +``` + +Then in the spring configuration there will be a `CompositeItemWriter` that will use delegate writers specifically configured for each kind of records. +Note that as the *InteractionMetadata* is an association in the example it will need to be written first so that Interaction can have the updated key. + +```xml + + + + + + + + + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public CompositeItemWriter interactionsItemWriter() { + CompositeItemWriter compositeItemWriter = new CompositeItemWriter(); + List> writers = new ArrayList<>(4); + writers.add(visitorInteractionsWriter()); + writers.add(customerInteractionsWriter()); + writers.add(interactionMetadataWriter()); + writers.add(interactionWriter()); + compositeItemWriter.setDelegates(writers); + return compositeItemWriter; + } +} +``` + +Then each delegate writer will be configured as needed; for example for *Interaction* and *InteractionMetadata*: + +```xml + +``` +```xml + +``` + +Same as the reader the `statementId` can refer to the statement with the prefixed namespace. + +Now in the mapper file the statement have to be crafted for each kind of records in the following way: + +```xml + + + +``` +```xml + + + +``` + +What's happening is that first the `insertInteractionMetadata` will be called, and the update statement is configured to return the ids created by the jdbc driver (`keyProperty` and `keyColumn`). +As the `InteractionMetadata` object were updated by this query the next query can be used to write the parent object `Interaction` via `insertInteraction`. + +***However note that JDBC drivers don't behave the same in this regard. At the time of this writing the H2 driver 1.3.168 will only return the latest index even in BATCH mode (see `org.h2.jdbc.JdbcStatement#getGeneratedKeys`), +while the MySQL JDBC driver will behave as expected and return all the IDs.*** diff --git a/src/site/markdown/boot.md b/src/site/markdown/boot.md new file mode 100644 index 0000000000..678db5d753 --- /dev/null +++ b/src/site/markdown/boot.md @@ -0,0 +1,4 @@ + +# Using Spring Boot + +Please see the [MyBatis Spring-boot-starter](http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure) sub project docs for details. diff --git a/src/site/markdown/factorybean.md b/src/site/markdown/factorybean.md new file mode 100644 index 0000000000..f6aa9680bf --- /dev/null +++ b/src/site/markdown/factorybean.md @@ -0,0 +1,180 @@ + +# SqlSessionFactoryBean + +In base MyBatis, the `SqlSessionFactory` is built using `SqlSessionFactoryBuilder`. In MyBatis-Spring, `SqlSessionFactoryBean` is used instead. + +## Setup + +To create the factory bean, put the following in the Spring XML configuration file: + +```xml + + + +``` +Note that `SqlSessionFactoryBean` implements Spring's `FactoryBean` interface see [the Spring documentation(Core Technologies -Customizing instantiation logic with a FactoryBean-)](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-extension-factorybean)). +This means that the bean Spring ultimately creates is **not** the `SqlSessionFactoryBean` itself, but what the factory returns as a result of the `getObject()` call on the factory. +In this case, Spring will build an `SqlSessionFactory` for you at application startup and store it with the name `sqlSessionFactory`. +In Java, the equivalent code would be: + +```java +@Configuration +public class MyBatisConfig { + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + return factoryBean.getObject(); + } +} +``` + +In normal MyBatis-Spring usage, you will not need to use `SqlSessionFactoryBean` or the corresponding `SqlSessionFactory` directly. +Instead, the session factory will be injected into `MapperFactoryBean`s or other DAOs that extend `SqlSessionDaoSupport`. + +## Properties + +`SqlSessionFactory` has a single required property, the JDBC `DataSource`. This can be any `DataSource` and should be configured just like any other Spring database connection. + +One common property is `configLocation` which is used to specify the location of the MyBatis XML configuration file. +One case where this is needed is if the base MyBatis configuration needs to be changed. Usually this will be `` or `` sections. + +Note that this config file does **not** need to be a complete MyBatis config. Specifically, any environments, data sources and MyBatis transaction managers will be **ignored**. +`SqlSessionFactoryBean` creates its own, custom MyBatis `Environment` with these values set as required. + +Another reason to require a config file is if the MyBatis mapper XML files are not in the same classpath location as the mapper classes. With this configuration, there are two options. +This first is to manually specify the classpath of the XML files using a `` section in the MyBatis config file. A second option is to use the `mapperLocations` property of the factory bean. + +The `mapperLocations` property takes a list of resource locations. This property can be used to specify the location of MyBatis XML mapper files. +The value can contain Ant-style patterns to load all files in a directory or to recursively search all paths from a base location. + +For example: + +```xml + + + + +``` + +In Java, the equivalent code would be: + +```java +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sample/config/mappers/**/*.xml")); + return factoryBean.getObject(); +} +``` + +This will load all the MyBatis mapper XML files in the `sample.config.mappers` package and its sub-packages from the classpath. + +One property that may be required in an environment with container managed transactions is `transactionFactoryClass`. Please see the relevant section in the Transactions chapter. + +In case you are using the multi-db feature you will need to set the `databaseIdProvider` property: + +```xml + + + + sqlserver + db2 + oracle + mysql + + + +``` +```xml + + + + + +``` + +In Java, the equivalent code would be: + +```java +@Bean +public VendorDatabaseIdProvider databaseIdProvider() { + VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); + Properties properties = new Properties(); + properties.setProperty("SQL Server", "sqlserver"); + properties.setProperty("DB2", "db2"); + properties.setProperty("Oracle", "oracle"); + properties.setProperty("MySQL", "mysql"); + databaseIdProvider.setProperties(properties); + return databaseIdProvider; +} + +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource, DatabaseIdProvider databaseIdProvider) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + factoryBean.setDatabaseIdProvider(databaseIdProvider); + return factoryBean.getObject(); +} +``` +NOTE +Since 1.3.0, `configuration` property has been added. It can be specified a `Configuration` instance directly without MyBatis XML configuration file. + +For example: + +```xml + + + + + + + + +``` + +In Java, the equivalent code would be: + +```java +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + + return factoryBean.getObject(); +} +``` + +## Java Configuration Example + +Here is a complete example of a configuration class that combines the properties described above. + +```java +@Configuration +public class MyBatisConfig { + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + + // Setting mapper locations + factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sample/config/mappers/**/*.xml")); + + // Setting configuration property + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + + return factoryBean.getObject(); + } +} +``` + +NOTE +This configuration class must be located within a package scanned by the Spring container (e.g., within the main application package). The class name itself (e.g., `MyBatisConfig`) is arbitrary; only the `@Configuration` annotation is required. diff --git a/src/site/markdown/getting-started.md b/src/site/markdown/getting-started.md new file mode 100644 index 0000000000..c33f7f393d --- /dev/null +++ b/src/site/markdown/getting-started.md @@ -0,0 +1,100 @@ + +# Getting Started + +This chapter will show you in a few steps how to install and setup MyBatis-Spring and how to build a simple transactional application. + +## Installation + +To use the MyBatis-Spring module, you just need to include the `mybatis-spring-${project.version}.jar` file and its dependencies in the classpath. + +If you are using Maven just add the following dependency to your pom.xml: + +```xml + + org.mybatis + mybatis-spring + ${project.version} + +``` + +## Quick Setup + +To use MyBatis with Spring you need at least two things defined in the Spring application context: +an `SqlSessionFactory` and at least one mapper interface. + +In MyBatis-Spring, an `SqlSessionFactoryBean` is used to create an `SqlSessionFactory`. To configure the factory bean, put the following in the Spring configuration file: + +```xml + + + +``` + +```java +@Configuration +public class MyBatisConfig { + @Bean + public SqlSessionFactory sqlSessionFactory() throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource()); + return factoryBean.getObject(); + } +} +``` + +Notice that the `SqlSessionFactory` requires a `DataSource`. This can be any `DataSource` and should be configured just like any other Spring database connection. + +Assume you have a mapper interface defined like the following: + +```java +public interface UserMapper { + @Select("SELECT * FROM users WHERE id = #{userId}") + User getUser(@Param("userId") String userId); +} +``` + +This interface is added to Spring using a `MapperFactoryBean` like the following: + +```xml + + + + +``` + +Note that the mapper class specified **must** be an interface, not an actual implementation class. In this example, annotations are used to specify the SQL, but a MyBatis mapper XML file could also be used. + +Once configured, you can inject mappers directly into your business/service objects in the same way you inject any other Spring bean. +The `MapperFactoryBean` handles creating an `SqlSession` as well as closing it. +If there is a Spring transaction in progress, the session will also be committed or rolled back when the transaction completes. +Finally, any exceptions will be translated into Spring `DataAccessException`s. + +If you use the Java Configuration: + +```java +@Configuration +public class MyBatisConfig { + @Bean + public UserMapper userMapper() throws Exception { + SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory()); + return sqlSessionTemplate.getMapper(UserMapper.class); + } +} +``` + +Calling MyBatis data methods is now only one line of code: + +```java +public class FooServiceImpl implements FooService { + + private final UserMapper userMapper; + + public FooServiceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public User doSomeBusinessStuff(String userId) { + return this.userMapper.getUser(userId); + } +} +``` diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md new file mode 100644 index 0000000000..f789a5692a --- /dev/null +++ b/src/site/markdown/index.md @@ -0,0 +1,57 @@ + +# Introduction + +## What is MyBatis-Spring? + +MyBatis-Spring integrates MyBatis seamlessly with Spring. +This library allows MyBatis to participate in Spring transactions, takes care of building MyBatis mappers and `SqlSession`s and inject them into other beans, translates MyBatis exceptions into Spring `DataAccessException`s, and finally, it lets you build your application code free of dependencies on MyBatis, Spring or MyBatis-Spring. + +## Motivation + +Spring version 2 only supports iBATIS version 2. An attempt was made to add MyBatis 3 support into Spring 3 (see the Spring Jira [issue](https://jira.springsource.org/browse/SPR-5991). +Unfortunately, Spring 3 development ended before MyBatis 3 was officially released. Because the Spring team did not want to release with code based on a non-released version of MyBatis, official Spring support would have to wait. +Given the interest in Spring support for MyBatis, the MyBatis community decided it was time to reunite the interested contributors and add Spring integration as a community sub-project of MyBatis instead. + +## Requirements + +Before starting with MyBatis-Spring integration, it is very important that you are familiar with both MyBatis and Spring terminology. +This document does not attempt to provide background information or basic setup and configuration tutorials for either MyBatis or Spring. + +MyBatis-Spring requires following versions: + +| MyBatis-Spring | MyBatis | Spring Framework | Spring Batch | Java | +|----------------| --- |------------------|--------------|-------------| +| **4.0** | 3.5+ | 7.0+ | 6.0+ | Java 17+ | +| **3.0** | 3.5+ | 6.x | 5.x | Java 17+ | +| **2.1** | 3.5+ | 5.x | 4.x | Java 8+ | +| ~~**2.0**~~ | ~~3.5+~~ | ~~5.x~~ | ~~4.x~~ | ~~Java 8+~~ | +| ~~**1.3**~~ | ~~3.4+~~ | ~~3.2.2+~~ | ~~2.1+~~ | ~~Java 6+~~ | + +## Acknowledgements + +A special thanks goes to all the special people who made this project a reality (in alphabetical order): Eduardo Macarron, Hunter Presnall and Putthiphong Boonphong for the coding, +testing and documentation; Andrius Juozapaitis, Giovanni Cuccu, Mike Lanyon, Raj Nagappan and Tomas Pinos for their contributions; and Simone Tripodi for finding everyone and bringing them all back to the project under MyBatis ;) +Without them, this project wouldn't exist. + +## Help make this documentation better… + +If you find this documentation lacking in any way, or missing documentation for a feature, then the best thing to do is learn about it and then write the documentation yourself! + +Sources of this manual are available in markdown format at [project's Git](https://github.com/mybatis/spring/tree/master/src/site) Fork the repository, update them and send a pull request. + +You’re the best author of this documentation, people like you have to read it! + +## Translations + +Users can read about MyBatis-Spring in the following translations: + + + +Do you want to read about MyBatis in your own native language? Fill an issue providing patches with your mother tongue documentation! + diff --git a/src/site/markdown/mappers.md b/src/site/markdown/mappers.md new file mode 100644 index 0000000000..79a6582e10 --- /dev/null +++ b/src/site/markdown/mappers.md @@ -0,0 +1,182 @@ + +# Injecting Mappers + +Rather than code data access objects (DAOs) manually using `SqlSessionDaoSupport` or `SqlSessionTemplate`, Mybatis-Spring can create a thread safe mapper that you can inject directly into other beans: + +```xml + + + +``` + +Once injected, the mapper is ready to be used in application logic: + +```java +public class FooServiceImpl implements FooService { + + private final UserMapper userMapper; + + public FooServiceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public User doSomeBusinessStuff(String userId) { + return this.userMapper.getUser(userId); + } +} +``` + +Notice that there are no `SqlSession` or MyBatis references in this code. Nor is there any need to create, open or close the session, MyBatis-Spring will take care of that. + + +## Registering a mapper + +The way you register a mapper depends on whether you are using a classic XML configuration or the new 3.0+ Java Config (a.k.a. `@Configuration`). + +### With XML Config + +A mapper is registered to Spring by including a `MapperFactoryBean` in your XML config file like follows: + +```xml + + + + +``` + +If the UserMapper has a corresponding MyBatis XML mapper file in the same classpath location as the mapper interface, it will be parsed automatically by the `MapperFactoryBean`. +There is no need to specify the mapper in a MyBatis configuration file unless the mapper XML files are in a different classpath location. See the `SqlSessionFactoryBean`'s [`configLocation`](factorybean.html) property for more information. + +Note that `MapperFactoryBean` requires either an `SqlSessionFactory` or an `SqlSessionTemplate`. These can be set through the respective `sqlSessionFactory` and `sqlSessionTemplate` properties. +If both properties are set, the `SqlSessionFactory` is ignored. Since the `SqlSessionTemplate` is required to have a session factory set, that factory will be used by `MapperFactoryBean`. + +### With Java Config + +```java +@Configuration +public class MyBatisConfig { + @Bean + public MapperFactoryBean userMapper() throws Exception { + MapperFactoryBean factoryBean = new MapperFactoryBean<>(UserMapper.class); + factoryBean.setSqlSessionFactory(sqlSessionFactory()); + return factoryBean; + } +} +``` + + +## Scanning for mappers + +There is no need to register all your mappers one by one. Instead, you can let MyBatis-Spring scan your classpath for them. + +There are three different ways to do it: + +* Using the `` element. +* Using the annotation `@MapperScan` +* Using a classic Spring xml file and registering the `MapperScannerConfigurer` + +Both `` and `@MapperScan` are features introduced in MyBatis-Spring 1.2.0. `@MapperScan` requires Spring 3.1+. + +Since 2.0.2, mapper scanning feature support an option (`lazy-initialization`) that control lazy initialization enabled/disabled of mapper bean. +The motivation for adding this option is supporting a lazy initialization control feature supported by Spring Boot 2.2. The default of this option is `false` (= not use lazy initialization). +If developer want to use lazy initialization for mapper bean, it should be set to the `true` expressly. + +IMPORTANT +If use the lazy initialization feature, the developer need to understand following limitations. If any of following conditions are matches, usually the lazy initialization feature cannot use on your application. + +* When refers to the statement of **other mapper** using ``(`@One`) and ``(`@Many`) +* When includes to the fragment of **other mapper** using `` +* When refers to the cache of **other mapper** using ``(`@CacheNamespaceRef`) +* When refers to the result mapping of **other mapper** using `]]> - -

Follows below a sample configuration snippet:

- - - - -
]]> - - reader() { - return new MyBatisPagingItemReaderBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") - .build(); -}]]> - -

Explaining a more complex example:

- - - - - - - - -]]> - - dateBasedCriteriaReader( - @Value("#{@datesParameters}") Map datesParameters) throws Exception { - return new MyBatisPagingItemReaderBuilder() - .sqlSessionFactory(batchReadingSessionFactory()) - .queryId("com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot") - .parameterValues(datesParameters) - .pageSize(200) - .build(); -} - -@StepScope -@Bean -public Map datesParameters( - @Value("#{jobExecutionContext['EXTRACTION_START_DATE']}") LocalDate yesterday, - @Value("#{jobExecutionContext['TODAY_DATE']}") LocalDate today, - @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}") LocalDate firstDayOfTheMonth, - @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}") LocalDate firstDayOfThePreviousMonth) { - Map map = new HashMap<>(); - map.put("yesterday", yesterday); - map.put("today", today); - map.put("first_day_of_the_month", firstDayOfTheMonth); - map.put("first_day_of_the_previous_month", firstDayOfThePreviousMonth); - return map; -}]]> - -

- The previous example makes use of a few different things: -

- -
    -
  • sqlSessionFactory: You can specify your own sessionFactory to the reader, it might be - useful if you want to read from several databases.
  • -
  • queryId: If the base code have several tables or databases to read from, and that you have different - queries, it might be interesting to use different mapper files with different namespaces. - so when referring to the query, don't forget about the namespace of the mapper file.
  • -
  • parameterValues: You can pass additional parameters via this map, the example above uses - a map that is build by spring using a SpEL expression taking values from the jobExecutionContext. - The keys of the map will be used by MyBatis in the mapper file (ex: - yesterday could be used as #{yesterday,jdbcType=TIMESTAMP}). - Note that the map and the reader are both built in the step scope in order to be able to use - the Spring EL expression with the jobExecutionContext. Also if MyBatis type handlers - are correctly configured you can pass custom instances like the parameters of this map that are JodaTime - dates.
  • -
  • pageSize: If the batch flow is configured with chunk size, it is relevant to pass this - information to the reader as well, which is done via this property.
  • -
- - - - -

- This bean is an ItemReader that reads records from a database using a cursor. -

- -

- NOTE To use this bean you need at least MyBatis 3.4.0 or a newer version. -

- -

- It executes the query specified as the setQueryId property to retrieve requested data - by using the method selectCursor(). - Each time a read() method is called it will return the next element of the cursor until no more - elements are left. -

- -

- The reader will use a separate connection so the select statement does no participate in any transactions created - as part of the step processing. -

- -

When using the cursor you can just execute a regular query:

- - SELECT id, name, job FROM employees ORDER BY id ASC -]]> - -

Follows below a sample configuration snippet:

- - - - -
]]> - - reader() { - return new MyBatisCursorItemReaderBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") - .build(); -}]]> - - - - - -

- It is an ItemWriter that uses the batching features from SqlSessionTemplate - to execute a batch of statements for all items provided. - The SqlSessionFactory needs to be configured with a BATCH executor. -

- -

- When write() is called it executes the mapped statement indicated in the property statementId. - It is expected that write() is called inside a transaction.
-

- -

Follows below a sample configuration snippet:

- - - - -
]]> - - writer() { - return new MyBatisBatchItemWriterBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .statementId("com.my.name.space.batch.EmployeeMapper.updateEmployee") - .build(); -}]]> - -

Writing to different tables using composite writers (with some caveats):

- -

This technique can only be used with MyBatis 3.2+, as there was an - issue in previous - versions that made the writer misbehave. -

- -

If the batch needs to write complex data, like records with associations, or even to different databases, then - it is possible to work around the fact that insert statements only insert in one table. In order to make it - happen the batch have to prepare the Item to be written by the writer. However depending on the - constraints, opportunities or insight on the processed data it might be interesting to use the following technique. - The following trick can work on items with simple associations or just with unrelated tables. -

- -

In a processor craft the Spring Batch Item in such way it will hold all the different records. - Suppose for each Item there is an Interaction that have one association - InteractionMetadata, and two non associated rows VisitorInteraction and - CustomerInteraction, the holder object will look like: -

- - - -

Then in the spring configuration there will be a CompositeItemWriter that will use delegate - writers specifically configured for each kind of records. Note that as the InteractionMetadata is - an association in the example it will need to be written first so that Interaction can have the updated key. -

- - - - - - - - - - - - -
]]> - - interactionsItemWriter() { - CompositeItemWriter compositeItemWriter = new CompositeItemWriter(); - List> writers = new ArrayList<>(4); - writers.add(visitorInteractionsWriter()); - writers.add(customerInteractionsWriter()); - writers.add(interactionMetadataWriter()); - writers.add(interactionWriter()); - compositeItemWriter.setDelegates(writers); - return compositeItemWriter; -}]]> - -

Then each delegate writer will be configured as needed; for example for Interaction and - InteractionMetadata: -

- - -]]> - -

Same as the reader the statementId can refer to the statement with the prefixed namespace.

- -

Now in the mapper file the statement have to be crafted for each kind of records in the following way:

- - - - - - -]]> - -

What's happening is that first the insertInteractionMetadata will be called, and the update - statement is configured to return the ids created by the jdbc driver (keyProperty and keyColumn). - As the InteractionMetadata object were updated by this query the next query can be used to write the parent - object Interaction via insertInteraction. -

- -

However note that JDBC drivers don't behave the same in this regard. At the time of this writing - the H2 driver 1.3.168 will only return the latest index even in BATCH mode (see org.h2.jdbc.JdbcStatement#getGeneratedKeys), - while the MySQL JDBC driver will behave as expected and return all the IDs. -

- - - - \ No newline at end of file diff --git a/src/site/xdoc/boot.xml b/src/site/xdoc/boot.xml deleted file mode 100644 index ed8b27a157..0000000000 --- a/src/site/xdoc/boot.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - MyBatis-Spring | Spring Boot - Eduardo Macarron - - - -
-

- Please see the MyBatis Spring-boot-stater - subproject docs for details. -

-
- -
diff --git a/src/site/xdoc/factorybean.xml b/src/site/xdoc/factorybean.xml deleted file mode 100644 index 45904afe52..0000000000 --- a/src/site/xdoc/factorybean.xml +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - MyBatis-Spring | SqlSessionFactoryBean - Hunter Presnall - Eduardo Macarron - - - -
-

- In base MyBatis, the SqlSessionFactory is built using - SqlSessionFactoryBuilder. In MyBatis-Spring, - SqlSessionFactoryBean is used instead. -

- - -

- To create the factory bean, put the following in the Spring XML - configuration file: -

- - -]]> -

- Note that - SqlSessionFactoryBean - implements Spring's - FactoryBean - interface (see the Spring documentation(Core Technologies -Customizing instantiation logic with a FactoryBean-)). - This means that the bean Spring ultimately creates is - not - the - SqlSessionFactoryBean - itself, but what the - factory returns as a result of the - getObject() - call on - the factory. In this case, Spring will build an - SqlSessionFactory - for you at application startup and - store it with the name - sqlSessionFactory - . In Java, the - equivalent code would be: -

- - - -

- In normal MyBatis-Spring usage, you will not need to use - SqlSessionFactoryBean - or the corresponding - SqlSessionFactory - directly. Instead, the session - factory will be injected into - MapperFactoryBeans or - other DAOs that extend - SqlSessionDaoSupport - . -

-
- - - -

- SqlSessionFactory - has a single required property, the - JDBC - DataSource - . This can be any - DataSource - and should be configured just - like any other Spring database - connection. -

- -

- One common property is - configLocation - which is used to - specify the location of the MyBatis XML - configuration file. One case - where this is needed is if the base - MyBatis configuration needs to be - changed. Usually this will be - <settings> - or - <typeAliases> - sections. -

- -

- Note that this config file does - not - need to be a - complete MyBatis config. Specifically, any environments, - data sources - and MyBatis transaction managers will be - ignored - . - SqlSessionFactoryBean - creates its own, custom MyBatis - Environment - with these values set as required. -

- -

- Another reason to require a config file is if the MyBatis mapper XML - files are not in the same classpath location as the mapper classes. - With - this configuration, there are two options. This first is to - manually - specify the classpath of the XML files using a - <mappers> - section in the MyBatis config - file. A second option is to use the - mapperLocations - property of the factory bean. -

- -

- The - mapperLocations - property takes a list of resource - locations. This property can be - used to specify the location of MyBatis - XML mapper files. The value - can contain Ant-style patterns to load all - files in a directory or to - recursively search all paths from a base - location. For example: -

- - - - -]]> - -

- This will load all the MyBatis mapper XML files in the - sample.config.mappers package and its sub-packages from the - classpath. -

- -

- One property that may be required in an environment with container - managed transactions is - transactionFactoryClass - . - Please see the relevant section in the - Transactions chapter. -

- -

- In case you are using the multi-db feature you will need to set the databaseIdProvider property: -

- - - - - sqlserver - db2 - oracle - mysql - - - - - - - - -]]> - -

- NOTE - Since 1.3.0, configuration property has been added. - It can be specified a Configuration instance directly without MyBatis XML configuration file. - For example: -

- - - - - - - - -]]> - -
-
- -
diff --git a/src/site/xdoc/getting-started.xml.vm b/src/site/xdoc/getting-started.xml.vm deleted file mode 100644 index 3e18537423..0000000000 --- a/src/site/xdoc/getting-started.xml.vm +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - MyBatis-Spring | Getting Started - Hunter Presnall - Eduardo Macarron - - - -
-

- This chapter will show you in a few steps how to install and setup - MyBatis-Spring and how to build - a simple transactional application. -

- - -

- To use the MyBatis-Spring module, you just need to include the - mybatis-spring-${project.version}.jar - file and its dependencies in the classpath. -

-

- If you are using Maven just add the following dependency - to your - pom.xml: -

- - org.mybatis - mybatis-spring - ${project.version} -]]> -
- - -

- To use MyBatis with Spring you need at least two things defined in - the - Spring application context: an - SqlSessionFactory - and - at least one mapper interface. -

- -

- In MyBatis-Spring, an - SqlSessionFactoryBean - is used to - create an - SqlSessionFactory - . To configure the - factory bean, put the following in the Spring - configuration file: -

- - - -]]> - - - -

- Notice that the - SqlSessionFactory - requires a - DataSource - . This can be any - DataSource - and should be configured just like any other Spring database - connection. -

- -

- Assume you have a mapper interface defined like the following: -

- - -

- This interface is added to Spring using a - MapperFactoryBean - like the following: -

- - - -]]> - -

- Note that the mapper class specified - must - be an - interface, not an actual implementation class. In this example, - annotations are used to specify the SQL, but a MyBatis mapper XML - file - could also be used. -

- -

- Once configured, you can inject mappers directly into your - business/service objects in the same way you inject any other Spring - bean. The MapperFactoryBean - handles creating an SqlSession - as well as closing it. If there is a Spring transaction in - progress, - the session will also be committed or rolled back when the - transaction completes. Finally, any exceptions will be translated - into Spring DataAccessExceptions. -

- -

- If you use the Java Configuration: -

- - - -

- Calling MyBatis data methods is now only one line of code: -

- -
-
- -
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml deleted file mode 100644 index f42067c862..0000000000 --- a/src/site/xdoc/index.xml +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - MyBatis-Spring | Introduction - Hunter Presnall - Eduardo Macarron - - - -
- -

- MyBatis-Spring integrates MyBatis seamlessly with Spring. - This library allows MyBatis to participate in Spring transactions, takes care of - building MyBatis mappers and SqlSessions and inject them into other beans, - translates MyBatis exceptions into Spring DataAccessExceptions, - and finally, it lets you build your application code free of - dependencies on MyBatis, Spring or MyBatis-Spring. -

-
- - -

- Spring version 2 only supports iBATIS version 2. An attempt was made to - add MyBatis 3 support into Spring 3 (see the Spring Jira - issue). - Unfortunately, Spring 3 development ended before MyBatis 3 was - officially released. Because the Spring team did not want to release with - code based on a non-released version of MyBatis, official Spring support - would have to wait. Given the interest in Spring support for MyBatis, - the MyBatis community decided it was time to reunite the interested contributors - and add Spring integration as a community sub-project of MyBatis instead. -

-
- - -

- Before starting with MyBatis-Spring integration, it is very important - that you are familiar with both MyBatis and Spring terminology. This - document does not attempt to provide background information or basic - setup and configuration tutorials for either MyBatis or Spring. -

-

- MyBatis-Spring requires following versions: -

- - - - - - - - - - - - - - - - - - - - - - - - - - -
- MyBatis-Spring - - MyBatis - - Spring Framework - - Spring Batch - - Java -
- 2.0 - - 3.4+ - - 5.0+ - - 4.0+ - - Java 8+ -
- 1.3 - - 3.4+ - - 3.2.2+ - - 2.1+ - - Java 6+ -
-
- - -

- A special thanks goes to all the special people who made this project - a reality (in alphabetical order): Eduardo Macarron, Hunter Presnall - and Putthiphong Boonphong for the coding, testing and documentation; - Andrius Juozapaitis, Giovanni Cuccu, Mike Lanyon, Raj Nagappan and Tomas Pinos - for their contributions; and Simone Tripodi for finding everyone and - bringing them all back to the project under MyBatis ;) Without them, - this project wouldn't exist. -

-
- - -

- If you find this documentation lacking in any way, or missing - documentation for a feature, then the best thing to do is learn - about it and then write the documentation yourself! -

-

- Sources of this manual are available in xdoc format at - project's Git - Fork the repository, update them and send a pull request. -

-

- You’re the best author of this documentation, people like you have - to read it! -

-
- - -

Users can read about MyBatis-Spring in the following translations:

- -

Do you want to read about MyBatis in your own native language? Fill an issue providing patches with your - mother tongue documentation!

-
-
- - -
diff --git a/src/site/xdoc/mappers.xml b/src/site/xdoc/mappers.xml deleted file mode 100644 index 8f7cf3eeb0..0000000000 --- a/src/site/xdoc/mappers.xml +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - MyBatis-Spring | Injecting Mappers - Hunter Presnall - Eduardo Macarron - - - -
-

- Rather than code data access objects (DAOs) manually using - SqlSessionDaoSupport or - SqlSessionTemplate, Mybatis-Spring can create a - thread safe mapper that you can inject directly into other beans: -

- - - -]]> - -

- Once injected, the mapper is ready to be used in application logic: -

- -

- Notice that there are no SqlSession or MyBatis - references in this code. Nor is there any need to create, open or close - the session, MyBatis-Spring will take care of that. -

- - -

- The way you register a mapper depends on whether you are using a classic XML configuration - or the new 3.0+ Java Config (a.k.a. @Configuration).

- -

With XML Config

- -

- A mapper is registered to Spring by including a MapperFactoryBean in your XML - config file like follows: -

- - - -]]> - -

- If the UserMapper has a corresponding MyBatis XML mapper file - in the same classpath location as the mapper interface, it will be - parsed automatically by the MapperFactoryBean. There - is no need to specify the mapper in a MyBatis configuration file unless - the mapper XML files are in a different classpath location. See the - SqlSessionFactoryBean's - configLocation - property for more information. -

- -

- Note that MapperFactoryBean requires either an - SqlSessionFactory or an SqlSessionTemplate. - These can be set through the respective sqlSessionFactory and - sqlSessionTemplate properties. - If both properties are set, the SqlSessionFactory is ignored. - Since the SqlSessionTemplate is required to have a session - factory set, that factory will be used by MapperFactoryBean. -

- -

With Java Config

- -

- When using Spring Java Config you can get a mapper directly out - of an SqlSessionTemplate like follows: -

- - - -

- Note that you can not return a mapper got from the MyBatis default SqlSession because - it would not be thread safe and will only live until the SqlSession - it was created from is closed. You must use an - SqlSessionTemplate instead, - as shown in the sample. -

-
- - -

- There is no need to register all your mappers one by one. - Instead, you can let MyBatis-Spring scan your classpath for them. -

- -

- There are three different ways to do it: -

-
    -
  • Using the <mybatis:scan/> element.
  • -
  • Using the annotation @MapperScan
  • -
  • Using a classic Spring xml file and registering the MapperScannerConfigurer
  • -
- -

Both <mybatis:scan/> and @MapperScan are features introduced in MyBatis-Spring 1.2.0. - @MapperScan requires Spring 3.1+.

- -

<mybatis:scan/>

- -

- The <mybatis:scan/> XML element will search for mappers - in a very similar way than the Spring built-in element <context:component-scan/> - searches for beans. -

- -

Follows below a sample XML configuration:

- - - - - - - -]]> - -

- The base-package attribute lets you set the base package - for your mapper interface files. You can set more than one package by - using a semicolon or comma as a separator. Mappers will be searched for - recursively starting in the specified package(s). -

- -

- Notice that there is no need to specify a SqlSessionFactory or - SqlSessionTemplate as an attribute in the <mybatis:scan/> - element because it will create MapperFactoryBeans that can be autowired. - But if you are using more than one DataSource autowire may not work for you. - In this case you can use the factory-ref or - template-ref attributes to set the right bean name to use. -

- -

- <mybatis:scan/> supports filtering the mappers - created by either specifying a marker interface or an annotation. The - annotation property specifies an annotation to - search for. The marker-interface attribute specifies a - parent interface to search for. If both properties are specified, mappers - are added for interfaces that match either criteria. - By default, these two properties are null, so all interfaces in the given - base package(s) will be loaded as mappers. -

- -

- Discovered mappers will be named using Spring default naming strategy for - autodetected components (see the Spring reference document(Core Technologies -Naming autodetected components-)). - That is, if no annotation is found, it will use the uncapitalized non-qualified class - name of the mapper. But if either a @Component or a JSR-330 @Named annotation is - found it will get the name from the annotation. - Notice that you can set the annotation attribute - to org.springframework.stereotype.Component, - javax.inject.Named (if you have JSE 6) or to your own annotation - (that must be itself annotated) so the annotation will work both as a marker - and as a name provider. -

- -

- NOTE <context:component-scan/> - won't be able to scan and register mappers. Mappers are interfaces and, in order to register them to - Spring, the scanner must know how to create a MapperFactoryBean for each interface - it finds. -

- -

@MapperScan

- -

- If you are using the Spring Java Configuration (a.k.a @Configuration) you would - prefer to use the @MapperScan rather than the - <mybatis:scan/>. -

- -

The @MapperScan annotation is used as follows:

- - - -

The annotation works in the same exact way than <mybatis:scan/> we - saw in the previous section. It also lets you specify a marker interface or an annotation class - through its properties markerInterface and annotationClass. - You can also provide an specific SqlSessionFactory or SqlSessionTemplate - by using its properties sqlSessionFactory and sqlSessionTemplate. -

- -

MapperScannerConfigurer

- -

- The MapperScannerConfigurer is a BeanDefinitionRegistryPostProcessor that - can be included in a classic xml application context as a normal bean. - To set up a MapperScannerConfigurer add the following to the Spring configuration: -

- - -]]> - -

- If you need to specify an specific sqlSessionFactory or sqlSessionTemplate - note that bean names are required, - not bean references, thus the value attribute is used instead of the - usual ref: -

- ]]> - -

- NOTE sqlSessionFactoryBean and - sqlSessionTemplateBean properties were the only option available up to MyBatis-Spring 1.0.2 - but given that the MapperScannerConfigurer runs earlier in the startup - process that PropertyPlaceholderConfigurer there were frequent errors. - For that purpose that properties have been deprecated and the new properties - sqlSessionFactoryBeanName and sqlSessionTemplateBeanName - are recommended. -

-
-
- -
diff --git a/src/site/xdoc/sample.xml b/src/site/xdoc/sample.xml deleted file mode 100644 index 31fe7bfc59..0000000000 --- a/src/site/xdoc/sample.xml +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - MyBatis-Spring | Sample Code - Eduardo Macarron - - - -
- -

- NOTE - See JPetstore 6 demo to know about how to use Spring with a full web application server. -

- -

- You can check out sample code from the MyBatis-Spring repo: -

-

- Any of the samples can be run with JUnit 5. -

-

- The sample code shows a typical design where a transactional service gets domain objects from a data access layer. -

-

- FooService.java acts as the service: -

- -

- It is a transactional bean, so when the method is called, the transaction is started - and the transaction is committed when the method ends without throwing an uncaught exception. - Notice that transactional behaviour is configured with the - @Transactional - attribute. This is not required; any other way provided by Spring can be used to demarcate - your transactions. -

-

- This service calls a data access layer built with MyBatis. This layer - consists on a just an interface UserMapper.java - that will be used with a dynamic proxy built by MyBatis at - runtime and injected into the service by Spring. -

- -

- Note that, for the sake of simplicity we used the interface UserMapper.java for the DAO scenario - where a DAO is built with an interface and a implementation though in this case it would have been more - adequate to use an interface called UserDao.java instead. -

-

- We will see different ways to find the mapper interface, register it to Spring and inject it into the service bean: -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Scenarios
Sample testDescription
- SampleMapperTest.java - - Shows you the base configuration based on a MapperFactoryBean - that will dynamically build an implementation for UserMapper -
- SampleScannerTest.java - - Shows how to use the MapperScannerConfigurer so all the mappers in a project are autodiscovered. -
- SampleSqlSessionTest.java - - Shows how to hand code a DAO using a Spring managed SqlSession - and providing your own implementation UserDaoImpl.java. -
- SampleEnableTest - - Shows how to use Spring's @Configuration with the @MapperScann annotation so - mappers are autodiscovered. -
- SampleNamespaceTest - - Shows how to use the custom MyBatis XML namespace. -
- SampleJavaConfigTest.java - - Shows how to use Spring's @Configuration to create MyBatis beans manually. -
- SampleJobJavaConfigTest.java - - Shows how to use ItemReader and ItemWriter on Spring Batch using Java Configuration. -
- SampleJobXmlConfigTest.java - - Shows how to use ItemReader and ItemWriter on Spring Batch using XML Configuration. -
-

- Please take a look at the different applicationContext.xml files to see MyBatis-Spring in action. -

- -
- -
- diff --git a/src/site/xdoc/sqlsession.xml b/src/site/xdoc/sqlsession.xml deleted file mode 100644 index e224fd701a..0000000000 --- a/src/site/xdoc/sqlsession.xml +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - MyBatis-Spring | Using an SqlSession - Hunter Presnall - Eduardo Macarron - - - -
-

- In MyBatis you use the SqlSessionFactory to create an - SqlSession. Once you have a session, you use it to - execute your mapped statements, commit or rollback connections and - finally, when it is no longer needed, you close the session. With - MyBatis-Spring you don't need to use SqlSessionFactory - directly because your beans can be injected with a thread safe - SqlSession that automatically commits, rollbacks and - closes the session based on Spring's transaction configuration. -

- - -

- SqlSessionTemplate is the heart of MyBatis-Spring. - It implements SqlSession and is meant to be a drop-in replacement - for any existing use of SqlSession in your code. - SqlSessionTemplate is thread safe and can be shared - by multiple DAOs or mappers. -

- -

- When calling SQL methods, including any method from Mappers returned by - getMapper(), SqlSessionTemplate - will ensure that the SqlSession used is the one - associated with the current Spring transaction. In addition, it manages - the session life-cycle, including closing, committing or rolling back the - session as necessary. It will also translate MyBatis exceptions - into Spring DataAccessExceptions. -

- -

- SqlSessionTemplate should always - be used instead of default MyBatis implementation DefaultSqlSession - because the template can participate in Spring transactions and is thread safe for use by - multiple injected mapper classes. Switching between the two classes in the - same application can cause data integrity issues. -

- -

- A SqlSessionTemplate can be constructed - using an SqlSessionFactory as a constructor argument. -

- - -]]> - - - -

- This bean can now be injected directly in your DAO beans. You need a - SqlSession property in your bean like the following -

- -

- And inject the SqlSessionTemplate as follows -

- - -]]> - -

- SqlSessionTemplate has also a constructor that takes - an ExecutorType as an argument. This allows you to - construct, for example, a batch SqlSession by using - the following in Spring's configuration file: -

- - - -]]> - - - -

- Now all your statements will be batched so the following could be coded - in a DAO -

- users) { - for (User user : users) { - sqlSession.insert("org.mybatis.spring.sample.mapper.UserMapper.insertUser", user); - } -}]]> - -

- Note that this configuration style only needs to be used if the desired - execution method differs from the default set for the - SqlSessionFactory. -

- -

- The caveat to this form is that - there cannot be an existing transaction running with - a different ExecutorType when this method is called. Either ensure that - calls to SqlSessionTemplates with different executor - types run in a separate transaction (e.g. with PROPAGATION_REQUIRES_NEW) or - completely outside of a transaction. -

-
- - -

- SqlSessionDaoSupport is an abstract support class that - provides you with a SqlSession. Calling - getSqlSession() you will get a SqlSessionTemplate - which can then be used to execute SQL methods, like the following: -

- -

- Usually MapperFactoryBean is preferred to this class, - since it requires no extra code. But, this class is useful if you need - to do other non-MyBatis work in your DAO and concrete classes are - required. -

- -

- SqlSessionDaoSupport requires either an - sqlSessionFactory or an sqlSessionTemplate - property to be set. - If both properties are set, the sqlSessionFactory is ignored. -

- -

- Assuming a class UserDaoImpl that subclasses - SqlSessionDaoSupport, it can be configured in Spring - like the following: -

- - -]]> -
-
- -
diff --git a/src/site/xdoc/transactions.xml b/src/site/xdoc/transactions.xml deleted file mode 100644 index a592749192..0000000000 --- a/src/site/xdoc/transactions.xml +++ /dev/null @@ -1,181 +0,0 @@ - - - - - - MyBatis-Spring | Transactions - Hunter Presnall - Eduardo Macarron - - - -
-

- One of the primary reasons for using MyBatis-Spring is that it allows - MyBatis to participate in Spring transactions. Rather than create a new - transaction manager specific to MyBatis, MyBatis-Spring leverages the - existing DataSourceTransactionManager in Spring. -

-

- Once a Spring transaction manager is configured, you can configure - transactions in Spring as you normally would. Both - @Transactional annotations and AOP style - configurations are supported. A single SqlSession - object will be created and used for the duration of the transaction. This - session will be committed or rolled back as appropriate when then - transaction completes. -

-

- MyBatis-Spring will transparently manage transactions once they are set up. - There is no need for additional code in your DAO classes. -

- - -

- To enable Spring transaction processing, simply create a - DataSourceTransactionManager in your Spring - configuration file: -

- - - -]]> - - - -

- The DataSource specified can be any JDBC - DataSource you would normally - use with Spring. This includes connection pools as well as DataSources - obtained through JNDI lookups. -

-

- Note that the DataSource specified for the transaction - manager must be the same one that is used to create - the SqlSessionFactoryBean or transaction management will - not work. -

-
- - -

- If you are using a JEE container and would like Spring to participate in - container managed transactions (CMT), then Spring should be configured - with a JtaTransactionManager or one of its container - specific subclasses. The easiest way to do this is to use the Spring - transaction namespace or the JtaTransactionManagerFactoryBean: -

- ]]> - - - -

- In this configuration, MyBatis will behave like any other Spring - transactional resource configured with CMT. Spring will automatically use - any existing container transaction and attach an SqlSession to it. If no - transaction is started and one is needed based on the transaction - configuration, Spring will start a new container managed transaction. -

-

- Note that if you want to use CMT and do not want to - use Spring transaction management, you must not - configure any Spring transaction manager and you must - also configure the SqlSessionFactoryBean to use the base - MyBatis ManagedTransactionFactory: -

- - - - - -]]> - - -
- - -

- MyBatis SqlSession provides you with specific methods to handle - transactions programmatically. But when using MyBatis-Spring your beans will be - injected with a Spring managed SqlSession or a Spring managed mapper. - That means that Spring will always handle your transactions. -

-

- You cannot call SqlSession.commit(), SqlSession.rollback() - or SqlSession.close() over a Spring managed SqlSession. - If you try to do so, a UnsupportedOperationException exception - will be thrown. Note these methods are not exposed in injected mapper classes. -

-

- Regardless of your JDBC connection's autocommit setting, any execution of a - SqlSession data method or any call to a mapper - method outside a Spring transaction will be automatically committed. -

-

- If you want to control your transactions programmatically please refer to - the Spring reference document(Data Access -Programmatic transaction management-). - This code shows how to handle a transaction manually using the PlatformTransactionManager. -

- - -

- You can omit to call the commit and rollback method using the TransactionTemplate. -

- - { - userMapper.insertUser(user); - return null; -});]]> - -

- Notice that this code uses a mapper, but it will also work with a SqlSession. -

-
-
- -
diff --git a/src/site/xdoc/using-api.xml b/src/site/xdoc/using-api.xml deleted file mode 100644 index 944fc8e014..0000000000 --- a/src/site/xdoc/using-api.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - MyBatis-Spring | Using the MyBatis API - Hunter Presnall - Eduardo Macarron - - - -
-

- With MyBatis-Spring, you can continue to directly use the MyBatis API. - Simply create an SqlSessionFactory in Spring using - SqlSessionFactoryBean and use the factory in your code. -

- - -

- Use this option with care because wrong usage may produce runtime errors or - worse, data integrity problems. Be aware of the following caveats with direct API usage: -

-
    -
  • -

    - It will not participate in any Spring transactions. -

    -
  • -
  • -

    - If the SqlSession is using a DataSource - that is also being used by a Spring transaction manager and there is currently - a transaction in progress, this code will throw an exception. -

    -
  • -
  • -

    - MyBatis' DefaultSqlSession is not thread safe. If you - inject it in your beans you will get errors. -

    -
  • -
  • -

    - Mappers created using DefaultSqlSession are not thread safe either. - If you inject them it in your beans you will get errors. -

    -
  • -
  • -

    - You must make sure that your SqlSessions - are always closed in a finally block. -

    -
  • -
-
- -
diff --git a/src/site/zh/xdoc/batch.xml b/src/site/zh/xdoc/batch.xml deleted file mode 100644 index ca405d5d7c..0000000000 --- a/src/site/zh/xdoc/batch.xml +++ /dev/null @@ -1,338 +0,0 @@ - - - - - - MyBatis-Spring | Spring Batch - Eduardo Macarron - Brice Dutheil - - - -
-

- As of version 1.1.0 MyBatis-Spring provides three beans for building Spring Batch applications: the MyBatisPagingItemReader, - the MyBatisCursorItemReader and the MyBatisBatchItemWriter. - Also, As of version 2.0.0 provides three builder classes for supporting the Java Configuration: the MyBatisPagingItemReaderBuilder, - the MyBatisCursorItemReaderBuilder and the MyBatisBatchItemWriterBuilder. -

- -

- NOTE This is about Spring Batch and not - about MyBatis batch SqlSessions. For information about batch sessions go to section Using an SqlSession. -

- - -

- This bean is an ItemReader that reads records from a database in a paging fashion. -

- -

- It executes the query specified as the setQueryId property to retrieve requested data. - The query is executed using paged requests of a size specified in setPageSize property. - Additional pages are requested when needed as read() method is called, returning an object corresponding - to current position. - - Some standard query parameters are provided by the reader and the SQL in the named query must use some or all of these parameters - (depending on the SQL variant) to construct a result set of the required size. The parameters are: -

- -
    -
  • _page: the page number to be read (starting at 0)
  • -
  • _pagesize: the size of the pages, i.e. the number of rows to return
  • -
  • _skiprows: the product of _page and - _pagesize
  • -
- -

And they could be mapped as the follow in a select statement:

- - SELECT id, name, job FROM employees ORDER BY id ASC LIMIT #{_skiprows}, #{_pagesize} -]]> - -

Follows below a sample configuration snippet:

- - - - -]]> - - reader() { - return new MyBatisPagingItemReaderBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") - .build(); -}]]> - -

Explaining a more complex example:

- - - - - - - - -]]> - - dateBasedCriteriaReader( - @Value("#{@datesParameters}") Map datesParameters) throws Exception { - return new MyBatisPagingItemReaderBuilder() - .sqlSessionFactory(batchReadingSessionFactory()) - .queryId("com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot") - .parameterValues(datesParameters) - .pageSize(200) - .build(); -} - -@StepScope -@Bean -public Map datesParameters( - @Value("#{jobExecutionContext['EXTRACTION_START_DATE']}") LocalDate yesterday, - @Value("#{jobExecutionContext['TODAY_DATE']}") LocalDate today, - @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}") LocalDate firstDayOfTheMonth, - @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}") LocalDate firstDayOfThePreviousMonth) { - Map map = new HashMap<>(); - map.put("yesterday", yesterday); - map.put("today", today); - map.put("first_day_of_the_month", firstDayOfTheMonth); - map.put("first_day_of_the_previous_month", firstDayOfThePreviousMonth); - return map; -}]]> - -

- The previous example makes use of a few different things: -

- -
    -
  • sqlSessionFactory: You can specify your own sessionFactory to the reader, it might be - useful if you want to read from several databases.
  • -
  • queryId: If the base code have several tables or databases to read from, and that you have different - queries, it might be interesting to use different mapper files with different namespaces. - so when referring to the query, don't forget about the namespace of the mapper file.
  • -
  • parameterValues: You can pass additional parameters via this map, the example above uses - a map that is build by spring using a SpEL expression taking values from the jobExecutionContext. - The keys of the map will be used by MyBatis in the mapper file (ex: - yesterday could be used as #{yesterday,jdbcType=TIMESTAMP}). - Note that the map and the reader are both built in the step scope in order to be able to use - the Spring EL expression with the jobExecutionContext. Also if MyBatis type handlers - are correctly configured you can pass custom instances like the parameters of this map that are JodaTime - dates.
  • -
  • pageSize: If the batch flow is configured with chunk size, it is relevant to pass this - information to the reader as well, which is done via this property.
  • -
- -
- - -

- This bean is an ItemReader that reads records from a database using a cursor. -

- -

- NOTE To use this bean you need at least MyBatis 3.4.0 or a newer version. -

- -

- It executes the query specified as the setQueryId property to retrieve requested data - by using the method selectCursor(). - Each time a read() method is called it will return the next element of the cursor until no more - elements are left. -

- -

- The reader will use a separate connection so the select statement does no participate in any transactions created - as part of the step processing. -

- -

When using the cursor you can just execute a regular query:

- - SELECT id, name, job FROM employees ORDER BY id ASC -]]> - -

Follows below a sample configuration snippet:

- - - - -]]> - - reader() { - return new MyBatisCursorItemReaderBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") - .build(); -}]]> - -
- - - -

- It is an ItemWriter that uses the batching features from SqlSessionTemplate - to execute a batch of statements for all items provided. - The SqlSessionFactory needs to be configured with a BATCH executor. -

- -

- When write() is called it executes the mapped statement indicated in the property statementId. - It is expected that write() is called inside a transaction.
-

- -

Follows below a sample configuration snippet:

- - - - -]]> - - writer() { - return new MyBatisBatchItemWriterBuilder() - .sqlSessionFactory(sqlSessionFactory()) - .statementId("com.my.name.space.batch.EmployeeMapper.updateEmployee") - .build(); -}]]> - -

Writing to different tables using composite writers (with some caveats):

- -

This technique can only be used with MyBatis 3.2+, as there was an - issue in previous - versions that made the writer misbehave. -

- -

If the batch needs to write complex data, like records with associations, or even to different databases, then - it is possible to work around the fact that insert statements only insert in one table. In order to make it - happen the batch have to prepare the Item to be written by the writer. However depending on the - constraints, opportunities or insight on the processed data it might be interesting to use the following technique. - The following trick can work on items with simple associations or just with unrelated tables. -

- -

In a processor craft the Spring Batch Item in such way it will hold all the different records. - Suppose for each Item there is an Interaction that have one association - InteractionMetadata, and two non associated rows VisitorInteraction and - CustomerInteraction, the holder object will look like: -

- - - -

Then in the spring configuration there will be a CompositeItemWriter that will use delegate - writers specifically configured for each kind of records. Note that as the InteractionMetadata is - an association in the example it will need to be written first so that Interaction can have the updated key. -

- - - - - - - - - - - - -]]> - - interactionsItemWriter() { - CompositeItemWriter compositeItemWriter = new CompositeItemWriter(); - List> writers = new ArrayList<>(4); - writers.add(visitorInteractionsWriter()); - writers.add(customerInteractionsWriter()); - writers.add(interactionMetadataWriter()); - writers.add(interactionWriter()); - compositeItemWriter.setDelegates(writers); - return compositeItemWriter; -}]]> - -

Then each delegate writer will be configured as needed; for example for Interaction and - InteractionMetadata: -

- - -]]> - -

Same as the reader the statementId can refer to the statement with the prefixed namespace.

- -

Now in the mapper file the statement have to be crafted for each kind of records in the following way:

- - - - - - -]]> - -

What's happening is that first the insertInteractionMetadata will be called, and the update - statement is configured to return the ids created by the jdbc driver (keyProperty and keyColumn). - As the InteractionMetadata object were updated by this query the next query can be used to write the parent - object Interaction via insertInteraction. -

- -

However note that JDBC drivers don't behave the same in this regard. At the time of this writing - the H2 driver 1.3.168 will only return the latest index even in BATCH mode (see org.h2.jdbc.JdbcStatement#getGeneratedKeys), - while the MySQL JDBC driver will behave as expected and return all the IDs. -

-
-
- -
\ No newline at end of file diff --git a/src/site/zh/xdoc/boot.xml b/src/site/zh/xdoc/boot.xml deleted file mode 100644 index 76432ac4d4..0000000000 --- a/src/site/zh/xdoc/boot.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - MyBatis-Spring | Spring Boot - Eduardo Macarron - - - -
-

- Please see the MyBatis Spring-boot-stater - subproject docs for details. -

-
- -
diff --git a/src/site/zh/xdoc/factorybean.xml b/src/site/zh/xdoc/factorybean.xml deleted file mode 100644 index 0e745ea46e..0000000000 --- a/src/site/zh/xdoc/factorybean.xml +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - MyBatis-Spring | 第三章 使用SqlSessionFactoryBean - Hunter Presnall - Eduardo Macarron - Nan Lei - - - -
-

-在基础的MyBatis中,通过SqlSessionFactoryBuilder来创建SqlSessionFactory。而在 -MyBatis-Spring中,则使用SqlSessionFactoryBean来创建。 -

- - -

-要创建工厂bean,将下面的代码放到Spring的XML配置文件中: -

- - -]]> - -

-需要注意的是SqlSessionFactoryBean实现了Spring的FactoryBean接口(可查看官方文档通过工厂bean自定义实例化逻辑)。这意味着由Spring最终创建的bean并不是SqlSessionFactoryBean本身, -而是工厂类(SqlSessionFactoryBean)的 getObject()方法的返回结果。这种情况下,Spring -将会在应用启动时为你创建SqlSessionFactory的bean对象,然后将其名字设为sqlSessionFactory。 -在 Java 中的等效代码如下: -

- - - -

-在通常的MyBatis-Spring 用法中,你不需要直接使用SqlSessionFactoryBean或对 -应的SqlSessionFactory。相反,session的工厂bean将会被注入到MapperFactoryBean或其它扩 -展了SqlSessionDaoSupport的DAO(Data Access Object,数据访问对象,译者注)中。 -

-
- - - -

-SqlSessionFactory有一个唯一的必要属性:用于JDBC的DataSource。这可以是任意 -的DataSource对象,它的配置应该和其它Spring数据库连接是一样的。 -

- -

-一个通用的属性是configLocation,它用来指定MyBatis的XML配置文件路径。 -如果需要修改MyBatis的基础配置,那么它就有用了。通常这会是<settings> -或<typeAliases>的部分。 -

- -

-需要注意的是,这个配置文件并不需要是一个完整的MyBatis配置。确切地说,任何环境,数据源 -和MyBatis的事务管理器都会被忽略。SqlSessionFactoryBean会创建它自己的,使用这些 -值定制MyBatis的Environment时是需要的。 -

- -

-如果MyBatis在映射器类对应的路径下不存在与之相应的映射器XML文件,那么此时也需要配置文件。 -有两种方式来使用配置:第一种是是手动在MyBatis的XML配置文件中使用<mappers>部分来 -指定类路径;第二种是使用工厂bean的mapperLocations属性。 -

- -

-mapperLocations属性使用list来表示资源位置。这个属性可以用来指定MyBatis的XML -映射器文件的位置。它的值可以包含Ant样式来加载一个目录中所有文件,或者从基路径下 -递归搜索所有路径。比如: -

- - - - -]]> - -

-这会从类路径下加载在sample.config.mappers包和它的子包中所有的MyBatis映射器XML文件。 -

- -

-在容器环境管理事务中,一个可能需要的属性是transactionFactoryClass。请参考第四章(4.2 节)中来查看有关部分。 -如果你正在使用多数据库功能,那么就需要设置databaseIdProvider属性。 -

- -

- 如果你使用了多数据库,那么需要设置databaseIdProvider属性: -

- - - - - sqlserver - db2 - oracle - mysql - - - - - - - - -]]> - -

- NOTE - 自1.3.0版本开始添加configuration属性。 - 它能够在没有MyBatis对应的XML配置文件的情况下直接指定Configuration实例。 - 举个栗子: -

- - - - - - - - -]]> - -
-
- -
\ No newline at end of file diff --git a/src/site/zh/xdoc/getting-started.xml.vm b/src/site/zh/xdoc/getting-started.xml.vm deleted file mode 100644 index 36e608d864..0000000000 --- a/src/site/zh/xdoc/getting-started.xml.vm +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - MyBatis-Spring | 第二章 入门 - Hunter Presnall - Eduardo Macarron - Nan Lei - - - -
-

-本章将会以简略的步骤告诉你如何安装和创建 MyBatis-Spring,并构建一个简单的数据访问事务性的应用程序。 -

- - -

-要使用MyBatis-Spring模块,只需要在类路径下包含 - mybatis-spring-${project.version}.jar -文件和相关依赖。 -

-

-如果使用Maven,仅需要在pom.xml中加入以下代码即可: -

- - org.mybatis - mybatis-spring - ${project.version} -]]> -
- - -

-要和Spring一起使用MyBatis,需要在Spring应用上下文中定义至少两样东西:一个SqlSessionFactory和至少一个数据映射器类。 -

- -

-在MyBatis-Spring中,可使用SqlSessionFactoryBean来创建SqlSessionFactory。要配置这个工厂bean,只需要把下面代码放在Spring的XML配置文件中: -

- - - -]]> - - - -

-需要注意的是:SqlSessionFactory需要一个DataSource(数据源,译者注)。这可以是任意的DataSource,配置它就和配置其它Spring数据库连接一样。 -

- -

-假设你定义了一个如下的mapper接口: -

- - -

-那么可以通过MapperFactoryBean像下面那样把接口加入到Spring中: -

- - - - -]]> - -

-需要注意的是:所指定的映射器类必须是一个接口,而不是具体的实现类。在这个示例中通过注解来指定SQL语句,但是通过MyBatis映射器的XML文件也行。 -

- -

-一旦完成配置,你可以像其它Spring中的bean注入方法将映射器到业务(bussiness)或服务(service)对象中。MapperFactoryBean用来处理SqlSession的创建和关闭动作。 -如果使用了Spring的事务,那么当事务完成时,session将会被提交或回滚。最终任何异常都会被翻译成Spring的DataAccessException异常。 -

- -

- 如果你使用Java代码来配置: -

- - -

-调用MyBatis数据方法现在只需一行代码: -

- -
-
- -
diff --git a/src/site/zh/xdoc/index.xml b/src/site/zh/xdoc/index.xml deleted file mode 100644 index 35ccf779a7..0000000000 --- a/src/site/zh/xdoc/index.xml +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - MyBatis-Spring | 第一章 简介 - Hunter Presnall - Eduardo Macarron - Nan Lei - - - -
- -

- MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。 -使用这个类库中的类, -Spring 将会加载必要的 MyBatis 工厂类和 session 类。 -这个类库也提供一个简单的方式来注入 -MyBatis 数据映射器和 SqlSession 到业务层的 bean 中。 -而且它也会处理事务, -翻译 MyBatis -的异常到 Spring 的 DataAccessException 异常(数据访问异常,译者注)中。最终,它并 -不会依赖于 MyBatis,Spring 或 MyBatis-Spring 来构建应用程序代码。 -

-
- - -

- 正如第二版那样,Spring 3.0 也仅支持 iBatis2。那么,我们就想将 MyBatis3 的支持添加 -到 Spring3.0(参考 Spring Jira 中的问题)中。而不幸的是,Spring 3.0 的开发在 MyBatis 3.0 -官方发布前就结束了。 -因为 Spring 开发团队不想发布一个基于非发布版的 MyBatis 的整合支 -持,那么 Spring 官方的支持就不得不继续等待了。要在 Spring 中支持 MyBatis,MyBatis 社 -区认为现在应该是自己团结贡献者和有兴趣的人一起来开始将 Spring 的整合作为 MyBatis 社 -区的子项目的时候了。 -

-
- - -

-在开始使用 MyBatis-Spring 的整合之前,很重要的一点是,你要熟悉 Spring 和 MyBatis -这两个框架还有和它们有关的术语,本手册中不会提供二者的基本内容,安装和配置教程。 -

-

- MyBatis-Spring requires following versions: -

- - - - - - - - - - - - - - - - - - - - - - - - - - -
- MyBatis-Spring - - MyBatis - - Spring Framework - - Spring Batch - - Java -
- 2.0 - - 3.4+ - - 5.0+ - - 4.0+ - - Java 8+ -
- 1.3 - - 3.4+ - - 3.2.2+ - - 2.1+ - - Java 6+ -
-
- - -

-特别感谢那些使得本项目成为现实的人们(按字母顺序排序)。 -Eduardo Macarron, Hunter Presnall和Putthiphong Boonphong的编码, -测试和文档修改工作; -Andrius Juozapaitis, -Giovanni Cuccu,Raj Nagappan和Tomas Pinos的贡献;而Simone Tripodi发现了这些人并 -把他们带入项目之中。没有他们的努力,这个项目是不可能存在的。 -

-
- - -

- 如果你以任何方式发现文档的缺失,或者文档缺少某一个功能点,最好的方式是你学习它 - 并自己写文档! -

-

- 手册的xdoc方式的来源可用地址: - project's Git - Fork仓库,更新它并提交合并的请求吧。 -

-

- 你是该文档最好的作者,像你一样大家都要阅读它! -

-
- -

可以在以下的翻译版本中阅读 MyBatis-Spring 文档:

- -

想用本土语言阅读这篇文档吗?那就用你的母语文档来丰富它吧!

-
- -
- - -
diff --git a/src/site/zh/xdoc/mappers.xml b/src/site/zh/xdoc/mappers.xml deleted file mode 100644 index 5ecc5261cb..0000000000 --- a/src/site/zh/xdoc/mappers.xml +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - MyBatis-Spring | 第六章 注入映射器 - Hunter Presnall - Eduardo Macarron - Nan Lei - - - -
-

- Rather than code data access objects (DAOs) manually using - SqlSessionDaoSupport or - SqlSessionTemplate, Mybatis-Spring can create a - thread safe mapper that you can inject directly into other beans: -

- - - -]]> - -

- Once injected, the mapper is ready to be used in application logic: -

- -

- Notice that there are no SqlSession or MyBatis - references in this code. Nor is there any need to create, open or close - the session, MyBatis-Spring will take care of that. -

- - -

- The way you register a mapper depends on whether you are using a classic XML configuration - or the new 3.0+ Java Config (a.k.a. @Configuration).

- -

With XML Config

- -

- A mapper is registered to Spring by including a MapperFactoryBean in your XML - config file like follows: -

- - - -]]> - -

- If the UserMapper has a corresponding MyBatis XML mapper file - in the same classpath location as the mapper interface, it will be - parsed automatically by the MapperFactoryBean. There - is no need to specify the mapper in a MyBatis configuration file unless - the mapper XML files are in a different classpath location. See the - SqlSessionFactoryBean's - configLocation - property for more information. -

- -

- Note that MapperFactoryBean requires either an - SqlSessionFactory or an SqlSessionTemplate. - These can be set through the respective sqlSessionFactory and - sqlSessionTemplate properties. - If both properties are set, the SqlSessionFactory is ignored. - Since the SqlSessionTemplate is required to have a session - factory set, that factory will be used by MapperFactoryBean. -

- -

With Java Config

- -

- When using Spring Java Config you can get a mapper directly out - of an SqlSessionTemplate like follows: -

- - - -

- Note that you can not return a mapper got from the MyBatis default SqlSession because - it would not be thread safe and will only live until the SqlSession - it was created from is closed. You must use an - SqlSessionTemplate instead, - as shown in the sample. -

-
- - -

- There is no need to register all your mappers one by one. - Instead, you can let MyBatis-Spring scan your classpath for them. -

- -

- There are three different ways to do it: -

-
    -
  • Using the <mybatis:scan/> element.
  • -
  • Using the annotation @MapperScan
  • -
  • Using a classic Spring xml file and registering the MapperScannerConfigurer
  • -
- -

Both <mybatis:scan/> and @MapperScan are features introduced in MyBatis-Spring 1.2.0. - @MapperScan requires Spring 3.1+.

- -

<mybatis:scan/>

- -

- The <mybatis:scan/> XML element will search for mappers - in a very similar way than the Spring built-in element <context:component-scan/> - searches for beans. -

- -

Follows below a sample XML configuration:

- - - - - - - -]]> - -

- The base-package attribute lets you set the base package - for your mapper interface files. You can set more than one package by - using a semicolon or comma as a separator. Mappers will be searched for - recursively starting in the specified package(s). -

- -

- Notice that there is no need to specify a SqlSessionFactory or - SqlSessionTemplate as an attribute in the <mybatis:scan/> - element because it will create MapperFactoryBeans that can be autowired. - But if you are using more than one DataSource autowire may not work for you. - In this case you can use the factory-ref or - template-ref attributes to set the right bean name to use. -

- -

- <mybatis:scan/> supports filtering the mappers - created by either specifying a marker interface or an annotation. The - annotation property specifies an annotation to - search for. The marker-interface attribute specifies a - parent interface to search for. If both properties are specified, mappers - are added for interfaces that match either criteria. - By default, these two properties are null, so all interfaces in the given - base package(s) will be loaded as mappers. -

- -

- Discovered mappers will be named using Spring default naming strategy for - autodetected components (see the Spring reference document(Core Technologies -Naming autodetected components-)). - That is, if no annotation is found, it will use the uncapitalized non-qualified class - name of the mapper. But if either a @Component or a JSR-330 @Named annotation is - found it will get the name from the annotation. - Notice that you can set the annotation attribute - to org.springframework.stereotype.Component, - javax.inject.Named (if you have JSE 6) or to your own annotation - (that must be itself annotated) so the annotation will work both as a marker - and as a name provider. -

- -

- NOTE <context:component-scan/> - won't be able to scan and register mappers. Mappers are interfaces and, in order to register them to - Spring, the scanner must know how to create a MapperFactoryBean for each interface - it finds. -

- -

@MapperScan

- -

- If you are using the Spring Java Configuration (a.k.a @Configuration) you would - prefer to use the @MapperScan rather than the - <mybatis:scan/>. -

- -

The @MapperScan annotation is used as follows:

- - - -

The annotation works in the same exact way than <mybatis:scan/> we - saw in the previous section. It also lets you specify a marker interface or an annotation class - through its properties markerInterface and annotationClass. - You can also provide an specific SqlSessionFactory or SqlSessionTemplate - by using its properties sqlSessionFactory and sqlSessionTemplate. -

- -

MapperScannerConfigurer

- -

- The MapperScannerConfigurer is a BeanDefinitionRegistryPostProcessor that - can be included in a classic xml application context as a normal bean. - To set up a MapperScannerConfigurer add the following to the Spring configuration: -

- - -]]> - -

- If you need to specify an specific sqlSessionFactory or sqlSessionTemplate - note that bean names are required, - not bean references, thus the value attribute is used instead of the - usual ref: -

- ]]> - -

- NOTE sqlSessionFactoryBean and - sqlSessionTemplateBean properties were the only option available up to MyBatis-Spring 1.0.2 - but given that the MapperScannerConfigurer runs earlier in the startup - process that PropertyPlaceholderConfigurer there were frequent errors. - For that purpose that properties have been deprecated and the new properties - sqlSessionFactoryBeanName and sqlSessionTemplateBeanName - are recommended. -

-
-
- -
diff --git a/src/site/zh/xdoc/sample.xml b/src/site/zh/xdoc/sample.xml deleted file mode 100644 index 14489219ea..0000000000 --- a/src/site/zh/xdoc/sample.xml +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - MyBatis-Spring | Sample Code - Eduardo Macarron - - - -
- -

- NOTE - 查看 JPetstore 6 demo 获取更多关于 Spring 在完整的应用服务上面的使用例子. -

- -

- 您可以从此处查看 MyBatis-Spring 的示例代码 repo: -

-

- 任何一个示例都在 JUnit 5 中运行. -

-

- 示例代码显示了事务服务从数据访问层获取域对象的典型设计. -

-

- FooService.java 作为服务: -

- -

- 它是一个事务 bean,所以当调用它的任何方法时,事务被启动并且在方法结束时提交,且不会抛出未经检查的异常.请注意,事务行为配置为 @Transactional 属性. 这不是必需的 Spring 提供的任何其他方式都可用于划分你的事务. -

-

- 此服务调用使用 MyBatis 构建的数据访问层. 这层只是一个界面 UserMapper.java 这将与 MyBatis 建立的动态代理一起使用运行时并通过 Spring 注入服务. -

- -

- 注意,为了简单起见,我们使用了接口 UserMapper.java 对于 DAO 场景其中一个 DAO 层是用接口和一个实现构建的,尽管在这种情况下它将会更多足够使用一个接口调用 UserDao.java 来取代. -

-

- 我们将看到不同的方法来找到映射器接口,将其注册到 Spring 并将其注入到服务 bean 中: -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
使用场景
测试用样例描述
- SampleMapperTest.java - - 显示基于 MapperFactoryBean 的基本配置,这将动态构建一个实现 UserMapper -
- SampleScannerTest.java - - 显示如何使用 MapperScannerConfigurer 来实现项目中所有 mappers 的自动发现. -
- SampleSqlSessionTest.java - - 显示如何使用 Spring SqlSession 手动编写 DAO 层并提供你自己的实现 UserDaoImpl.java. -
- SampleEnableTest - - 现实如何使用 Spring 的 @Configuration@MapperScann 注解来自动发现 mappers. -
- SampleNamespaceTest - - 显示如何使用自定义 MyBatis XML 命名空间. -
- SampleJavaConfigTest.java - - Shows how to use Spring's @Configuration to create MyBatis beans manually. -
- SampleJobJavaConfigTest.java - - Shows how to use ItemReader and ItemWriter on Spring Batch using Java Configuration. -
- SampleJobXmlConfigTest.java - - Shows how to use ItemReader and ItemWriter on Spring Batch using XML Configuration. -
-

- 查看 applicationContext.xml 文件的区别以进行 MyBatis-Spring 实战. -

- -
- -
- diff --git a/src/site/zh/xdoc/sqlsession.xml b/src/site/zh/xdoc/sqlsession.xml deleted file mode 100644 index 54fc787bbc..0000000000 --- a/src/site/zh/xdoc/sqlsession.xml +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - MyBatis-Spring | 第五章 使用 SqlSession - Hunter Presnall - Eduardo Macarron - Nan Lei - - - -
-

-在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 -session 之后,你可以使用它来执行映射语句,提交或回滚连接,最后,当不再需要它的时 -候, -你可以关闭 session。 -使用 MyBatis-Spring 之后, -你不再需要直接使用 SqlSessionFactory -了,因为你的 bean 可以通过一个线程安全的 SqlSession 来注入,基于 Spring 的事务配置 -来自动提交,回滚,关闭 session。 -

- -

-注意通常不必直接使用 SqlSession。 -在大多数情况下 MapperFactoryBean, -将会在 bean -中注入所需要的映射器。下一章节中的 MapperFactoryBean(6.1 节)会解释这个细节。 -

- - -

-SqlSessionTemplate 是 MyBatis-Spring 的核心。 -这个类负责管理 MyBatis 的 SqlSession, -调用 MyBatis 的 SQL 方法, -翻译异常。 -SqlSessionTemplate 是线程安全的, -可以被多个 DAO -所共享使用。 -

- -

-当调用 SQL 方法时, -包含从映射器 getMapper()方法返回的方法, -SqlSessionTemplate -将会保证使用的 SqlSession 是和当前 Spring 的事务相关的。此外,它管理 session 的生命 -周期,包含必要的关闭,提交或回滚操作。 -

- -

-SqlSessionTemplate 实现了 SqlSession 接口,这就是说,在代码中无需对 MyBatis 的 -SqlSession 进行替换。 SqlSessionTemplate 通常是被用来替代默认的 MyBatis 实现的 -DefaultSqlSession , -因为模板可以参与到 Spring 的事务中并且被多个注入的映射器类所使 -用时也是线程安全的。相同应用程序中两个类之间的转换可能会引起数据一致性的问题。 -

- -

-SqlSessionTemplate 对象可以使用 SqlSessionFactory 作为构造方法的参数来创建。 -

- - -]]> - - - -

-这个 bean 现在可以直接注入到 DAO bean 中。你需要在 bean 中添加一个 SqlSession -属性,就像下面的代码: -

- -

-如下注入 SqlSessionTemplate: -

- - -]]> - -

-SqlSessionTemplate 有一个使用 ExecutorType 作为参数的构造方法。这允许你用来 -创建对象,比如,一个批量 SqlSession,但是使用了下列 Spring 配置的 文件: -

- - - -]]> - - - -

-现在你所有的语句可以批量操作了,下面的语句就可以在 DAO 中使用了。 -

- users) { - for (User user : users) { - sqlSession.insert("org.mybatis.spring.sample.mapper.UserMapper.insertUser", user); - } -}]]> - -

-注意,如果所需的执行方法和默认的 SqlSessionFactory 设置不同,这种配置风格才 -能使用。 -

- -

-对这种形式需要说明的是当这个方法被调用时,不能有一个存在使用不同 ExecutorType -运行的事务。也要保证在不同的事务中,使用不同执行器来调用 SqlSessionTemplate 时, -(比如 PROPAGATION_REQUIRES_NEW)或完全在一个事务外面。 -

-
- - -

-SqlSessionDaoSupport 是 一 个 抽象 的支 持 类, 用来 为你 提供 SqlSession 。 调 用 -getSqlSession()方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法, -就像下面这样: -

- -

-通常 MapperFactoryBean 是这个类的首选,因为它不需要额外的代码。但是,如果你 -需要在 DAO 中做其它非 MyBatis 的工作或需要具体的类,那么这个类就很有用了。 -

- -

-SqlSessionDaoSupport 需要一个 sqlSessionFactory 或 sqlSessionTemplate 属性来 -设 置 。 这 些 被 明 确 地 设 置 或 由 Spring 来 自 动 装 配 。 如 果 两 者 都 被 设 置 了 , 那 么 -SqlSessionFactory 是被忽略的。 -

- -

-假设类 UserMapperImpl 是 SqlSessionDaoSupport 的子类,它可以在 Spring 中进行如 -下的配置: -

- - -]]> -
-
- -
diff --git a/src/site/zh/xdoc/transactions.xml b/src/site/zh/xdoc/transactions.xml deleted file mode 100644 index f709fffae9..0000000000 --- a/src/site/zh/xdoc/transactions.xml +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - MyBatis-Spring | 第四章 事务 - Hunter Presnall - Eduardo Macarron - Nan Lei - - - -
-

-一个使用 MyBatis-Spring 的主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而 -不是给 MyBatis 创建一个新的特定的事务管理器,MyBatis-Spring 利用了存在于 Spring 中的 -DataSourceTransactionManager。 -

-

-一旦 Spring 的 PlatformTransactionManager 配置好了,你可以在 Spring 中以你通常的做 -法来配置事务。@Transactional 注解和 AOP(Aspect-Oriented Program,面向切面编程,译 -者注)样式的配置都是支持的。在事务处理期间,一个单独的 SqlSession 对象将会被创建 -和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。 -

-

-一旦事务创建之后,MyBatis-Spring 将会透明的管理事务。在你的 DAO 类中就不需要额 -外的代码了。 -

- - -

-要 开 启 Spring 的 事 务 处 理 , 在 Spring 的 配 置 文 件 中 简 单 创 建 一 个 -DataSourceTransactionManager 对象: -

- - -]]> - - - -

-指定的 DataSource 一般可以是你使用 Spring 的任意 JDBC DataSource。这包含了连接 -池和通过 JNDI 查找获得的 DataSource。 -

-

-要注意, -为事务管理器指定的 DataSource 必须和用来创建 SqlSessionFactoryBean 的 -是同一个数据源,否则事务管理器就无法工作了。 -

-
- - -

-如果你正使用一个 JEE 容器而且想让 Spring 参与到容器管理事务(Container managed -transactions,CMT,译者注)中,那么 Spring 应该使用 JtaTransactionManager 或它的容 -器指定的子类来配置。做这件事情的最方便的方式是用 Spring 的事务命名空间 or the JtaTransactionManagerFactoryBean: -

- ]]> - - - -

-在这种配置中,MyBatis 将会和其它由 CMT 配置的 Spring 事务资源一样。Spring 会自动 -使用任意存在的容器事务,在上面附加一个 SqlSession。如果没有开始事务,或者需要基 -于事务配置,Spring 会开启一个新的容器管理事务。 -

-

-注 意 , 如 果 你 想 使 用 CMT , 而 不 想 使 用 Spring 的 事 务 管 理 , 你 就 必 须 配 置 -SqlSessionFactoryBean 来使用基本的 MyBatis 的 ManagedTransactionFactory 而不是其 -它任意的 Spring 事务管理器: -

- - - - - -]]> - - - -
- - -

-MyBatis 的 SqlSession 提供指定的方法来处理编程式的事务。 -但是当使用 MyBatis-Spring -时, -bean 将会使用 Spring 管理的 SqlSession 或映射器来注入。 -那就是说 Spring 通常是处理 -事务的。 -

-

-你 不 能 在 Spring 管 理 的 SqlSession 上 调 用 SqlSession.commit() , -SqlSession.rollback() 或 SqlSession.close() 方 法 。 如 果 这 样 做 了 , 就 会 抛 出 -UnsupportedOperationException 异常。注意在使用注入的映射器时不能访问那些方法。 -

-

-无论 JDBC 连接是否设置为自动提交, -SqlSession 数据方法的执行或在 Spring 事务之外 -任意调用映射器方法都将会自动被提交。 -

-

-如果你想编程式地控制事务,请参考 the Spring reference document(Data Access -Programmatic transaction management-)。这段代码展示了如何手动 -使用在 节描述的 PlatformTransactionManager 来处理事务。 -

- - -

- You can omit to call the commit and rollback method using the TransactionTemplate. -

- - { - userMapper.insertUser(user); - return null; -});]]> - -

-注意这段代码展示了一个映射器,但它也能和 SqlSession 一起使用。 -

-
-
- -
diff --git a/src/site/zh/xdoc/using-api.xml b/src/site/zh/xdoc/using-api.xml deleted file mode 100644 index bf74c81ea5..0000000000 --- a/src/site/zh/xdoc/using-api.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - MyBatis-Spring | 第七章 使用 MyBatis API - Hunter Presnall - Eduardo Macarron - Nan Lei - - - -
-

-使用 MyBatis-Spring,你可以继续直接使用 MyBatis 的 API。仅仅在代码中使用 Spring 中 -的 SqlSessionFactoryBean 来创建一个 SqlSessionFactory。 -

- - -

-小心使用此选项, -因为错误的使用会产生运行时错误, -或者更糟糕的数据一致性的问题。 -这些是告诫: -

-
    -
  • -

    -它不会参与到 Spring 的事务之中。 -

    -
  • -
  • -

    -如果 SqlSession 使用 DataSource,它也会被 Spring 事务管理器使用,而且当前 -有事务在进行时,这段代码会抛出异常。 -

    -
  • -
  • -

    -MyBatis 的 DefaultSqlSession 是线程不安全的。如果在 bean 中注入了它,就会 -发生错误。 -

    -
  • -
  • -

    -使用 DefaultSqlSession 创建的映射器也不是线程安全的。如果你将它们注入到 -bean 中,是会发生错误的。 -

    -
  • -
  • -

    -你必须保证在 finally 块中来关闭 SqlSession。 -

    -
  • -
-
- -
diff --git a/src/site/zh_CN/markdown/README.md b/src/site/zh_CN/markdown/README.md new file mode 100644 index 0000000000..823195bba3 --- /dev/null +++ b/src/site/zh_CN/markdown/README.md @@ -0,0 +1,18 @@ +# 目录 + +此页面用于在GitHub上呈现索引。 + +> **NOTE:** +> +> 由于链接目标是在使用maven-site-plugin转换为html的假设下指定的,因此在GitHub上的呈现中有一个锚点已损坏。 + +* [简介](./index.md) +* [入门](./getting-started.md) +* [SqlSessionFactoryBean](./factorybean.md) +* [事务](./transactions.md) +* [使用 SqlSession](./sqlsession.md) +* [注入映射器](./mappers.md) +* [Spring Boot](./boot.md) +* [使用 MyBatis API](./using-api.md) +* [Spring Batch](./batch.md) +* [示例代码](./sample.md) diff --git a/src/site/zh_CN/markdown/batch.md b/src/site/zh_CN/markdown/batch.md new file mode 100644 index 0000000000..5d891fe154 --- /dev/null +++ b/src/site/zh_CN/markdown/batch.md @@ -0,0 +1,343 @@ + +# 使用 Spring Batch + +MyBatis-Spring 1.1.0 发布以后,提供了三个 bean 以供构建 Spring Batch 应用程序:`MyBatisPagingItemReader`、`MyBatisCursorItemReader` 和 `MyBatisBatchItemWriter`。 +而在 2.0.0 中,还提供了三个建造器(builder)类来对 Java 配置提供支持:`MyBatisPagingItemReaderBuilder`、`MyBatisCursorItemReaderBuilder` 和 `MyBatisBatchItemWriterBuilder`。 + +提示 +本章是关于 [Spring Batch](http://static.springsource.org/spring-batch/) 的,而不是关于 MyBatis 的批量 `SqlSession`。要查找关于批量 session 的更多信息,请参考 [使用 SqlSession](sqlsession.html) 一章。 + +## MyBatisPagingItemReader + +这个 bean 是一个 `ItemReader`,能够从数据库中分页地读取记录。 + +它执行 `setQueryId` 属性指定的查询来获取请求的数据。这个查询使用 `setPageSize` 属性指定了分页请求的大小,并被执行。其它的页面将在必要时被请求(例如调用 `read()` 方法时),返回对应位置上的对象。 + +reader 还提供了一些标准的请求参数。在命名查询的 SQL 中,必须使用部分或全部的参数(视乎 SQL 方言而定)来构造指定大小的结果集。这些参数是: + +* `_page`: 欲读取的页码(从 0 开始) +* `_pagesize`: 每一页的大小,也就是返回的行数 +* `_skiprows`: `_page` 和 `_pagesize` 的乘积 + +它们可以被映射成如下的 select 语句: + +```xml + +``` + +配合如下的配置样例: + +```xml + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisPagingItemReader reader() { + return new MyBatisPagingItemReaderBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") + .build(); + } +} +``` + +**让我们通过一个更复杂一点的例子来阐明一切:** + +```xml + +``` +```xml + + + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @StepScope + @Bean + public MyBatisPagingItemReader dateBasedCriteriaReader( + @Value("#{@datesParameters}") Map datesParameters) throws Exception { + return new MyBatisPagingItemReaderBuilder() + .sqlSessionFactory(batchReadingSessionFactory()) + .queryId("com.my.name.space.batch.ExampleMapper.queryUserInteractionsOnSpecificTimeSlot") + .parameterValues(datesParameters) + .pageSize(200) + .build(); + } + + @StepScope + @Bean + public Map datesParameters( + @Value("#{jobExecutionContext['EXTRACTION_START_DATE']}") LocalDate yesterday, + @Value("#{jobExecutionContext['TODAY_DATE']}") LocalDate today, + @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_MONTH_DATE']}") LocalDate firstDayOfTheMonth, + @Value("#{jobExecutionContext['FIRST_DAY_OF_THE_PREVIOUS_MONTH_DATE']}") LocalDate firstDayOfThePreviousMonth) { + Map map = new HashMap<>(); + map.put("yesterday", yesterday); + map.put("today", today); + map.put("first_day_of_the_month", firstDayOfTheMonth); + map.put("first_day_of_the_previous_month", firstDayOfThePreviousMonth); + return map; + } +} +``` + +上面的样例使用了几个东西: + +* `sqlSessionFactory:可以为 reader 指定你自定义的 sessionFactory,当你想从多个数据库中读取数据时尤其有用 +* `queryId`:指定在检索记录时要执行的查询的 ID,可以指定短的 ID 或是带命名空间的完整 ID。一般来说,你的应用可能从多个表或数据库中读取数据,因此会配置多个查询,可能会使用到在不同命名空间中的不同映射器。因此最好提供映射器文件的命名空间以便准确指定你想使用的查询 ID。 +* `parameterValues`:可以通过这个 map 传递多个附加的参数,上面的例子中就使用了一个由 Spring 构建的 map,并使用 SpEL 表达式从 `jobExecutionContext` 中获取信息。 + 而 map 的键将在映射器文件中被 MyBatis 使用(例如:*yesterday* 可以通过 `#{yesterday,jdbcType=TIMESTAMP}` 来读取)。注意,map 和 reader 都构建于 `step` 作用域,这样才能够在 Spring 表达式语言中使用 `jobExecutionContext`。 + 另外,如果正确配置了 MyBatis 的类型处理器,你可以将自定义的实例像参数那样传递到 map 中,比如将参数类型换成 JodaTime。 +* `pageSize`:如果批处理流配置了块大小(chunk size),需要通过此属性将块大小告知 reader。 + +## MyBatisCursorItemReader + +这个 bean 是一个 `ItemReader` ,能够通过游标从数据库中读取记录。 + +提示 +为了使用这个 bean,你需要使用 MyBatis 3.4.0 或更新的版本。 + +它执行 `setQueryId` 属性指定的查询来获取请求的数据(通过 `selectCursor()`方法)。每次调用 `read()` 方法时,将会返回游标指向的下个元素,直到没有剩余的元素了。 + +这个 reader 将会使用一个单独的数据库连接,因此 select 语句将不会参与到 step 处理中创建的任何事务。 + +当使用游标时,只需要执行普通的查询: + +```xml + +``` + +搭配以下的配置样例: + +```xml + + + + +``` +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisCursorItemReader reader() { + return new MyBatisCursorItemReaderBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .queryId("com.my.name.space.batch.EmployeeMapper.getEmployee") + .build(); + } +} +``` + +## MyBatisBatchItemWriter + +这是一个 `ItemWriter`,通过利用 `SqlSessionTemplate` 中的批量处理功能来对给定的所有记录执行多个语句。`SqlSessionFactory` 需要被配置为 `BATCH` 执行类型。 + +当调用 `write()` 时,将会执行 `statementId` 属性中指定的映射语句。一般情况下,`write()` 应该在一个事务中进行调用。 + +下面是一个配置样例: + +```xml + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisBatchItemWriter writer() { + return new MyBatisBatchItemWriterBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .statementId("com.my.name.space.batch.EmployeeMapper.updateEmployee") + .build(); + } +} +``` + +**将 ItemReader 读取的记录转换为任意的参数对象:** + +默认情况下,`MyBatisBatchItemWriter` 会将 `ItemReader` 读取的对象(或 `ItemProcessor` 转换过的对象) 以参数对象的形式传递给 MyBatis(`SqlSession#update()`)。 +如果你想自定义传递给 MyBatis 的参数对象,可以使用 `itemToParameterConverter` 选项。使用该选项后,可以传递任意对象给 MyBatis。 +举个例子: + +首先,创建一个自定义的转换器类(或工厂方法)。以下例子使用了工厂方法。 + +```java +public class ItemToParameterMapConverters { + public static Converter> createItemToParameterMapConverter(String operationBy, LocalDateTime operationAt) { + return item -> { + Map parameter = new HashMap<>(); + parameter.put("item", item); + parameter.put("operationBy", operationBy); + parameter.put("operationAt", operationAt); + return parameter; + }; + } +} +``` + +接下来,编写 SQL 映射。 + +```xml + +``` + +最后,配置 `MyBatisBatchItemWriter`。 + +```java +@Configuration +public class BatchAppConfig { + @Bean + public MyBatisBatchItemWriter writer() throws Exception { + return new MyBatisBatchItemWriterBuilder() + .sqlSessionFactory(sqlSessionFactory()) + .statementId("org.mybatis.spring.sample.mapper.PersonMapper.createPerson") + .itemToParameterConverter(createItemToParameterMapConverter("batch_java_config_user", LocalDateTime.now())) + .build(); + } +} +``` + +```xml + + + + + + + + + + +``` + +**使用复合 writer 对多个表进行写入(但带有问题):** + +这个小技巧只能在 MyBatis 3.2+ 以上的版本中使用,因为之前的版本中含有导致 writer 行为不正确的 [问题](http://code.google.com/p/mybatis/issues/detail?id=741)。 + +如果批量处理时需要写入复杂的数据,例如含有关联的记录,甚至是向多个数据库写入数据,你可能就需要一种办法来绕开 insert 语句只能插入到一个表中的限制。为了绕过此限制,批处理必须准备好要通过 writer 写入的*项*。 +然而,基于对被处理的数据的观察,可以尝试使用下面的方法来解决此问题。下面的方法能够工作于具有简单关联或不相关的多个表的项。 + +在这种方法中,处理 Spring Batch 项的处理器中将会*持有*各种不同的记录。假设每个项都有一个与 *InteractionMetadata* 相关联的 *Interaction*,并且还有两个不相关的行 *VisitorInteraction* 和 *CustomerInteraction*,这时候持有器(holder)看起来像这样: + +```java +public class InteractionRecordToWriteInMultipleTables { + private final VisitorInteraction visitorInteraction; + private final CustomerInteraction customerInteraction; + private final Interaction interaction; + // ... +} +``` +```java +public class Interaction { + private final InteractionMetadata interactionMetadata; +} +``` + +在 Spring 配置中要配置一个 `CompositeItemWriter`,它将会将写入操作委托到特定种类的 writer 上面去。注意 *InteractionMetadata* 在例子里面是一个关联,它需要首先被写入,这样 Interaction 才能获得更新之后的键。 + +```xml + + + + + + + + + + + + +``` + +```java +@Configuration +public class BatchAppConfig { + @Bean + public CompositeItemWriter interactionsItemWriter() { + CompositeItemWriter compositeItemWriter = new CompositeItemWriter(); + List> writers = new ArrayList<>(4); + writers.add(visitorInteractionsWriter()); + writers.add(customerInteractionsWriter()); + writers.add(interactionMetadataWriter()); + writers.add(interactionWriter()); + compositeItemWriter.setDelegates(writers); + return compositeItemWriter; + } +} +``` + +接下来需要配置每一个被委托的 writer;例如 *Interaction* 和 *InteractionMetadata* 对应的 writer。 + +```xml + +``` +```xml + +``` + +和 reader 中的一样,通过 `statementId` 属性指定对应命名空间前缀的查询。 + +而在映射器配置文件中,应该根据每种特定的记录编写特定的语句,如下所示: + +```xml + + + +``` +```xml + + + +``` + +执行的时候会怎么样呢?首先,`insertInteractionMetadata` 将会被调用,update 语句被设置为返回由 JDBC 驱动返回的主键(参考 `keyProperty` 和 `keyColumn`)。 +由于 `InteractionMetadata` 的对象被此查询更新了,下一个查询将可以通过 `insertInteraction` 开展父对象 `Interaction` 的写入工作。 + +***然而要注意,JDBC 驱动在这方面的行为并不总是与此相一致。在编写文档时,H2 的数据库驱动 1.3.168 甚至只在 BATCH 模式下返回最后的索引值(参考 `org.h2.jdbc.JdbcStatement#getGeneratedKeys`), +而 MySQL 的 JDBC 驱动则工作良好并返回所有 ID。*** diff --git a/src/site/zh_CN/markdown/boot.md b/src/site/zh_CN/markdown/boot.md new file mode 100644 index 0000000000..187cd06688 --- /dev/null +++ b/src/site/zh_CN/markdown/boot.md @@ -0,0 +1,4 @@ + +# 搭配 Spring Boot + +请查看 [MyBatis Spring-boot-starter](http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure) 子项目获取更多信息。 diff --git a/src/site/zh_CN/markdown/factorybean.md b/src/site/zh_CN/markdown/factorybean.md new file mode 100644 index 0000000000..1db21fdf3a --- /dev/null +++ b/src/site/zh_CN/markdown/factorybean.md @@ -0,0 +1,175 @@ + +# SqlSessionFactoryBean + +在基础的 MyBatis 用法中,是通过 `SqlSessionFactoryBuilder` 来创建 `SqlSessionFactory` 的。而在 MyBatis-Spring 中,则使用 `SqlSessionFactoryBean` 来创建。 + +## 设置 + +要创建工厂 bean,将下面的代码放到 Spring 的 XML 配置文件中: + +```xml + + + +``` + +需要注意的是 `SqlSessionFactoryBean` 实现了 Spring 的 `FactoryBean` 接口(参见 Spring 官方文档 3.8 节 [通过工厂 bean 自定义实例化逻辑](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-extension-factorybean) )。 +这意味着由 Spring 最终创建的 bean **并不是** `SqlSessionFactoryBean` 本身,而是工厂类(`SqlSessionFactoryBean`)的 getObject() 方法的返回结果。这种情况下,Spring 将会在应用启动时为你创建 `SqlSessionFactory`,并使用 `sqlSessionFactory` 这个名字存储起来。 + +等效的 Java 代码如下: + +```java +@Configuration +public class MyBatisConfig { + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + return factoryBean.getObject(); + } +} +``` + +通常,在 MyBatis-Spring 中,你不需要直接使用 `SqlSessionFactoryBean` 或对应的 `SqlSessionFactory`。 +相反,session 的工厂 bean 将会被注入到 `MapperFactoryBean` 或其它继承于 `SqlSessionDaoSupport` 的 DAO(Data Access Object,数据访问对象)中。 + +## 属性 + +`SqlSessionFactory` 有一个唯一的必要属性:用于 JDBC 的 `DataSource`。这可以是任意的 `DataSource` 对象,它的配置方法和其它 Spring 数据库连接是一样的。 + +一个常用的属性是 `configLocation`,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 `` 或 `` 元素。 + +需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(``),数据源(``)和 MyBatis 的事务管理器(``)都会被**忽略**。 +`SqlSessionFactoryBean` 会创建它自有的 MyBatis 环境配置(`Environment`),并按要求设置自定义环境的值。 + +如果 MyBatis 在映射器类对应的路径下找不到与之相对应的映射器 XML 文件,那么也需要配置文件。这时有两种解决办法:第一种是手动在 MyBatis 的 XML 配置文件中的 `` 部分中指定 XML 文件的类路径;第二种是设置工厂 bean 的 `mapperLocations` 属性。 + +`mapperLocations` 属性接受多个资源位置。这个属性可以用来指定 MyBatis 的映射器 XML 配置文件的位置。属性的值是一个 Ant 风格的字符串,可以指定加载一个目录中的所有文件,或者从一个目录开始递归搜索所有目录。比如: + +```xml + + + + +``` + +In Java, the equivalent code would be: + +```java +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sample/config/mappers/**/*.xml")); + return factoryBean.getObject(); +} +``` + +这会从类路径下加载所有在 `sample.config.mappers` 包和它的子包中的 MyBatis 映射器 XML 配置文件。 + +在容器管理事务的时候,你可能需要的一个属性是 `transactionFactoryClass`。请参考事务一章的相关章节。 + +如果你使用了多个数据库,那么需要设置 `databaseIdProvider` 属性: + +```xml + + + + sqlserver + db2 + oracle + mysql + + + +``` +```xml + + + + + +``` + +In Java, the equivalent code would be: + +```java +@Bean +public VendorDatabaseIdProvider databaseIdProvider() { + VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); + Properties properties = new Properties(); + properties.setProperty("SQL Server", "sqlserver"); + properties.setProperty("DB2", "db2"); + properties.setProperty("Oracle", "oracle"); + properties.setProperty("MySQL", "mysql"); + databaseIdProvider.setProperties(properties); + return databaseIdProvider; +} + +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource, DatabaseIdProvider databaseIdProvider) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + factoryBean.setDatabaseIdProvider(databaseIdProvider); + return factoryBean.getObject(); +} +``` + +提示 +自 1.3.0 版本开始,新增的 `configuration` 属性能够在没有对应的 MyBatis XML 配置文件的情况下,直接设置 `Configuration` 实例。例如: + +```xml + + + + + + + + +``` + +In Java, the equivalent code would be: + +```java +@Bean +public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + + return factoryBean.getObject(); +} +``` + +## Java Configuration Example + +Here is a complete example of a configuration class that combines the properties described above. + +```java +@Configuration +public class MyBatisConfig { + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + + // Setting mapper locations + factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sample/config/mappers/**/*.xml")); + + // Setting configuration property + org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); + configuration.setMapUnderscoreToCamelCase(true); + factoryBean.setConfiguration(configuration); + + return factoryBean.getObject(); + } +} +``` + +NOTE +This configuration class must be located within a package scanned by the Spring container (e.g., within the main application package). The class name itself (e.g., `MyBatisConfig`) is arbitrary; only the `@Configuration` annotation is required. diff --git a/src/site/zh_CN/markdown/getting-started.md b/src/site/zh_CN/markdown/getting-started.md new file mode 100644 index 0000000000..69b7b84e40 --- /dev/null +++ b/src/site/zh_CN/markdown/getting-started.md @@ -0,0 +1,97 @@ + +# 入门 + +本章将会以简略的步骤告诉你如何安装和配置 MyBatis-Spring,并构建一个简单的具备事务管理功能的数据访问应用程序。 + +## 安装 + +要使用 MyBatis-Spring 模块,只需要在类路径下包含 `mybatis-spring-${project.version}.jar` 文件和相关依赖即可。 + +如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可: + +```xml + + org.mybatis + mybatis-spring + ${project.version} + +``` + +## 快速上手 + +要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 `SqlSessionFactory` 和至少一个数据映射器类。 + +在 MyBatis-Spring 中,可使用 `SqlSessionFactoryBean`来创建 `SqlSessionFactory`。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中: + +```xml + + + +``` + +```java +@Configuration +public class MyBatisConfig { + @Bean + public SqlSessionFactory sqlSessionFactory() throws Exception { + SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource()); + return factoryBean.getObject(); + } +} +``` + +注意:`SqlSessionFactory` 需要一个 `DataSource`(数据源)。这可以是任意的 `DataSource`,只需要和配置其它 Spring 数据库连接一样配置它就可以了。 + +假设你定义了一个如下的 mapper 接口: + +```java +public interface UserMapper { + @Select("SELECT * FROM users WHERE id = #{userId}") + User getUser(@Param("userId") String userId); +} +``` + +那么可以通过 `MapperFactoryBean` 将接口加入到 Spring 中: + +```xml + + + + +``` + +需要注意的是:所指定的映射器类必须是一个接口,而不是具体的实现类。在这个示例中,通过注解来指定 SQL 语句,但是也可以使用 MyBatis 映射器的 XML 配置文件。 + +配置好之后,你就可以像 Spring 中普通的 bean 注入方法那样,将映射器注入到你的业务或服务对象中。`MapperFactoryBean` 将会负责 `SqlSession` 的创建和关闭。 +如果使用了 Spring 的事务功能,那么当事务完成时,session 将会被提交或回滚。最终任何异常都会被转换成 Spring 的 `DataAccessException` 异常。 + +使用 Java 代码来配置的方式如下: + +```java +@Configuration +public class MyBatisConfig { + @Bean + public UserMapper userMapper() throws Exception { + SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory()); + return sqlSessionTemplate.getMapper(UserMapper.class); + } +} +``` + +要调用 MyBatis 的数据方法,只需一行代码: + +```java +public class FooServiceImpl implements FooService { + + private final UserMapper userMapper; + + public FooServiceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public User doSomeBusinessStuff(String userId) { + return this.userMapper.getUser(userId); + } +} +``` diff --git a/src/site/zh_CN/markdown/index.md b/src/site/zh_CN/markdown/index.md new file mode 100644 index 0000000000..c9a13fd0d7 --- /dev/null +++ b/src/site/zh_CN/markdown/index.md @@ -0,0 +1,54 @@ + +# 简介 + +## 什么是 MyBatis-Spring? + +MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 `SqlSession` 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 `DataAccessException`。 +最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。 + +## 动机 + +Spring 2.0 只支持 iBatis 2.0。那么,我们就想将 MyBatis3 的支持添加到 Spring 3.0 中(参见 Spring Jira 中的 [问题](https://jira.springsource.org/browse/SPR-5991) )。不幸的是,Spring 3.0 的开发在 MyBatis 3.0 官方发布前就结束了。 +由于 Spring 开发团队不想发布一个基于未发布版的 MyBatis 的整合支持,如果要获得 Spring 官方的支持,只能等待下一次的发布了。基于在 Spring 中对 MyBatis 提供支持的兴趣,MyBatis 社区认为,应该开始召集有兴趣参与其中的贡献者们,将对 Spring 的集成作为 MyBatis 的一个社区子项目。 + +## 知识基础 + +在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要——因为本手册中不会提供二者的基本内容,安装和配置教程。 + +MyBatis-Spring 需要以下版本: + +| MyBatis-Spring | MyBatis | Spring Framework | Spring Batch | Java | +|----------------| --- |------------------|--------------| --- | +| **4.0** | 3.5+ | 7.0+ | 6.0+ | Java 17+ | +| **3.0** | 3.5+ | 6.x | 5.x | Java 17+ | +| **2.1** | 3.5+ | 5.x | 4.x | Java 8+ | +| ~~**2.0**~~ | ~~3.5+~~ | ~~5.x~~ | ~~4.x~~ | ~~Java 8+~~ | +| ~~**1.3**~~ | ~~3.4+~~ | ~~3.2.2+~~ | ~~2.1+~~ | ~~Java 6+~~ | + +## 致谢 + +特别感谢那些使本项目变为现实的人们(按字母顺序排序): Eduardo Macarron, Hunter Presnall 和 Putthiphong Boonphong 负责本项目的代码实现,测试和编写文档工作; +Andrius Juozapaitis, Giovanni Cuccu, Raj Nagappan 和 Tomas Pinos 的贡献; +而 Simone Tripodi 发现了这些人并邀请他们参与到这一个 MyBatis 子项目之中。没有他们的努力,这个项目只能沦为空谈。 + +## 帮助改进文档... + +如果你发现文档有任何的缺失,或者缺少某一个功能点的说明,最好的解决办法是先自己学习,并且为缺失的部份补上相应的文档。 + +手册的 markdown 出自 [项目的 Git 仓库](https://github.com/mybatis/spring/tree/master/src/site) 。Fork 仓库,更新它并提交 Pull Request 吧。 + +还有其它像你一样的人都需要阅读这份文档,而你,就是这份文档最好的作者。 + +## 文档的翻译版本 + +可以阅读以下 MyBatis-Spring 文档的翻译版本: + + + +想用自己的母语阅读这篇文档吗?那就用你的母语翻译它吧! diff --git a/src/site/zh_CN/markdown/mappers.md b/src/site/zh_CN/markdown/mappers.md new file mode 100644 index 0000000000..d9cd92b532 --- /dev/null +++ b/src/site/zh_CN/markdown/mappers.md @@ -0,0 +1,183 @@ + +# 注入映射器 + +与其在数据访问对象(DAO)中手工编写使用 `SqlSessionDaoSupport` 或 `SqlSessionTemplate` 的代码,还不如让 Mybatis-Spring 为你创建一个线程安全的映射器,这样你就可以直接注入到其它的 bean 中了: + +```xml + + + +``` + +注入完毕后,映射器就可以在你的应用逻辑代码中使用了: + +```java +public class FooServiceImpl implements FooService { + + private final UserMapper userMapper; + + public FooServiceImpl(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public User doSomeBusinessStuff(String userId) { + return this.userMapper.getUser(userId); + } +} +``` + +注意代码中并没有任何的对 `SqlSession` 或 MyBatis 的引用。你也不需要担心创建、打开、关闭 session,MyBatis-Spring 将为你打理好一切。 + + +## 注册映射器 + +注册映射器的方法根据你的配置方法,即经典的 XML 配置或新的 3.0 以上版本的 Java 配置(也就是常说的 `@Configuration`),而有所不同。 + +### XML 配置 + +在你的 XML 中加入 `MapperFactoryBean` 以便将映射器注册到 Spring 中。就像下面一样: + +```xml + + + + +``` + +如果映射器接口 UserMapper 在相同的类路径下有对应的 MyBatis XML 映射器配置文件,将会被 `MapperFactoryBean` 自动解析。不需要在 MyBatis 配置文件中显式配置映射器,除非映射器配置文件与接口类不在同一个类路径下。 +参考 `SqlSessionFactoryBean` 的 [`configLocation`](factorybean.html) 属性以获取更多信息。 + +注意 `MapperFactoryBean` 需要配置一个 `SqlSessionFactory` 或 `SqlSessionTemplate`。它们可以分别通过 `sqlSessionFactory` 和 `sqlSessionTemplate` 属性来进行设置。 +如果两者都被设置,`SqlSessionFactory` 将被忽略。由于 `SqlSessionTemplate` 已经设置了一个 session 工厂,`MapperFactoryBean` 将使用那个工厂。 + +### Java 配置 + +```java +@Configuration +public class MyBatisConfig { + @Bean + public MapperFactoryBean userMapper() throws Exception { + MapperFactoryBean factoryBean = new MapperFactoryBean<>(UserMapper.class); + factoryBean.setSqlSessionFactory(sqlSessionFactory()); + return factoryBean; + } +} +``` + + +## 发现映射器 + +不需要一个个地注册你的所有映射器。你可以让 MyBatis-Spring 对类路径进行扫描来发现它们。 + + +有几种办法来发现映射器: + +* 使用 `` 元素 +* 使用 `@MapperScan` 注解 +* 在经典 Spring XML 配置文件中注册一个 `MapperScannerConfigurer` + +`` 和 `@MapperScan` 都在 MyBatis-Spring 1.2.0 中被引入。`@MapperScan` 需要你使用 Spring 3.1+。 + +从 2.0.2 版本开始,mapper 扫描机制支持控制 mapper bean 的懒加载 (`lazy-initialization`) ,这个选项是可选的。 +添加这个选项是为了支持 Spring Boot 2.2 中的懒加载特性。 默认的选项值为 `false`  (即不开启懒加载)。 +如果开发者想使用懒加载的特性,需要显式地将其设置为 `true`. + +IMPORTANT +如果开发者想使用懒加载的特性,需要首先知道其局限性。 +如果有下列情况,懒加载将在你的应用中不起作用: + +- 当使用 ``(`@One`) 与 ``(`@Many`) 指向**其它的 mapper** 时 +- 当使用 `` 将**其它的 mapper** 的一部分包含进来时 +- 当使用 ``(`@CacheNamespaceRef`) 指向**其它的 mapper** 的缓存时 +- 当使用 ` - insert into persons (first_name, last_name) values(#{firstName}, #{lastName}) - - - diff --git a/src/test/java/org/mybatis/spring/sample/mapper/UserMapper.java b/src/test/java/org/mybatis/spring/sample/mapper/UserMapper.java index c30c627401..0308a28a6e 100644 --- a/src/test/java/org/mybatis/spring/sample/mapper/UserMapper.java +++ b/src/test/java/org/mybatis/spring/sample/mapper/UserMapper.java @@ -1,25 +1,25 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.sample.mapper; import org.mybatis.spring.sample.domain.User; /** - * A org.mybatis.spring sample mapper. This interface will be used by MapperFactoryBean to create a - * proxy implementation at Spring application startup. + * A org.mybatis.spring sample mapper. This interface will be used by MapperFactoryBean to create a proxy implementation + * at Spring application startup. */ public interface UserMapper { diff --git a/src/test/java/org/mybatis/spring/sample/mapper/UserMapper.xml b/src/test/java/org/mybatis/spring/sample/mapper/UserMapper.xml deleted file mode 100644 index 6d70c9e4a8..0000000000 --- a/src/test/java/org/mybatis/spring/sample/mapper/UserMapper.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/java/org/mybatis/spring/sample/mapper/package-info.java b/src/test/java/org/mybatis/spring/sample/mapper/package-info.java index 8a5ed3c6eb..06930a9bfe 100644 --- a/src/test/java/org/mybatis/spring/sample/mapper/package-info.java +++ b/src/test/java/org/mybatis/spring/sample/mapper/package-info.java @@ -1,20 +1,19 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ /** * Package with DAOs/mappers. */ package org.mybatis.spring.sample.mapper; - diff --git a/src/test/java/org/mybatis/spring/sample/package-info.java b/src/test/java/org/mybatis/spring/sample/package-info.java index 19b5ff35fe..e93da2d804 100644 --- a/src/test/java/org/mybatis/spring/sample/package-info.java +++ b/src/test/java/org/mybatis/spring/sample/package-info.java @@ -1,26 +1,25 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ /** * Sample for four usage scenarios. *
    *
  • Automatic configuration with MapperScannerConfigurer
  • *
  • Automatic DAO creation with MapperFactoryBean
  • - *
  • Extending SqlSessionDaoSupport to code your mapper implementation
  • - *
  • Configuring a DAO for batch processing
  • + *
  • Extending SqlSessionDaoSupport to code your mapper implementation
  • + *
  • Configuring a DAO for batch processing
  • *
*/ package org.mybatis.spring.sample; - diff --git a/src/test/java/org/mybatis/spring/sample/service/BarService.java b/src/test/java/org/mybatis/spring/sample/service/BarService.java index bfac20111c..640e37e12c 100644 --- a/src/test/java/org/mybatis/spring/sample/service/BarService.java +++ b/src/test/java/org/mybatis/spring/sample/service/BarService.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.sample.service; diff --git a/src/test/java/org/mybatis/spring/sample/service/FooService.java b/src/test/java/org/mybatis/spring/sample/service/FooService.java index 9427479c64..1ff450b2f6 100644 --- a/src/test/java/org/mybatis/spring/sample/service/FooService.java +++ b/src/test/java/org/mybatis/spring/sample/service/FooService.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.sample.service; diff --git a/src/test/java/org/mybatis/spring/scan/ScanAnnotation.java b/src/test/java/org/mybatis/spring/scan/ScanAnnotation.java new file mode 100644 index 0000000000..310bf9b312 --- /dev/null +++ b/src/test/java/org/mybatis/spring/scan/ScanAnnotation.java @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.spring.scan; + +public @interface ScanAnnotation { +} diff --git a/src/test/java/org/mybatis/spring/scan/ScanClass1.java b/src/test/java/org/mybatis/spring/scan/ScanClass1.java new file mode 100644 index 0000000000..169ad00c85 --- /dev/null +++ b/src/test/java/org/mybatis/spring/scan/ScanClass1.java @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.spring.scan; + +import java.util.function.Supplier; + +import org.apache.ibatis.jdbc.SQL; + +public class ScanClass1 { + + public static class StaticInnerClass { + + } + + public class InnerClass { + + } + + public enum InnerEnum { + + } + + public @interface InnerAnnotation { + + } + + public String createSqlUsingAnonymousClass() { + return new SQL() { + { + SELECT("a"); + FROM("test1"); + } + }.toString(); + } + + public Supplier createSqlSupplier() { + return () -> "SELECT a FROM test1"; + } + +} diff --git a/src/test/java/org/mybatis/spring/scan/ScanClass2.java b/src/test/java/org/mybatis/spring/scan/ScanClass2.java new file mode 100644 index 0000000000..5c73725f03 --- /dev/null +++ b/src/test/java/org/mybatis/spring/scan/ScanClass2.java @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.spring.scan; + +import java.util.function.Supplier; + +import org.apache.ibatis.jdbc.SQL; + +public class ScanClass2 { + + public static class StaticInnerClass { + + } + + public class InnerClass { + + } + + public enum InnerEnum { + + } + + public @interface InnerAnnotation { + + } + + public String createSqlUsingAnonymousClass() { + return new SQL() { + { + SELECT("a"); + FROM("test2"); + } + }.toString(); + } + + public Supplier createSqlSupplier() { + return () -> "SELECT a FROM test2"; + } + +} diff --git a/src/test/java/org/mybatis/spring/scan/ScanEnum.java b/src/test/java/org/mybatis/spring/scan/ScanEnum.java new file mode 100644 index 0000000000..83804a1747 --- /dev/null +++ b/src/test/java/org/mybatis/spring/scan/ScanEnum.java @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.spring.scan; + +public enum ScanEnum { +} diff --git a/src/test/java/org/mybatis/spring/scan/ScanInterface.java b/src/test/java/org/mybatis/spring/scan/ScanInterface.java new file mode 100644 index 0000000000..260db20757 --- /dev/null +++ b/src/test/java/org/mybatis/spring/scan/ScanInterface.java @@ -0,0 +1,19 @@ +/* + * Copyright 2010-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.spring.scan; + +public interface ScanInterface { +} diff --git a/src/test/java/org/mybatis/spring/scan/package-info.java b/src/test/java/org/mybatis/spring/scan/package-info.java new file mode 100644 index 0000000000..30f05f1934 --- /dev/null +++ b/src/test/java/org/mybatis/spring/scan/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.spring.scan; diff --git a/src/test/java/org/mybatis/spring/submitted/autowire/AutowireTest.java b/src/test/java/org/mybatis/spring/submitted/autowire/AutowireTest.java index cec0071d03..5c348c0698 100644 --- a/src/test/java/org/mybatis/spring/submitted/autowire/AutowireTest.java +++ b/src/test/java/org/mybatis/spring/submitted/autowire/AutowireTest.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.autowire; @@ -20,20 +20,21 @@ import org.junit.jupiter.api.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; -public class AutowireTest { - private ClassPathXmlApplicationContext context; +class AutowireTest { @Test void shouldReturnMapper() { - context = new ClassPathXmlApplicationContext("classpath:org/mybatis/spring/submitted/autowire/spring.xml"); + try (var context = new ClassPathXmlApplicationContext( + "classpath:org/mybatis/spring/submitted/autowire/spring.xml")) { - FooMapper fooMapper = (FooMapper) context.getBean("fooMapper"); - assertThat(fooMapper).isNotNull(); - fooMapper.executeFoo(); + var fooMapper = (FooMapper) context.getBean("fooMapper"); + assertThat(fooMapper).isNotNull(); + fooMapper.executeFoo(); - BarMapper barMapper = (BarMapper) context.getBean("barMapper"); - assertThat(barMapper).isNotNull(); - barMapper.executeBar(); + var barMapper = (BarMapper) context.getBean("barMapper"); + assertThat(barMapper).isNotNull(); + barMapper.executeBar(); + } } } diff --git a/src/test/java/org/mybatis/spring/submitted/autowire/BarMapper.java b/src/test/java/org/mybatis/spring/submitted/autowire/BarMapper.java index 03b7be2c07..6ecced0f0e 100644 --- a/src/test/java/org/mybatis/spring/submitted/autowire/BarMapper.java +++ b/src/test/java/org/mybatis/spring/submitted/autowire/BarMapper.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.autowire; diff --git a/src/test/java/org/mybatis/spring/submitted/autowire/BarMapper.xml b/src/test/java/org/mybatis/spring/submitted/autowire/BarMapper.xml deleted file mode 100755 index cd9867bee9..0000000000 --- a/src/test/java/org/mybatis/spring/submitted/autowire/BarMapper.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/test/java/org/mybatis/spring/submitted/autowire/FooMapper.java b/src/test/java/org/mybatis/spring/submitted/autowire/FooMapper.java index 229ecbf040..b711bbbf9b 100644 --- a/src/test/java/org/mybatis/spring/submitted/autowire/FooMapper.java +++ b/src/test/java/org/mybatis/spring/submitted/autowire/FooMapper.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.autowire; diff --git a/src/test/java/org/mybatis/spring/submitted/autowire/FooMapper.xml b/src/test/java/org/mybatis/spring/submitted/autowire/FooMapper.xml deleted file mode 100644 index 0fc3998111..0000000000 --- a/src/test/java/org/mybatis/spring/submitted/autowire/FooMapper.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/test/java/org/mybatis/spring/submitted/autowire/IBar.java b/src/test/java/org/mybatis/spring/submitted/autowire/IBar.java index 01214a1676..cf1be92eba 100644 --- a/src/test/java/org/mybatis/spring/submitted/autowire/IBar.java +++ b/src/test/java/org/mybatis/spring/submitted/autowire/IBar.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2015 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.autowire; diff --git a/src/test/java/org/mybatis/spring/submitted/autowire/IFoo.java b/src/test/java/org/mybatis/spring/submitted/autowire/IFoo.java index cf653f95d2..08971ca776 100644 --- a/src/test/java/org/mybatis/spring/submitted/autowire/IFoo.java +++ b/src/test/java/org/mybatis/spring/submitted/autowire/IFoo.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2015 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.autowire; diff --git a/src/test/java/org/mybatis/spring/submitted/autowire/database-schema-bar.sql b/src/test/java/org/mybatis/spring/submitted/autowire/database-schema-bar.sql deleted file mode 100755 index 482d804eec..0000000000 --- a/src/test/java/org/mybatis/spring/submitted/autowire/database-schema-bar.sql +++ /dev/null @@ -1,21 +0,0 @@ --- --- Copyright 2010-2016 the original author or authors. --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -create table bar ( - id varchar(80) not null, - name varchar(80) not null, - constraint pk_user primary key (id) -); diff --git a/src/test/java/org/mybatis/spring/submitted/autowire/database-schema-foo.sql b/src/test/java/org/mybatis/spring/submitted/autowire/database-schema-foo.sql deleted file mode 100755 index 7274a7be13..0000000000 --- a/src/test/java/org/mybatis/spring/submitted/autowire/database-schema-foo.sql +++ /dev/null @@ -1,21 +0,0 @@ --- --- Copyright 2010-2016 the original author or authors. --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -create table foo ( - id varchar(80) not null, - name varchar(80) not null, - constraint pk_user primary key (id) -); diff --git a/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/Mapper.java b/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/Mapper.java index 80ede7eb72..13881744ea 100644 --- a/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/Mapper.java +++ b/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/Mapper.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.webapp_placeholder; diff --git a/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/WebappPlaceholderTest.java b/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/WebappPlaceholderTest.java index 41e0a71dd6..c50cdb94a9 100644 --- a/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/WebappPlaceholderTest.java +++ b/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/WebappPlaceholderTest.java @@ -1,39 +1,48 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.webapp_placeholder; import static org.assertj.core.api.Assertions.assertThat; +import org.apache.ibatis.session.SqlSessionFactory; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.web.WebAppConfiguration; @ExtendWith(SpringExtension.class) @WebAppConfiguration -@SpringJUnitConfig(locations = "file:src/test/java/org/mybatis/spring/submitted/webapp_placeholder/spring.xml") -public class WebappPlaceholderTest { +@SpringJUnitConfig(locations = "classpath:org/mybatis/spring/submitted/webapp_placeholder/spring.xml") +class WebappPlaceholderTest { @Autowired - private Mapper mapper; + private SqlSessionFactory sqlSessionFactory; + + @Autowired + private ApplicationContext applicationContext; @Test - void testName() throws Exception { + void testName() { + Assertions.assertEquals(0, sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers().size()); + var mapper = applicationContext.getBean(Mapper.class); assertThat(mapper).isNotNull(); + Assertions.assertEquals(1, sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers().size()); } } diff --git a/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/conf.properties b/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/conf.properties deleted file mode 100644 index c7ece4bdb7..0000000000 --- a/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/conf.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2010-2016 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -basePkg=org.mybatis.spring.submitted.webapp_placeholder diff --git a/src/test/java/org/mybatis/spring/submitted/xa/User.java b/src/test/java/org/mybatis/spring/submitted/xa/User.java index 4fe1b1c5a7..c27dabaa95 100644 --- a/src/test/java/org/mybatis/spring/submitted/xa/User.java +++ b/src/test/java/org/mybatis/spring/submitted/xa/User.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2015 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.xa; @@ -19,7 +19,7 @@ public class User { private Integer id; private String name; - + public User() { } diff --git a/src/test/java/org/mybatis/spring/submitted/xa/UserMapper.java b/src/test/java/org/mybatis/spring/submitted/xa/UserMapper.java index 8a0564f8b9..151969e96f 100644 --- a/src/test/java/org/mybatis/spring/submitted/xa/UserMapper.java +++ b/src/test/java/org/mybatis/spring/submitted/xa/UserMapper.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2015 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.xa; @@ -19,11 +19,11 @@ import org.apache.ibatis.annotations.Select; public interface UserMapper { - - @Insert("INSERT INTO USERS VALUES(#{id}, #{name})") - void save(User user); - @Select("SELECT * FROM USERS WHERE ID=#{id}") - User select(int id); + @Insert("INSERT INTO USERS VALUES(#{id}, #{name})") + void save(User user); + + @Select("SELECT * FROM USERS WHERE ID=#{id}") + User select(int id); } diff --git a/src/test/java/org/mybatis/spring/submitted/xa/UserService.java b/src/test/java/org/mybatis/spring/submitted/xa/UserService.java index 4256f1abd6..f28c790cbd 100644 --- a/src/test/java/org/mybatis/spring/submitted/xa/UserService.java +++ b/src/test/java/org/mybatis/spring/submitted/xa/UserService.java @@ -1,22 +1,22 @@ -/** - * Copyright 2010-2015 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.xa; public interface UserService { - + void saveWithNoFailure(User user); void saveWithFailure(User user); diff --git a/src/test/java/org/mybatis/spring/submitted/xa/UserServiceImpl.java b/src/test/java/org/mybatis/spring/submitted/xa/UserServiceImpl.java index 10a4d9665f..c98a1e1cd9 100644 --- a/src/test/java/org/mybatis/spring/submitted/xa/UserServiceImpl.java +++ b/src/test/java/org/mybatis/spring/submitted/xa/UserServiceImpl.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2015 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.xa; @@ -33,7 +33,7 @@ public void saveWithNoFailure(User user) { userMapperMaster.save(user); userMapperSlave.save(user); } - + @Override @Transactional public void saveWithFailure(User user) { @@ -44,8 +44,9 @@ public void saveWithFailure(User user) { @Override public boolean checkUserExists(int id) { - if (userMapperMaster.select(id) != null) return true; - if (userMapperSlave.select(id) != null) return true; + if (userMapperMaster.select(id) != null || userMapperSlave.select(id) != null) { + return true; + } return false; } } diff --git a/src/test/java/org/mybatis/spring/submitted/xa/UserServiceTest.java b/src/test/java/org/mybatis/spring/submitted/xa/UserServiceTest.java index f4bef0609a..3c2fc2ff4b 100644 --- a/src/test/java/org/mybatis/spring/submitted/xa/UserServiceTest.java +++ b/src/test/java/org/mybatis/spring/submitted/xa/UserServiceTest.java @@ -1,49 +1,52 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.submitted.xa; import static org.assertj.core.api.Assertions.assertThat; -import javax.transaction.UserTransaction; +import jakarta.transaction.UserTransaction; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +@Disabled("Yet not found OSS implementation that supported Jakarta EE 9+ APIs") @ExtendWith(SpringExtension.class) @SpringJUnitConfig(locations = "classpath:org/mybatis/spring/submitted/xa/applicationContext.xml") -public class UserServiceTest { +class UserServiceTest { + + @Autowired + UserTransaction userTransaction; - @Autowired UserTransaction userTransaction; - @Autowired private UserService userService; - + @Test void testCommit() { - User user = new User(1, "Pocoyo"); + var user = new User(1, "Pocoyo"); userService.saveWithNoFailure(user); assertThat(userService.checkUserExists(user.getId())).isTrue(); } - + @Test void testRollback() { - User user = new User(2, "Pocoyo"); + var user = new User(2, "Pocoyo"); try { userService.saveWithFailure(user); } catch (RuntimeException ignore) { @@ -55,23 +58,23 @@ void testRollback() { @Test void testCommitWithExistingTx() throws Exception { userTransaction.begin(); - User user = new User(3, "Pocoyo"); + var user = new User(3, "Pocoyo"); userService.saveWithNoFailure(user); userTransaction.commit(); assertThat(userService.checkUserExists(user.getId())).isTrue(); } - // TODO when the outer JTA tx is rolledback, + // TODO when the outer JTA tx is rolledback, // SqlSession should be rolledback but it is committed // because Spring calls beforeCommmit from its TX interceptor // then, the JTA TX may be rolledback. @Test void testRollbackWithExistingTx() throws Exception { userTransaction.begin(); - User user = new User(5, "Pocoyo"); + var user = new User(5, "Pocoyo"); userService.saveWithNoFailure(user); userTransaction.rollback(); assertThat(userService.checkUserExists(user.getId())).isFalse(); } - + } diff --git a/src/test/java/org/mybatis/spring/submitted/xa/database-schema.sql b/src/test/java/org/mybatis/spring/submitted/xa/database-schema.sql deleted file mode 100644 index b353338dc5..0000000000 --- a/src/test/java/org/mybatis/spring/submitted/xa/database-schema.sql +++ /dev/null @@ -1,20 +0,0 @@ --- --- Copyright 2010-2016 the original author or authors. --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -CREATE TABLE USERS ( - id integer not null, - name varchar(80) not null -); diff --git a/src/test/java/org/mybatis/spring/support/SqlSessionDaoSupportTest.java b/src/test/java/org/mybatis/spring/support/SqlSessionDaoSupportTest.java index c441605055..eb4a64c6a3 100644 --- a/src/test/java/org/mybatis/spring/support/SqlSessionDaoSupportTest.java +++ b/src/test/java/org/mybatis/spring/support/SqlSessionDaoSupportTest.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.support; @@ -31,7 +31,7 @@ import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; -public final class SqlSessionDaoSupportTest extends AbstractMyBatisSpringTest { +class SqlSessionDaoSupportTest extends AbstractMyBatisSpringTest { private SqlSessionDaoSupport sqlSessionDaoSupport; private GenericApplicationContext applicationContext; @@ -48,7 +48,7 @@ void closeConnection() throws SQLException { @Test void testWithSqlSessionTemplate() { - SqlSessionTemplate sessionTemplate = new SqlSessionTemplate(sqlSessionFactory); + var sessionTemplate = new SqlSessionTemplate(sqlSessionFactory); sqlSessionDaoSupport.setSqlSessionTemplate(sessionTemplate); sqlSessionDaoSupport.afterPropertiesSet(); @@ -61,13 +61,12 @@ void testWithSqlSessionFactory() { sqlSessionDaoSupport.afterPropertiesSet(); assertThat(((SqlSessionTemplate) sqlSessionDaoSupport.getSqlSession()).getSqlSessionFactory()) - .as("should store the Factory") - .isEqualTo(sqlSessionFactory); + .as("should store the Factory").isEqualTo(sqlSessionFactory); } @Test void testWithBothFactoryAndTemplate() { - SqlSessionTemplate sessionTemplate = new SqlSessionTemplate(sqlSessionFactory); + var sessionTemplate = new SqlSessionTemplate(sqlSessionFactory); sqlSessionDaoSupport.setSqlSessionTemplate(sessionTemplate); sqlSessionDaoSupport.setSqlSessionFactory(sqlSessionFactory); sqlSessionDaoSupport.afterPropertiesSet(); @@ -99,7 +98,7 @@ void testAutowireWithTwoFactories() { private void setupContext() { applicationContext = new GenericApplicationContext(); - GenericBeanDefinition definition = new GenericBeanDefinition(); + var definition = new GenericBeanDefinition(); definition.setBeanClass(MockSqlSessionDao.class); applicationContext.registerBeanDefinition("dao", definition); @@ -115,7 +114,7 @@ private void startContext() { } private void setupSqlSessionFactory(String name) { - GenericBeanDefinition definition = new GenericBeanDefinition(); + var definition = new GenericBeanDefinition(); definition.setBeanClass(SqlSessionFactoryBean.class); definition.getPropertyValues().add("dataSource", dataSource); diff --git a/src/test/java/org/mybatis/spring/transaction/SpringTransactionManagerTest.java b/src/test/java/org/mybatis/spring/transaction/SpringTransactionManagerTest.java index 45272e24a6..5c233a039b 100644 --- a/src/test/java/org/mybatis/spring/transaction/SpringTransactionManagerTest.java +++ b/src/test/java/org/mybatis/spring/transaction/SpringTransactionManagerTest.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.transaction; @@ -19,19 +19,18 @@ import org.junit.jupiter.api.Test; import org.mybatis.spring.AbstractMyBatisSpringTest; -import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; -public final class SpringTransactionManagerTest extends AbstractMyBatisSpringTest { +class SpringTransactionManagerTest extends AbstractMyBatisSpringTest { @Test void shouldNoOpWithTx() throws Exception { - DefaultTransactionDefinition txDef = new DefaultTransactionDefinition(); + var txDef = new DefaultTransactionDefinition(); txDef.setPropagationBehaviorName("PROPAGATION_REQUIRED"); - TransactionStatus status = txManager.getTransaction(txDef); + var status = txManager.getTransaction(txDef); - SpringManagedTransactionFactory transactionFactory = new SpringManagedTransactionFactory(); - SpringManagedTransaction transaction = (SpringManagedTransaction) transactionFactory.newTransaction(dataSource, null, false); + var transactionFactory = new SpringManagedTransactionFactory(); + var transaction = (SpringManagedTransaction) transactionFactory.newTransaction(dataSource, null, false); transaction.getConnection(); transaction.commit(); transaction.close(); @@ -41,26 +40,29 @@ void shouldNoOpWithTx() throws Exception { txManager.commit(status); } - // @Test - // public void shouldManageWithOtherDatasource() throws Exception { - // DefaultTransactionDefinition txDef = new DefaultTransactionDefinition(); - // txDef.setPropagationBehaviorName("PROPAGATION_REQUIRED"); - // TransactionStatus status = txManager.getTransaction(txDef); + // TODO Test does not compile + // @Disabled + // @Test + // public void shouldManageWithOtherDatasource() throws Exception { + // DefaultTransactionDefinition txDef = new DefaultTransactionDefinition(); + // txDef.setPropagationBehaviorName("PROPAGATION_REQUIRED"); + // TransactionStatus status = txManager.getTransaction(txDef); // - // SpringManagedTransactionFactory transactionFactory = new SpringManagedTransactionFactory(new MockDataSource()); - // SpringManagedTransaction transaction = (SpringManagedTransaction) transactionFactory.newTransaction(connection, false); - // transaction.commit(); - // transaction.close(); - // assertEquals("should call commit on Connection", 1, connection.getNumberCommits()); - // assertTrue("should close the Connection", connection.isClosed()); + // SpringManagedTransactionFactory transactionFactory = new SpringManagedTransactionFactory(new MockDataSource()); + // SpringManagedTransaction transaction = (SpringManagedTransaction) transactionFactory.newTransaction(connection, + // false); + // transaction.commit(); + // transaction.close(); + // assertEquals(1, connection.getNumberCommits(), "should call commit on Connection"); + // assertTrue(connection.isClosed(), "should close the Connection"); // - // txManager.commit(status); - // } + // txManager.commit(status); + // } @Test void shouldManageWithNoTx() throws Exception { - SpringManagedTransactionFactory transactionFactory = new SpringManagedTransactionFactory(); - SpringManagedTransaction transaction = (SpringManagedTransaction) transactionFactory.newTransaction(dataSource, null, false); + var transactionFactory = new SpringManagedTransactionFactory(); + var transaction = (SpringManagedTransaction) transactionFactory.newTransaction(dataSource, null, false); transaction.getConnection(); transaction.commit(); transaction.close(); @@ -70,8 +72,8 @@ void shouldManageWithNoTx() throws Exception { @Test void shouldNotCommitWithNoTxAndAutocommitIsOn() throws Exception { - SpringManagedTransactionFactory transactionFactory = new SpringManagedTransactionFactory(); - SpringManagedTransaction transaction = (SpringManagedTransaction) transactionFactory.newTransaction(dataSource, null, false); + var transactionFactory = new SpringManagedTransactionFactory(); + var transaction = (SpringManagedTransaction) transactionFactory.newTransaction(dataSource, null, false); connection.setAutoCommit(true); transaction.getConnection(); transaction.commit(); @@ -82,8 +84,8 @@ void shouldNotCommitWithNoTxAndAutocommitIsOn() throws Exception { @Test void shouldIgnoreAutocommit() throws Exception { - SpringManagedTransactionFactory transactionFactory = new SpringManagedTransactionFactory(); - SpringManagedTransaction transaction = (SpringManagedTransaction) transactionFactory.newTransaction(dataSource, null, true); + var transactionFactory = new SpringManagedTransactionFactory(); + var transaction = (SpringManagedTransaction) transactionFactory.newTransaction(dataSource, null, true); transaction.getConnection(); transaction.commit(); transaction.close(); diff --git a/src/test/java/org/mybatis/spring/type/DummyMapperFactoryBean.java b/src/test/java/org/mybatis/spring/type/DummyMapperFactoryBean.java index e070594934..7682d2b3d2 100644 --- a/src/test/java/org/mybatis/spring/type/DummyMapperFactoryBean.java +++ b/src/test/java/org/mybatis/spring/type/DummyMapperFactoryBean.java @@ -1,32 +1,31 @@ -/** - * Copyright 2010-2017 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.type; +import java.lang.reflect.Proxy; +import java.util.concurrent.atomic.AtomicInteger; + import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.logging.Logger; import org.mybatis.logging.LoggerFactory; import org.mybatis.spring.mapper.MapperFactoryBean; -import java.lang.reflect.Proxy; -import java.util.concurrent.atomic.AtomicInteger; - public class DummyMapperFactoryBean extends MapperFactoryBean { public DummyMapperFactoryBean() { - super(); } public DummyMapperFactoryBean(Class mapperInterface) { @@ -48,11 +47,11 @@ protected void checkDaoConfig() { @Override public T getObject() throws Exception { - MapperFactoryBean mapperFactoryBean = new MapperFactoryBean<>(); + var mapperFactoryBean = new MapperFactoryBean(); mapperFactoryBean.setMapperInterface(getMapperInterface()); mapperFactoryBean.setAddToConfig(isAddToConfig()); mapperFactoryBean.setSqlSessionFactory(getCustomSessionFactoryForClass()); - T object = mapperFactoryBean.getObject(); + var object = mapperFactoryBean.getObject(); mapperInstanceCount.incrementAndGet(); return object; } @@ -61,20 +60,22 @@ private SqlSessionFactory getCustomSessionFactoryForClass() { // can for example read a custom annotation to set a custom sqlSessionFactory // just a dummy implementation example - return (SqlSessionFactory) Proxy.newProxyInstance( - SqlSessionFactory.class.getClassLoader(), - new Class[]{SqlSessionFactory.class}, - (proxy, method, args) -> { - if ("getConfiguration".equals(method.getName())) { - return getSqlSession().getConfiguration(); - } - // dummy - return null; - } - ); + return (SqlSessionFactory) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), + new Class[] { SqlSessionFactory.class }, (proxy, method, args) -> { + if ("getConfiguration".equals(method.getName())) { + return getSqlSession().getConfiguration(); + } + // dummy + return null; + }); } - public static int getMapperCount(){ + public static int getMapperCount() { return mapperInstanceCount.get(); } + + public static void clear() { + mapperInstanceCount.set(0); + } + } diff --git a/src/test/java/org/mybatis/spring/type/DummyTypeAlias.java b/src/test/java/org/mybatis/spring/type/DummyTypeAlias.java index e9d55099fc..9c3e41862f 100644 --- a/src/test/java/org/mybatis/spring/type/DummyTypeAlias.java +++ b/src/test/java/org/mybatis/spring/type/DummyTypeAlias.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.type; diff --git a/src/test/java/org/mybatis/spring/type/DummyTypeAlias2.java b/src/test/java/org/mybatis/spring/type/DummyTypeAlias2.java index b477a15272..be4139622b 100644 --- a/src/test/java/org/mybatis/spring/type/DummyTypeAlias2.java +++ b/src/test/java/org/mybatis/spring/type/DummyTypeAlias2.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.type; diff --git a/src/test/java/org/mybatis/spring/type/DummyTypeHandler.java b/src/test/java/org/mybatis/spring/type/DummyTypeHandler.java index 2ad46d449b..421db96c51 100644 --- a/src/test/java/org/mybatis/spring/type/DummyTypeHandler.java +++ b/src/test/java/org/mybatis/spring/type/DummyTypeHandler.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.type; diff --git a/src/test/java/org/mybatis/spring/type/DummyTypeHandler2.java b/src/test/java/org/mybatis/spring/type/DummyTypeHandler2.java index 5cd5d407e3..581ba77a21 100644 --- a/src/test/java/org/mybatis/spring/type/DummyTypeHandler2.java +++ b/src/test/java/org/mybatis/spring/type/DummyTypeHandler2.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2016 the original author or authors. +/* + * Copyright 2010-2024 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.type; diff --git a/src/test/java/org/mybatis/spring/type/SuperType.java b/src/test/java/org/mybatis/spring/type/SuperType.java index 129b962c71..c494cdef8a 100644 --- a/src/test/java/org/mybatis/spring/type/SuperType.java +++ b/src/test/java/org/mybatis/spring/type/SuperType.java @@ -1,17 +1,17 @@ -/** - * Copyright 2010-2015 the original author or authors. +/* + * Copyright 2010-2022 the original author or authors. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.mybatis.spring.type; diff --git a/src/test/java/org/mybatis/spring/type/TypeHandlerFactory.java b/src/test/java/org/mybatis/spring/type/TypeHandlerFactory.java new file mode 100644 index 0000000000..2f563c208a --- /dev/null +++ b/src/test/java/org/mybatis/spring/type/TypeHandlerFactory.java @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.spring.type; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.UUID; + +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedTypes; +import org.apache.ibatis.type.TypeHandler; + +public interface TypeHandlerFactory { + + static TypeHandler handler1() { + return new TypeHandler<>() { + @Override + public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) { + + } + + @Override + public String getResult(ResultSet rs, String columnName) { + return null; + } + + @Override + public String getResult(ResultSet rs, int columnIndex) { + return null; + } + + @Override + public String getResult(CallableStatement cs, int columnIndex) { + return null; + } + }; + } + + static TypeHandler handler2() { + return new InnerTypeHandler(); + } + + @MappedTypes({ UUID.class }) + class InnerTypeHandler implements TypeHandler { + + @Override + public void setParameter(PreparedStatement ps, int i, UUID parameter, JdbcType jdbcType) { + } + + @Override + public UUID getResult(ResultSet rs, String columnName) { + return null; + } + + @Override + public UUID getResult(ResultSet rs, int columnIndex) { + return null; + } + + @Override + public UUID getResult(CallableStatement cs, int columnIndex) { + return null; + } + + } + +} diff --git a/src/test/java/org/mybatis/spring/type/package-info.java b/src/test/java/org/mybatis/spring/type/package-info.java new file mode 100644 index 0000000000..f3e10d6528 --- /dev/null +++ b/src/test/java/org/mybatis/spring/type/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mybatis.spring.type; diff --git a/src/test/resources/log4j2-test.xml b/src/test/resources/log4j2-test.xml deleted file mode 100644 index c14cfa5b67..0000000000 --- a/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/test/java/org/mybatis/spring/TestMapper.xml b/src/test/resources/org/mybatis/spring/TestMapper.xml similarity index 50% rename from src/test/java/org/mybatis/spring/TestMapper.xml rename to src/test/resources/org/mybatis/spring/TestMapper.xml index 3517d22bf1..389d432dba 100644 --- a/src/test/java/org/mybatis/spring/TestMapper.xml +++ b/src/test/resources/org/mybatis/spring/TestMapper.xml @@ -1,19 +1,19 @@ + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/TestMapper3.xml b/src/test/resources/org/mybatis/spring/TestMapper3.xml new file mode 100644 index 0000000000..8a58cf70af --- /dev/null +++ b/src/test/resources/org/mybatis/spring/TestMapper3.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/annotation/override.properties b/src/test/resources/org/mybatis/spring/annotation/override.properties new file mode 100644 index 0000000000..10239f89cf --- /dev/null +++ b/src/test/resources/org/mybatis/spring/annotation/override.properties @@ -0,0 +1,17 @@ +# +# Copyright 2010-2025 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +myBean.name=MyBean!! diff --git a/src/test/resources/org/mybatis/spring/annotation/placeholders.properties b/src/test/resources/org/mybatis/spring/annotation/placeholders.properties new file mode 100644 index 0000000000..88160c486c --- /dev/null +++ b/src/test/resources/org/mybatis/spring/annotation/placeholders.properties @@ -0,0 +1,17 @@ +# +# Copyright 2010-2024 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +scan-package=org.mybatis.spring.annotation.mapper.ds1 diff --git a/src/test/resources/org/mybatis/spring/annotation/scan.properties b/src/test/resources/org/mybatis/spring/annotation/scan.properties new file mode 100644 index 0000000000..72055a9304 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/annotation/scan.properties @@ -0,0 +1,17 @@ +# +# Copyright 2010-2025 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +mybatis.lazy-initialization=true diff --git a/src/test/java/org/mybatis/spring/batch/applicationContext.xml b/src/test/resources/org/mybatis/spring/batch/applicationContext.xml similarity index 85% rename from src/test/java/org/mybatis/spring/batch/applicationContext.xml rename to src/test/resources/org/mybatis/spring/batch/applicationContext.xml index 0d068375f9..1d8bee023c 100644 --- a/src/test/java/org/mybatis/spring/batch/applicationContext.xml +++ b/src/test/resources/org/mybatis/spring/batch/applicationContext.xml @@ -1,19 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/mybatis/spring/config/factory-ref.xml b/src/test/resources/org/mybatis/spring/config/factory-ref.xml similarity index 60% rename from src/test/java/org/mybatis/spring/config/factory-ref.xml rename to src/test/resources/org/mybatis/spring/config/factory-ref.xml index cd3e4cb17f..06389b5869 100644 --- a/src/test/java/org/mybatis/spring/config/factory-ref.xml +++ b/src/test/resources/org/mybatis/spring/config/factory-ref.xml @@ -1,19 +1,19 @@ + + + + + + diff --git a/src/test/resources/org/mybatis/spring/config/mapper-factory-bean-class.xml b/src/test/resources/org/mybatis/spring/config/mapper-factory-bean-class.xml new file mode 100644 index 0000000000..5b0426d862 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/config/mapper-factory-bean-class.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/src/test/java/org/mybatis/spring/config/marker-and-annotation.xml b/src/test/resources/org/mybatis/spring/config/marker-and-annotation.xml similarity index 62% rename from src/test/java/org/mybatis/spring/config/marker-and-annotation.xml rename to src/test/resources/org/mybatis/spring/config/marker-and-annotation.xml index 5a309173dd..3fa23dcdf5 100644 --- a/src/test/java/org/mybatis/spring/config/marker-and-annotation.xml +++ b/src/test/resources/org/mybatis/spring/config/marker-and-annotation.xml @@ -1,19 +1,19 @@ + + + + diff --git a/src/test/resources/org/mybatis/spring/config/override.properties b/src/test/resources/org/mybatis/spring/config/override.properties new file mode 100644 index 0000000000..10239f89cf --- /dev/null +++ b/src/test/resources/org/mybatis/spring/config/override.properties @@ -0,0 +1,17 @@ +# +# Copyright 2010-2025 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +myBean.name=MyBean!! diff --git a/src/test/resources/org/mybatis/spring/config/placeholders.properties b/src/test/resources/org/mybatis/spring/config/placeholders.properties new file mode 100644 index 0000000000..300f2666ac --- /dev/null +++ b/src/test/resources/org/mybatis/spring/config/placeholders.properties @@ -0,0 +1,17 @@ +# +# Copyright 2010-2024 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +scan-package=org.mybatis.spring.mapper diff --git a/src/test/resources/org/mybatis/spring/config/process-property-placeholders-false.xml b/src/test/resources/org/mybatis/spring/config/process-property-placeholders-false.xml new file mode 100644 index 0000000000..e9c9a260b1 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/config/process-property-placeholders-false.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/config/process-property-placeholders-true.xml b/src/test/resources/org/mybatis/spring/config/process-property-placeholders-true.xml new file mode 100644 index 0000000000..87e98af616 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/config/process-property-placeholders-true.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/src/test/java/org/mybatis/spring/config/template-ref.xml b/src/test/resources/org/mybatis/spring/config/template-ref.xml similarity index 60% rename from src/test/java/org/mybatis/spring/config/template-ref.xml rename to src/test/resources/org/mybatis/spring/config/template-ref.xml index a2a5f7907c..76121280cb 100644 --- a/src/test/java/org/mybatis/spring/config/template-ref.xml +++ b/src/test/resources/org/mybatis/spring/config/template-ref.xml @@ -1,19 +1,19 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/appContextAspectJFilter.xml b/src/test/resources/org/mybatis/spring/filter/xml/appContextAspectJFilter.xml new file mode 100644 index 0000000000..8dcbac7aa0 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/appContextAspectJFilter.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/appContextAssignFilter.xml b/src/test/resources/org/mybatis/spring/filter/xml/appContextAssignFilter.xml new file mode 100644 index 0000000000..37cf7c18e1 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/appContextAssignFilter.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/appContextCombinedFilter.xml b/src/test/resources/org/mybatis/spring/filter/xml/appContextCombinedFilter.xml new file mode 100644 index 0000000000..09ec80551d --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/appContextCombinedFilter.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/appContextCustFilter.xml b/src/test/resources/org/mybatis/spring/filter/xml/appContextCustFilter.xml new file mode 100644 index 0000000000..918b0daf1a --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/appContextCustFilter.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/appContextInvalidFilter.xml b/src/test/resources/org/mybatis/spring/filter/xml/appContextInvalidFilter.xml new file mode 100644 index 0000000000..906765847e --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/appContextInvalidFilter.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/appContextInvalidFilter1.xml b/src/test/resources/org/mybatis/spring/filter/xml/appContextInvalidFilter1.xml new file mode 100644 index 0000000000..08a6d8aa07 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/appContextInvalidFilter1.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/appContextInvalidFilter2.xml b/src/test/resources/org/mybatis/spring/filter/xml/appContextInvalidFilter2.xml new file mode 100644 index 0000000000..b479e0bed6 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/appContextInvalidFilter2.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/appContextPlaceHolder.xml b/src/test/resources/org/mybatis/spring/filter/xml/appContextPlaceHolder.xml new file mode 100644 index 0000000000..145a6cd829 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/appContextPlaceHolder.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/appContextPlaceHolder1.xml b/src/test/resources/org/mybatis/spring/filter/xml/appContextPlaceHolder1.xml new file mode 100644 index 0000000000..bce32a0c58 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/appContextPlaceHolder1.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/appContextProcessPlaceHolderOff.xml b/src/test/resources/org/mybatis/spring/filter/xml/appContextProcessPlaceHolderOff.xml new file mode 100644 index 0000000000..c6ab57a115 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/appContextProcessPlaceHolderOff.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/appContextRegexFilter.xml b/src/test/resources/org/mybatis/spring/filter/xml/appContextRegexFilter.xml new file mode 100644 index 0000000000..2f82ca529d --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/appContextRegexFilter.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/filter/xml/default.properties b/src/test/resources/org/mybatis/spring/filter/xml/default.properties new file mode 100644 index 0000000000..6effd30b64 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/filter/xml/default.properties @@ -0,0 +1,19 @@ +# +# Copyright 2010-2025 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +filter.custom=org.mybatis.spring.filter.customfilter.CustomTypeFilter +filter.aspectj=*..CommonDataSourceMapper +filter.regex=org\.mybatis\.spring\.filter\.datasource\.datasource1\..* diff --git a/src/test/resources/org/mybatis/spring/mybatis-config.xml b/src/test/resources/org/mybatis/spring/mybatis-config.xml new file mode 100644 index 0000000000..4abec86943 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/mybatis-config.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/mybatis/spring/sample/config/applicationContext-batch.xml b/src/test/resources/org/mybatis/spring/sample/config/applicationContext-batch.xml similarity index 63% rename from src/test/java/org/mybatis/spring/sample/config/applicationContext-batch.xml rename to src/test/resources/org/mybatis/spring/sample/config/applicationContext-batch.xml index 1ad849e629..eb8731ae12 100644 --- a/src/test/java/org/mybatis/spring/sample/config/applicationContext-batch.xml +++ b/src/test/resources/org/mybatis/spring/sample/config/applicationContext-batch.xml @@ -1,19 +1,19 @@ - + @@ -57,7 +57,7 @@ --> - + diff --git a/src/test/java/org/mybatis/spring/sample/config/applicationContext-job.xml b/src/test/resources/org/mybatis/spring/sample/config/applicationContext-job.xml similarity index 71% rename from src/test/java/org/mybatis/spring/sample/config/applicationContext-job.xml rename to src/test/resources/org/mybatis/spring/sample/config/applicationContext-job.xml index 6ae670680d..b6423d874f 100644 --- a/src/test/java/org/mybatis/spring/sample/config/applicationContext-job.xml +++ b/src/test/resources/org/mybatis/spring/sample/config/applicationContext-job.xml @@ -1,19 +1,19 @@ - - + + + org.mybatis.spring.sample.mapper.UserMapper + diff --git a/src/test/resources/org/mybatis/spring/sample/db/database-schema.sql b/src/test/resources/org/mybatis/spring/sample/db/database-schema.sql new file mode 100644 index 0000000000..389bc32c2e --- /dev/null +++ b/src/test/resources/org/mybatis/spring/sample/db/database-schema.sql @@ -0,0 +1,31 @@ +-- +-- Copyright 2010-2024 the original author or authors. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +drop table users if exists; +create table users ( + id varchar(80) not null, + name varchar(80) not null, + constraint pk_user primary key (id) +); + +drop table persons if exists; +create table persons ( + person_id integer identity not null primary key, + first_name varchar(20), + last_name varchar(20), + operation_by varchar(64), + operation_at timestamp +); diff --git a/src/test/resources/org/mybatis/spring/sample/db/database-test-data.sql b/src/test/resources/org/mybatis/spring/sample/db/database-test-data.sql new file mode 100644 index 0000000000..597e8e121d --- /dev/null +++ b/src/test/resources/org/mybatis/spring/sample/db/database-test-data.sql @@ -0,0 +1,21 @@ +-- +-- Copyright 2010-2024 the original author or authors. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +insert into users VALUES ( 'u1', 'Pocoyo' ); +insert into users VALUES ( 'u2', 'Pato' ); +insert into users VALUES ( 'u3', 'Eli' ); +insert into users VALUES ( 'u4', 'Valentina' ); +insert into users VALUES ( 'u5', 'Taro Yamada' ); diff --git a/src/test/resources/org/mybatis/spring/sample/mapper/PersonMapper.xml b/src/test/resources/org/mybatis/spring/sample/mapper/PersonMapper.xml new file mode 100644 index 0000000000..e6677da739 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/sample/mapper/PersonMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/sample/mapper/UserMapper.xml b/src/test/resources/org/mybatis/spring/sample/mapper/UserMapper.xml new file mode 100644 index 0000000000..5d9459e6c2 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/sample/mapper/UserMapper.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/src/test/resources/org/mybatis/spring/submitted/autowire/BarMapper.xml b/src/test/resources/org/mybatis/spring/submitted/autowire/BarMapper.xml new file mode 100644 index 0000000000..793ada7b1c --- /dev/null +++ b/src/test/resources/org/mybatis/spring/submitted/autowire/BarMapper.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/src/test/resources/org/mybatis/spring/submitted/autowire/FooMapper.xml b/src/test/resources/org/mybatis/spring/submitted/autowire/FooMapper.xml new file mode 100644 index 0000000000..44ba73af94 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/submitted/autowire/FooMapper.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/src/test/resources/org/mybatis/spring/submitted/autowire/database-schema-bar.sql b/src/test/resources/org/mybatis/spring/submitted/autowire/database-schema-bar.sql new file mode 100644 index 0000000000..383cc2689f --- /dev/null +++ b/src/test/resources/org/mybatis/spring/submitted/autowire/database-schema-bar.sql @@ -0,0 +1,21 @@ +-- +-- Copyright 2010-2024 the original author or authors. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +create table bar ( + id varchar(80) not null, + name varchar(80) not null, + constraint pk_user primary key (id) +); diff --git a/src/test/resources/org/mybatis/spring/submitted/autowire/database-schema-foo.sql b/src/test/resources/org/mybatis/spring/submitted/autowire/database-schema-foo.sql new file mode 100644 index 0000000000..b625783eff --- /dev/null +++ b/src/test/resources/org/mybatis/spring/submitted/autowire/database-schema-foo.sql @@ -0,0 +1,21 @@ +-- +-- Copyright 2010-2024 the original author or authors. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +create table foo ( + id varchar(80) not null, + name varchar(80) not null, + constraint pk_user primary key (id) +); diff --git a/src/test/java/org/mybatis/spring/submitted/autowire/spring.xml b/src/test/resources/org/mybatis/spring/submitted/autowire/spring.xml similarity index 77% rename from src/test/java/org/mybatis/spring/submitted/autowire/spring.xml rename to src/test/resources/org/mybatis/spring/submitted/autowire/spring.xml index fca573a572..0a9dd607cb 100644 --- a/src/test/java/org/mybatis/spring/submitted/autowire/spring.xml +++ b/src/test/resources/org/mybatis/spring/submitted/autowire/spring.xml @@ -1,33 +1,33 @@ - + - + @@ -35,16 +35,16 @@ - - + + - + - + @@ -63,4 +63,4 @@ - \ No newline at end of file + diff --git a/src/test/resources/org/mybatis/spring/submitted/webapp_placeholder/conf.properties b/src/test/resources/org/mybatis/spring/submitted/webapp_placeholder/conf.properties new file mode 100644 index 0000000000..cecf6c4166 --- /dev/null +++ b/src/test/resources/org/mybatis/spring/submitted/webapp_placeholder/conf.properties @@ -0,0 +1,19 @@ +# +# Copyright 2010-2024 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +basePkg=org.mybatis.spring.submitted.webapp_placeholder + +mybatis.lazy-initialization=true diff --git a/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/spring.xml b/src/test/resources/org/mybatis/spring/submitted/webapp_placeholder/spring.xml similarity index 62% rename from src/test/java/org/mybatis/spring/submitted/webapp_placeholder/spring.xml rename to src/test/resources/org/mybatis/spring/submitted/webapp_placeholder/spring.xml index db457cc8c7..6e341c6772 100644 --- a/src/test/java/org/mybatis/spring/submitted/webapp_placeholder/spring.xml +++ b/src/test/resources/org/mybatis/spring/submitted/webapp_placeholder/spring.xml @@ -1,19 +1,19 @@ + diff --git a/src/test/java/org/mybatis/spring/submitted/xa/applicationContext.xml b/src/test/resources/org/mybatis/spring/submitted/xa/applicationContext.xml similarity index 87% rename from src/test/java/org/mybatis/spring/submitted/xa/applicationContext.xml rename to src/test/resources/org/mybatis/spring/submitted/xa/applicationContext.xml index bccbb53150..28fff979e6 100644 --- a/src/test/java/org/mybatis/spring/submitted/xa/applicationContext.xml +++ b/src/test/resources/org/mybatis/spring/submitted/xa/applicationContext.xml @@ -1,19 +1,19 @@ - - @@ -78,6 +78,7 @@ init-method="init" destroy-method="close"> + 1 @@ -96,6 +97,7 @@ init-method="init" destroy-method="close"> + 1 @@ -146,4 +148,4 @@ - + diff --git a/src/test/resources/org/mybatis/spring/submitted/xa/database-schema.sql b/src/test/resources/org/mybatis/spring/submitted/xa/database-schema.sql new file mode 100644 index 0000000000..a03c83472b --- /dev/null +++ b/src/test/resources/org/mybatis/spring/submitted/xa/database-schema.sql @@ -0,0 +1,20 @@ +-- +-- Copyright 2010-2024 the original author or authors. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +CREATE TABLE USERS ( + id integer not null, + name varchar(80) not null +); diff --git a/travis/after_success.sh b/travis/after_success.sh deleted file mode 100644 index a37744227e..0000000000 --- a/travis/after_success.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -# -# Copyright 2010-2017 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - - -# Get Commit Message -commit_message=$(git log --format=%B -n 1) -echo "Current commit detected: ${commit_message}" - -# We build for several JDKs on Travis. -# Some actions, like analyzing the code (Coveralls) and uploading -# artifacts on a Maven repository, should only be made for one version. - -# If the version is 1.8, then perform the following actions. -# 1. Upload artifacts to Sonatype. -# 2. Use -q option to only display Maven errors and warnings. -# 3. Use --settings to force the usage of our "settings.xml" file. -# 4. Notify Coveralls. -# 5. Deploy site -# 6. Notify Sonar - -if [ $TRAVIS_REPO_SLUG == "mybatis/spring" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" ] && [[ "$commit_message" != *"[maven-release-plugin]"* ]]; then - - if [ $TRAVIS_JDK_VERSION == "oraclejdk8" ]; then - - # Deploy to sonatype - ./mvnw clean deploy -q --settings ./travis/settings.xml - echo -e "Successfully deployed SNAPSHOT artifacts to Sonatype under Travis job ${TRAVIS_JOB_NUMBER}" - - # Deploy to coveralls - ./mvnw clean test jacoco:report coveralls:report -q --settings ./travis/settings.xml - echo -e "Successfully ran coveralls under Travis job ${TRAVIS_JOB_NUMBER}" - - # Deploy to site - # Cannot currently run site this way - # ./mvnw site site:deploy -q - # echo -e "Successfully deploy site under Travis job ${TRAVIS_JOB_NUMBER}" - - # Notify Sonar - ./mvnw clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar -Dsonar.host.url=https://sonarqube.com -Dsonar.login=ccf0be39fd0ca5ea5aa712247c79da7233cd3caa - echo -e "Successfully ran Sonar integration under Travis job ${TRAVIS_JOB_NUMBER}" - else - echo "Java Version does not support additonal activity for travis CI" - fi -else - echo "Travis Pull Request: $TRAVIS_PULL_REQUEST" - echo "Travis Branch: $TRAVIS_BRANCH" - echo "Travis build skipped" -fi diff --git a/travis/settings.xml b/travis/settings.xml deleted file mode 100644 index 44eb340264..0000000000 --- a/travis/settings.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - ossrh - ${env.CI_DEPLOY_USERNAME} - ${env.CI_DEPLOY_PASSWORD} - - - gh-pages - git - ${env.CI_SITE_PASSWORD} - - -