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 extends BeanNameGenerator> 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 extends Annotation> 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 extends MapperFactoryBean> 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 extends Annotation> 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 extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
- scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
+ builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
}
Class extends MapperFactoryBean> 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 extends T> items) {
+ public void write(final Chunk extends T> 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 extends Annotation> markerInterface = (Class extends Annotation>) classLoader.loadClass(annotationClassName);
- scanner.setAnnotationClass(markerInterface);
+ Class extends Annotation> annotationClass = (Class extends Annotation>) 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 extends MapperFactoryBean> mapperFactoryBeanClass = (Class extends MapperFactoryBean>) 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 extends MapperFactoryBean> 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 extends Annotation> 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 extends MapperFactoryBean> 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 extends MapperFactoryBean> 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 extends Annotation> annotationClass) {
this.annotationClass = annotationClass;
@@ -157,25 +191,54 @@ public void setAnnotationClass(Class extends Annotation> 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 extends MapperFactoryBean> 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
+
+ SELECT id, name, job FROM employees ORDER BY id ASC LIMIT #{_skiprows}, #{_pagesize}
+
+```
+
+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
+
+ SELECT id, name, job FROM employees ORDER BY id ASC
+
+```
+
+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
+
+ insert into persons (first_name, last_name, operation_by, operation_at)
+ values(#{item.firstName}, #{item.lastName}, #{operationBy}, #{operationAt})
+
+```
+
+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 ``(`@ResultMap`)
+
+NOTE
+However, It become possible to use it by simultaneously initializing dependent beans using `@DependsOn`(Spring's feature) as follow:
+
+```java
+@DependsOn("vendorMapper")
+public interface GoodsMapper {
+ // ...
+}
+```
+
+Since 2.0.6, the develop become can specified scope of mapper using mapper scanning feature option(`default-scope`) and scope annotation(`@Scope`, `@RefreshScope`, etc ...).
+The motivation for adding this option is supporting the `refresh` scope provided by the Spring Cloud. The default of this option is empty (= equiv to specify the `singleton` scope).
+The `default-scope` apply to the mapper bean(`MapperFactoryBean`) when scope of scanned bean definition is `singleton`(default scope) and create a scoped proxy bean for scanned mapper when final scope is not `singleton`.
+
+### \
+
+El elemento XML ` ` busca mappers de una forma muy similar a cómo ` ` busca beans.
+
+A continuación se muestra un fichero XML de configuración:
+
+```xml
+
+
+
+
+
+
+
+```
+
+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 ` ` creará `MapperFactoryBean`s 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.
+
+` ` 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-](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-scanning-name-generator)).
+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`, `jakarta.inject.Named` (if you have Jakarta EE) 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
+` ` 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 ` `.
+
+La anotación `@MapperScan` se usa de la siguiente forma:
+
+```java
+@Configuration
+@MapperScan("org.mybatis.spring.sample.mapper")
+public class AppConfig {
+ // ...
+}
+```
+
+La anotación fucniona exactamente igual que ` ` 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`.
+
+NOTE
+Since 2.0.4, If `basePackageClasses` or `basePackages` are not defined, scanning will occur from the package of the class that declares this annotation.
+
+### 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:
+
+```xml
+
+
+
+```
+
+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`:
+
+```xml
+
+```
diff --git a/src/site/es/markdown/sample.md b/src/site/es/markdown/sample.md
new file mode 100644
index 0000000000..077d525b77
--- /dev/null
+++ b/src/site/es/markdown/sample.md
@@ -0,0 +1,63 @@
+
+# Sample Code
+
+NOTE
+See [JPetstore 6 demo](https://github.com/mybatis/jpetstore-6) to know about how to use Spring with a full web application server.
+
+You can check out sample code from the MyBatis-Spring [repo](https://github.com/mybatis/spring/tree/master/src/test/java/org/mybatis/spring/sample):
+
+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:
+
+```java
+@Transactional
+public class FooService {
+
+ private final UserMapper userMapper;
+
+ public FooService(UserMapper userMapper) {
+ this.userMapper = userMapper;
+ }
+
+ public User doSomeBusinessStuff(String userId) {
+ return this.userMapper.getUser(userId);
+ }
+
+}
+```
+
+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.
+
+```java
+public interface UserMapper {
+
+ User getUser(String userId);
+
+}
+```
+
+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 test | Description |
+| --- | --- |
+| `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.java` | Shows how to use Spring's `@Configuration` with the `@MapperScann` annotation so mappers are autodiscovered. |
+| `SampleNamespaceTest.java` | 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/markdown/sqlsession.md b/src/site/es/markdown/sqlsession.md
new file mode 100644
index 0000000000..1615d88896
--- /dev/null
+++ b/src/site/es/markdown/sqlsession.md
@@ -0,0 +1,130 @@
+
+# Uso de SqlSession
+
+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.
+
+## SqlSessionTemplate
+
+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.
+
+```xml
+
+
+
+```
+
+```java
+@Configuration
+public class MyBatisConfig {
+ @Bean
+ public SqlSessionTemplate sqlSession() throws Exception {
+ return new SqlSessionTemplate(sqlSessionFactory());
+ }
+}
+```
+
+Este bean puede ser inyectado directamente en tus DAOs. Necesitarás una propiedad `SqlSession` en tu bean como se muestra a continuación:
+
+```java
+public class UserDaoImpl implements UserDao {
+
+ private SqlSession sqlSession;
+
+ public void setSqlSession(SqlSession sqlSession) {
+ this.sqlSession = sqlSession;
+ }
+
+ public User getUser(String userId) {
+ return sqlSession.selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
+ }
+}
+```
+
+E inyectar el `SqlSessionTemplate` de la siguiente forma:
+
+```xml
+
+
+
+```
+
+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:
+
+```xml
+
+
+
+
+```
+
+```java
+@Configuration
+public class MyBatisConfig {
+ @Bean
+ public SqlSessionTemplate sqlSession() throws Exception {
+ return new SqlSessionTemplate(sqlSessionFactory(), ExecutorType.BATCH);
+ }
+}
+```
+
+Ahora todos tus statements se ejecutarán en batch de forma que puedes programar lo siguiente:
+
+```java
+public class UserService {
+ private final SqlSession sqlSession;
+ public UserService(SqlSession sqlSession) {
+ this.sqlSession = sqlSession;
+ }
+ public void insertUsers(List 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 `SqlSessionTemplate`s 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
+
+`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:
+
+```java
+public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
+ public User getUser(String userId) {
+ return getSqlSession().selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
+ }
+}
+```
+
+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:
+
+```xml
+
+
+
+```
diff --git a/src/site/es/markdown/transactions.md b/src/site/es/markdown/transactions.md
new file mode 100644
index 0000000000..54b9dae328
--- /dev/null
+++ b/src/site/es/markdown/transactions.md
@@ -0,0 +1,141 @@
+
+# Transacciones
+
+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.
+
+
+## Configuration Estándar
+
+Para habilitar las transacciones Spring, simplemente crea un `DataSourceTransactionManager` en tu fichero de configuración:
+
+```xml
+
+
+
+```
+
+```java
+@Configuration
+public class DataSourceConfig {
+ @Bean
+ public DataSourceTransactionManager transactionManager() {
+ return new DataSourceTransactionManager(dataSource());
+ }
+}
+```
+
+El `DataSource` especificado puede ser cualquier `DataSource` JDBC que usarías normalmente con Spring.
+Esto incluye connection pools y `DataSource`s 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á.
+
+
+## Container Managed Transactions
+
+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`:
+
+```xml
+
+```
+
+```java
+@Configuration
+public class DataSourceConfig {
+ @Bean
+ public JtaTransactionManager transactionManager() {
+ return new JtaTransactionManagerFactoryBean().getObject();
+ }
+}
+```
+
+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:
+
+```xml
+
+
+
+
+
+
+```
+
+```java
+@Configuration
+public class MyBatisConfig {
+ @Bean
+ public SqlSessionFactory sqlSessionFactory() {
+ SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
+ factoryBean.setDataSource(dataSource());
+ factoryBean.setTransactionFactory(new ManagedTransactionFactory());
+ return factoryBean.getObject();
+ }
+}
+```
+
+
+## Ǵestión programática de transacción
+
+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-)](https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-programmatic).
+Este código muetra como controlar una transacción manualmente usando el `PlatformTransactionManager`.
+
+```java
+public class UserService {
+ private final PlatformTransactionManager transactionManager;
+ public UserService(PlatformTransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+ public void createUser() {
+ TransactionStatus txStatus =
+ transactionManager.getTransaction(new DefaultTransactionDefinition());
+ try {
+ userMapper.insertUser(user);
+ } catch (Exception e) {
+ transactionManager.rollback(txStatus);
+ throw e;
+ }
+ transactionManager.commit(txStatus);
+ }
+}
+```
+
+You can omit to call the `commit` and `rollback` method using the `TransactionTemplate`.
+
+```java
+public class UserService {
+ private final PlatformTransactionManager transactionManager;
+ public UserService(PlatformTransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+ public void createUser() {
+ TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+ transactionTemplate.execute(txStatus -> {
+ userMapper.insertUser(user);
+ return null;
+ });
+ }
+}
+```
+
+Este código usa un mapper pero también funcionaría con `SqlSession.
diff --git a/src/site/es/markdown/using-api.md b/src/site/es/markdown/using-api.md
new file mode 100644
index 0000000000..b9a7b29ad5
--- /dev/null
+++ b/src/site/es/markdown/using-api.md
@@ -0,0 +1,31 @@
+
+# Using the MyBatis API
+
+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.
+
+```java
+public class UserDaoImpl implements UserDao {
+ // SqlSessionFactory would normally be set by SqlSessionDaoSupport
+ private final SqlSessionFactory sqlSessionFactory;
+
+ public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
+ this.sqlSessionFactory = sqlSessionFactory;
+ }
+
+ public User getUser(String userId) {
+ // note standard MyBatis API usage - opening and closing the session manually
+ try (SqlSession session = sqlSessionFactory.openSession()) {
+ return session.selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
+ }
+ }
+}
+```
+
+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 `SqlSession`s are **always** closed in a finally block.
diff --git a/src/site/es/xdoc/batch.xml b/src/site/es/xdoc/batch.xml
deleted file mode 100644
index f5de8c226b..0000000000
--- a/src/site/es/xdoc/batch.xml
+++ /dev/null
@@ -1,344 +0,0 @@
-
-
-
-
-
- MyBatis-Spring | Spring Batch
- Hunter Presnall
- Eduardo Macarron
-
-
-
-
-
- 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 y no a
- sesiones batch de MyBatis. Para obtener información sobre las sesiones batch ve a la sección
- Usnado un SqlSession .
-
-
-
-
- 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:
-
- SELECT id, name, job FROM employees ORDER BY id ASC LIMIT #{_skiprows}, #{_pagesize}
- ]]>
-
- 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
-
-
-
-
-
-
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 test
- Description
-
-
-
-
-
- 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
+
+ SELECT id, name, job FROM employees ORDER BY id ASC LIMIT #{_skiprows}, #{_pagesize}
+
+```
+
+設定例:
+
+```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
+
+ SELECT id, name, job FROM employees ORDER BY id ASC
+
+```
+
+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
+
+ insert into persons (first_name, last_name, operation_by, operation_at)
+ values(#{item.firstName}, #{item.lastName}, #{operationBy}, #{operationAt})
+
+```
+
+さいごに, `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の**フラグメントをインクルードしている場合
+* `