View Javadoc
1   /*
2    * Copyright 2010-2022 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.mybatis.spring.transaction;
17  
18  import static org.springframework.util.Assert.notNull;
19  
20  import java.sql.Connection;
21  import java.sql.SQLException;
22  
23  import javax.sql.DataSource;
24  
25  import org.apache.ibatis.transaction.Transaction;
26  import org.mybatis.logging.Logger;
27  import org.mybatis.logging.LoggerFactory;
28  import org.springframework.jdbc.datasource.ConnectionHolder;
29  import org.springframework.jdbc.datasource.DataSourceUtils;
30  import org.springframework.transaction.support.TransactionSynchronizationManager;
31  
32  /**
33   * {@code SpringManagedTransaction} handles the lifecycle of a JDBC connection. It retrieves a connection from Spring's
34   * transaction manager and returns it back to it when it is no longer needed.
35   * <p>
36   * If Spring's transaction handling is active it will no-op all commit/rollback/close calls assuming that the Spring
37   * transaction manager will do the job.
38   * <p>
39   * If it is not it will behave like {@code JdbcTransaction}.
40   *
41   * @author Hunter Presnall
42   * @author Eduardo Macarron
43   */
44  public class SpringManagedTransaction implements Transaction {
45  
46    private static final Logger LOGGER = LoggerFactory.getLogger(SpringManagedTransaction.class);
47  
48    private final DataSource dataSource;
49  
50    private Connection connection;
51  
52    private boolean isConnectionTransactional;
53  
54    private boolean autoCommit;
55  
56    public SpringManagedTransaction(DataSource dataSource) {
57      notNull(dataSource, "No DataSource specified");
58      this.dataSource = dataSource;
59    }
60  
61    /**
62     * {@inheritDoc}
63     */
64    @Override
65    public Connection getConnection() throws SQLException {
66      if (this.connection == null) {
67        openConnection();
68      }
69      return this.connection;
70    }
71  
72    /**
73     * Gets a connection from Spring transaction manager and discovers if this {@code Transaction} should manage
74     * connection or let it to Spring.
75     * <p>
76     * It also reads autocommit setting because when using Spring Transaction MyBatis thinks that autocommit is always
77     * false and will always call commit/rollback so we need to no-op that calls.
78     */
79    private void openConnection() throws SQLException {
80      this.connection = DataSourceUtils.getConnection(this.dataSource);
81      this.autoCommit = this.connection.getAutoCommit();
82      this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
83  
84      LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
85          + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
86    }
87  
88    /**
89     * {@inheritDoc}
90     */
91    @Override
92    public void commit() throws SQLException {
93      if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
94        LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]");
95        this.connection.commit();
96      }
97    }
98  
99    /**
100    * {@inheritDoc}
101    */
102   @Override
103   public void rollback() throws SQLException {
104     if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
105       LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]");
106       this.connection.rollback();
107     }
108   }
109 
110   /**
111    * {@inheritDoc}
112    */
113   @Override
114   public void close() throws SQLException {
115     DataSourceUtils.releaseConnection(this.connection, this.dataSource);
116   }
117 
118   /**
119    * {@inheritDoc}
120    */
121   @Override
122   public Integer getTimeout() throws SQLException {
123     ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
124     if (holder != null && holder.hasTimeout()) {
125       return holder.getTimeToLiveInSeconds();
126     }
127     return null;
128   }
129 
130 }