SpringManagedTransaction.java

  1. /*
  2.  * Copyright 2010-2024 the original author or authors.
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *    https://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16. package org.mybatis.spring.transaction;

  17. import static org.springframework.util.Assert.notNull;

  18. import java.sql.Connection;
  19. import java.sql.SQLException;

  20. import javax.sql.DataSource;

  21. import org.apache.ibatis.transaction.Transaction;
  22. import org.mybatis.logging.Logger;
  23. import org.mybatis.logging.LoggerFactory;
  24. import org.springframework.jdbc.datasource.ConnectionHolder;
  25. import org.springframework.jdbc.datasource.DataSourceUtils;
  26. import org.springframework.transaction.support.TransactionSynchronizationManager;

  27. /**
  28.  * {@code SpringManagedTransaction} handles the lifecycle of a JDBC connection. It retrieves a connection from Spring's
  29.  * transaction manager and returns it back to it when it is no longer needed.
  30.  * <p>
  31.  * If Spring's transaction handling is active it will no-op all commit/rollback/close calls assuming that the Spring
  32.  * transaction manager will do the job.
  33.  * <p>
  34.  * If it is not it will behave like {@code JdbcTransaction}.
  35.  *
  36.  * @author Hunter Presnall
  37.  * @author Eduardo Macarron
  38.  */
  39. public class SpringManagedTransaction implements Transaction {

  40.   private static final Logger LOGGER = LoggerFactory.getLogger(SpringManagedTransaction.class);

  41.   private final DataSource dataSource;

  42.   private Connection connection;

  43.   private boolean isConnectionTransactional;

  44.   private boolean autoCommit;

  45.   public SpringManagedTransaction(DataSource dataSource) {
  46.     notNull(dataSource, "No DataSource specified");
  47.     this.dataSource = dataSource;
  48.   }

  49.   @Override
  50.   public Connection getConnection() throws SQLException {
  51.     if (this.connection == null) {
  52.       openConnection();
  53.     }
  54.     return this.connection;
  55.   }

  56.   /**
  57.    * Gets a connection from Spring transaction manager and discovers if this {@code Transaction} should manage
  58.    * connection or let it to Spring.
  59.    * <p>
  60.    * It also reads autocommit setting because when using Spring Transaction MyBatis thinks that autocommit is always
  61.    * false and will always call commit/rollback so we need to no-op that calls.
  62.    */
  63.   private void openConnection() throws SQLException {
  64.     this.connection = DataSourceUtils.getConnection(this.dataSource);
  65.     this.autoCommit = this.connection.getAutoCommit();
  66.     this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

  67.     LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
  68.         + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
  69.   }

  70.   @Override
  71.   public void commit() throws SQLException {
  72.     if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
  73.       LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]");
  74.       this.connection.commit();
  75.     }
  76.   }

  77.   @Override
  78.   public void rollback() throws SQLException {
  79.     if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
  80.       LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]");
  81.       this.connection.rollback();
  82.     }
  83.   }

  84.   @Override
  85.   public void close() throws SQLException {
  86.     DataSourceUtils.releaseConnection(this.connection, this.dataSource);
  87.   }

  88.   @Override
  89.   public Integer getTimeout() throws SQLException {
  90.     var holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  91.     if (holder != null && holder.hasTimeout()) {
  92.       return holder.getTimeToLiveInSeconds();
  93.     }
  94.     return null;
  95.   }

  96. }