001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jdbc.datasource; 018 019import java.sql.SQLException; 020import java.sql.Savepoint; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024 025import org.springframework.lang.Nullable; 026import org.springframework.transaction.CannotCreateTransactionException; 027import org.springframework.transaction.NestedTransactionNotSupportedException; 028import org.springframework.transaction.SavepointManager; 029import org.springframework.transaction.TransactionException; 030import org.springframework.transaction.TransactionSystemException; 031import org.springframework.transaction.TransactionUsageException; 032import org.springframework.transaction.support.SmartTransactionObject; 033import org.springframework.util.Assert; 034 035/** 036 * Convenient base class for JDBC-aware transaction objects. Can contain a 037 * {@link ConnectionHolder} with a JDBC {@code Connection}, and implements the 038 * {@link SavepointManager} interface based on that {@code ConnectionHolder}. 039 * 040 * <p>Allows for programmatic management of JDBC {@link java.sql.Savepoint Savepoints}. 041 * Spring's {@link org.springframework.transaction.support.DefaultTransactionStatus} 042 * automatically delegates to this, as it autodetects transaction objects which 043 * implement the {@link SavepointManager} interface. 044 * 045 * @author Juergen Hoeller 046 * @since 1.1 047 * @see DataSourceTransactionManager 048 */ 049public abstract class JdbcTransactionObjectSupport implements SavepointManager, SmartTransactionObject { 050 051 private static final Log logger = LogFactory.getLog(JdbcTransactionObjectSupport.class); 052 053 054 @Nullable 055 private ConnectionHolder connectionHolder; 056 057 @Nullable 058 private Integer previousIsolationLevel; 059 060 private boolean readOnly = false; 061 062 private boolean savepointAllowed = false; 063 064 065 /** 066 * Set the ConnectionHolder for this transaction object. 067 */ 068 public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder) { 069 this.connectionHolder = connectionHolder; 070 } 071 072 /** 073 * Return the ConnectionHolder for this transaction object. 074 */ 075 public ConnectionHolder getConnectionHolder() { 076 Assert.state(this.connectionHolder != null, "No ConnectionHolder available"); 077 return this.connectionHolder; 078 } 079 080 /** 081 * Check whether this transaction object has a ConnectionHolder. 082 */ 083 public boolean hasConnectionHolder() { 084 return (this.connectionHolder != null); 085 } 086 087 /** 088 * Set the previous isolation level to retain, if any. 089 */ 090 public void setPreviousIsolationLevel(@Nullable Integer previousIsolationLevel) { 091 this.previousIsolationLevel = previousIsolationLevel; 092 } 093 094 /** 095 * Return the retained previous isolation level, if any. 096 */ 097 @Nullable 098 public Integer getPreviousIsolationLevel() { 099 return this.previousIsolationLevel; 100 } 101 102 /** 103 * Set the read-only status of this transaction. 104 * The default is {@code false}. 105 * @since 5.2.1 106 */ 107 public void setReadOnly(boolean readOnly) { 108 this.readOnly = readOnly; 109 } 110 111 /** 112 * Return the read-only status of this transaction. 113 * @since 5.2.1 114 */ 115 public boolean isReadOnly() { 116 return this.readOnly; 117 } 118 119 /** 120 * Set whether savepoints are allowed within this transaction. 121 * The default is {@code false}. 122 */ 123 public void setSavepointAllowed(boolean savepointAllowed) { 124 this.savepointAllowed = savepointAllowed; 125 } 126 127 /** 128 * Return whether savepoints are allowed within this transaction. 129 */ 130 public boolean isSavepointAllowed() { 131 return this.savepointAllowed; 132 } 133 134 @Override 135 public void flush() { 136 // no-op 137 } 138 139 140 //--------------------------------------------------------------------- 141 // Implementation of SavepointManager 142 //--------------------------------------------------------------------- 143 144 /** 145 * This implementation creates a JDBC 3.0 Savepoint and returns it. 146 * @see java.sql.Connection#setSavepoint 147 */ 148 @Override 149 public Object createSavepoint() throws TransactionException { 150 ConnectionHolder conHolder = getConnectionHolderForSavepoint(); 151 try { 152 if (!conHolder.supportsSavepoints()) { 153 throw new NestedTransactionNotSupportedException( 154 "Cannot create a nested transaction because savepoints are not supported by your JDBC driver"); 155 } 156 if (conHolder.isRollbackOnly()) { 157 throw new CannotCreateTransactionException( 158 "Cannot create savepoint for transaction which is already marked as rollback-only"); 159 } 160 return conHolder.createSavepoint(); 161 } 162 catch (SQLException ex) { 163 throw new CannotCreateTransactionException("Could not create JDBC savepoint", ex); 164 } 165 } 166 167 /** 168 * This implementation rolls back to the given JDBC 3.0 Savepoint. 169 * @see java.sql.Connection#rollback(java.sql.Savepoint) 170 */ 171 @Override 172 public void rollbackToSavepoint(Object savepoint) throws TransactionException { 173 ConnectionHolder conHolder = getConnectionHolderForSavepoint(); 174 try { 175 conHolder.getConnection().rollback((Savepoint) savepoint); 176 conHolder.resetRollbackOnly(); 177 } 178 catch (Throwable ex) { 179 throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex); 180 } 181 } 182 183 /** 184 * This implementation releases the given JDBC 3.0 Savepoint. 185 * @see java.sql.Connection#releaseSavepoint 186 */ 187 @Override 188 public void releaseSavepoint(Object savepoint) throws TransactionException { 189 ConnectionHolder conHolder = getConnectionHolderForSavepoint(); 190 try { 191 conHolder.getConnection().releaseSavepoint((Savepoint) savepoint); 192 } 193 catch (Throwable ex) { 194 logger.debug("Could not explicitly release JDBC savepoint", ex); 195 } 196 } 197 198 protected ConnectionHolder getConnectionHolderForSavepoint() throws TransactionException { 199 if (!isSavepointAllowed()) { 200 throw new NestedTransactionNotSupportedException( 201 "Transaction manager does not allow nested transactions"); 202 } 203 if (!hasConnectionHolder()) { 204 throw new TransactionUsageException( 205 "Cannot create nested transaction when not exposing a JDBC transaction"); 206 } 207 return getConnectionHolder(); 208 } 209 210}