Introduce JdbcTransactionManager with SQLExceptionTranslator support
Closes gh-24064
This commit is contained in:
@@ -98,6 +98,10 @@ import org.springframework.util.Assert;
|
||||
* setup analogous to {@code JtaTransactionManager}, in particular with respect to
|
||||
* lazily registered ORM resources (e.g. a Hibernate {@code Session}).
|
||||
*
|
||||
* <p><b>NOTE: As of 5.3, {@link org.springframework.jdbc.support.JdbcTransactionManager}
|
||||
* is available as an extended subclass which includes commit/rollback exception
|
||||
* translation, aligned with {@link org.springframework.jdbc.core.JdbcTemplate}.</b>
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 02.05.2003
|
||||
* @see #setNestedTransactionAllowed
|
||||
@@ -332,7 +336,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
con.commit();
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
|
||||
throw translateException("JDBC commit", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +351,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
con.rollback();
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
|
||||
throw translateException("JDBC rollback", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,6 +422,22 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the given JDBC commit/rollback exception to a common Spring
|
||||
* exception to propagate from the {@link #commit}/{@link #rollback} call.
|
||||
* <p>The default implementation throws a {@link TransactionSystemException}.
|
||||
* Subclasses may specifically identify concurrency failures etc.
|
||||
* @param task the task description (commit or rollback)
|
||||
* @param ex the SQLException thrown from commit/rollback
|
||||
* @return the translated exception to throw, either a
|
||||
* {@link org.springframework.dao.DataAccessException} or a
|
||||
* {@link org.springframework.transaction.TransactionException}
|
||||
* @since 5.3
|
||||
*/
|
||||
protected RuntimeException translateException(String task, SQLException ex) {
|
||||
return new TransactionSystemException(task + " failed", ex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DataSource transaction object, representing a ConnectionHolder.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -22,8 +22,6 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.UncategorizedSQLException;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -65,7 +63,7 @@ public abstract class AbstractFallbackSQLExceptionTranslator implements SQLExcep
|
||||
* {@link #getFallbackTranslator() fallback translator} if necessary.
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
@Nullable
|
||||
public DataAccessException translate(String task, @Nullable String sql, SQLException ex) {
|
||||
Assert.notNull(ex, "Cannot translate a null SQLException");
|
||||
|
||||
@@ -78,15 +76,10 @@ public abstract class AbstractFallbackSQLExceptionTranslator implements SQLExcep
|
||||
// Looking for a fallback...
|
||||
SQLExceptionTranslator fallback = getFallbackTranslator();
|
||||
if (fallback != null) {
|
||||
dae = fallback.translate(task, sql, ex);
|
||||
if (dae != null) {
|
||||
// Fallback exception match found.
|
||||
return dae;
|
||||
}
|
||||
return fallback.translate(task, sql, ex);
|
||||
}
|
||||
|
||||
// We couldn't identify it more precisely.
|
||||
return new UncategorizedSQLException(task, sql, ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jdbc.support;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link JdbcAccessor}-aligned subclass of the plain {@link DataSourceTransactionManager},
|
||||
* adding common JDBC exception translation for the commit and rollback step.
|
||||
* Typically used in combination with {@link org.springframework.jdbc.core.JdbcTemplate}
|
||||
* which applies the same {@link SQLExceptionTranslator} infrastructure by default.
|
||||
*
|
||||
* <p>Exception translation is specifically relevant for commit steps in serializable
|
||||
* transactions (e.g. on Postgres) where concurrency failures may occur late on commit.
|
||||
* This allows for throwing {@link org.springframework.dao.ConcurrencyFailureException} to
|
||||
* callers instead of {@link org.springframework.transaction.TransactionSystemException}.
|
||||
*
|
||||
* <p>Analogous to {@code HibernateTransactionManager} and {@code JpaTransactionManager},
|
||||
* this transaction manager may throw {@link DataAccessException} from {@link #commit}
|
||||
* and possibly also from {@link #rollback}. Calling code should be prepared for handling
|
||||
* such exceptions next to {@link org.springframework.transaction.TransactionException},
|
||||
* which is generally sensible since {@code TransactionSynchronization} implementations
|
||||
* may also throw such exceptions in their {@code flush} and {@code beforeCommit} phases.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.3
|
||||
* @see DataSourceTransactionManager
|
||||
* @see #setDataSource
|
||||
* @see #setExceptionTranslator
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class JdbcTransactionManager extends DataSourceTransactionManager {
|
||||
|
||||
@Nullable
|
||||
private volatile SQLExceptionTranslator exceptionTranslator;
|
||||
|
||||
private boolean lazyInit = true;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new JdbcTransactionManager instance.
|
||||
* A DataSource has to be set to be able to use it.
|
||||
* @see #setDataSource
|
||||
*/
|
||||
public JdbcTransactionManager() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JdbcTransactionManager instance.
|
||||
* @param dataSource the JDBC DataSource to manage transactions for
|
||||
*/
|
||||
public JdbcTransactionManager(DataSource dataSource) {
|
||||
this();
|
||||
setDataSource(dataSource);
|
||||
afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify the database product name for the DataSource that this transaction manager
|
||||
* uses. This allows to initialize an SQLErrorCodeSQLExceptionTranslator without
|
||||
* obtaining a Connection from the DataSource to get the meta-data.
|
||||
* @param dbName the database product name that identifies the error codes entry
|
||||
* @see JdbcAccessor#setDatabaseProductName
|
||||
* @see SQLErrorCodeSQLExceptionTranslator#setDatabaseProductName
|
||||
* @see java.sql.DatabaseMetaData#getDatabaseProductName()
|
||||
*/
|
||||
public void setDatabaseProductName(String dbName) {
|
||||
this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dbName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the exception translator for this instance.
|
||||
* <p>If no custom translator is provided, a default
|
||||
* {@link SQLErrorCodeSQLExceptionTranslator} is used
|
||||
* which examines the SQLException's vendor-specific error code.
|
||||
* @see JdbcAccessor#setExceptionTranslator
|
||||
* @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
|
||||
*/
|
||||
public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {
|
||||
this.exceptionTranslator = exceptionTranslator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the exception translator for this instance.
|
||||
* <p>Creates a default {@link SQLErrorCodeSQLExceptionTranslator}
|
||||
* for the specified DataSource if none set.
|
||||
* @see #getDataSource()
|
||||
*/
|
||||
public SQLExceptionTranslator getExceptionTranslator() {
|
||||
SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;
|
||||
if (exceptionTranslator != null) {
|
||||
return exceptionTranslator;
|
||||
}
|
||||
synchronized (this) {
|
||||
exceptionTranslator = this.exceptionTranslator;
|
||||
if (exceptionTranslator == null) {
|
||||
exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(obtainDataSource());
|
||||
this.exceptionTranslator = exceptionTranslator;
|
||||
}
|
||||
return exceptionTranslator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to lazily initialize the SQLExceptionTranslator for this transaction manager,
|
||||
* on first encounter of an SQLException. Default is "true"; can be switched to
|
||||
* "false" for initialization on startup.
|
||||
* <p>Early initialization just applies if {@code afterPropertiesSet()} is called.
|
||||
* @see #getExceptionTranslator()
|
||||
* @see #afterPropertiesSet()
|
||||
*/
|
||||
public void setLazyInit(boolean lazyInit) {
|
||||
this.lazyInit = lazyInit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether to lazily initialize the SQLExceptionTranslator for this transaction manager.
|
||||
* @see #getExceptionTranslator()
|
||||
*/
|
||||
public boolean isLazyInit() {
|
||||
return this.lazyInit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eagerly initialize the exception translator, if demanded,
|
||||
* creating a default one for the specified DataSource if none set.
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
if (!isLazyInit()) {
|
||||
getExceptionTranslator();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This implementation attempts to use the {@link SQLExceptionTranslator},
|
||||
* falling back to a {@link org.springframework.transaction.TransactionSystemException}.
|
||||
* @see #getExceptionTranslator()
|
||||
* @see DataSourceTransactionManager#translateException
|
||||
*/
|
||||
@Override
|
||||
protected RuntimeException translateException(String task, SQLException ex) {
|
||||
DataAccessException dae = getExceptionTranslator().translate(task, null, ex);
|
||||
if (dae != null) {
|
||||
return dae;
|
||||
}
|
||||
return super.translateException(task, ex);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -49,9 +49,7 @@ public interface SQLExceptionTranslator {
|
||||
* @param sql the SQL query or update that caused the problem (if known)
|
||||
* @param ex the offending {@code SQLException}
|
||||
* @return the DataAccessException wrapping the {@code SQLException},
|
||||
* or {@code null} if no translation could be applied
|
||||
* (in a custom translator; the default translators always throw an
|
||||
* {@link org.springframework.jdbc.UncategorizedSQLException} in such a case)
|
||||
* or {@code null} if no specific translation could be applied
|
||||
* @see org.springframework.dao.DataAccessException#getRootCause()
|
||||
*/
|
||||
@Nullable
|
||||
|
||||
Reference in New Issue
Block a user