Merge branch '6.0.x'

# Conflicts:
#	spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/R2dbcTransactionManager.java
#	spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/R2dbcTransactionManagerUnitTests.java
This commit is contained in:
Juergen Hoeller 2023-09-11 17:40:11 +02:00
commit 3099710087
10 changed files with 105 additions and 89 deletions

View File

@ -1084,6 +1084,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Let subclasses do some final clean-up if they wish...
onClose();
// Reset common introspection caches to avoid class reference leaks.
resetCommonCaches();
// Reset local application listeners to pre-refresh state.
if (this.earlyApplicationListeners != null) {
this.applicationListeners.clear();

View File

@ -1326,6 +1326,9 @@ public abstract class AnnotationUtils {
public static void clearCache() {
AnnotationTypeMappings.clearCache();
AnnotationsScanner.clearCache();
AttributeMethods.cache.clear();
RepeatableContainers.cache.clear();
OrderUtils.orderCache.clear();
}

View File

@ -39,9 +39,7 @@ final class AttributeMethods {
static final AttributeMethods NONE = new AttributeMethods(null, new Method[0]);
private static final Map<Class<? extends Annotation>, AttributeMethods> cache =
new ConcurrentReferenceHashMap<>();
static final Map<Class<? extends Annotation>, AttributeMethods> cache = new ConcurrentReferenceHashMap<>();
private static final Comparator<Method> methodComparator = (m1, m2) -> {
if (m1 != null && m2 != null) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@ -41,7 +41,7 @@ public abstract class OrderUtils {
private static final String JAVAX_PRIORITY_ANNOTATION = "jakarta.annotation.Priority";
/** Cache for @Order value (or NOT_ANNOTATED marker) per Class. */
private static final Map<AnnotatedElement, Object> orderCache = new ConcurrentReferenceHashMap<>(64);
static final Map<AnnotatedElement, Object> orderCache = new ConcurrentReferenceHashMap<>(64);
/**

View File

@ -43,6 +43,8 @@ import org.springframework.util.ObjectUtils;
*/
public abstract class RepeatableContainers {
static final Map<Class<? extends Annotation>, Object> cache = new ConcurrentReferenceHashMap<>();
@Nullable
private final RepeatableContainers parent;
@ -141,8 +143,6 @@ public abstract class RepeatableContainers {
*/
private static class StandardRepeatableContainers extends RepeatableContainers {
private static final Map<Class<? extends Annotation>, Object> cache = new ConcurrentReferenceHashMap<>();
private static final Object NONE = new Object();
private static final StandardRepeatableContainers INSTANCE = new StandardRepeatableContainers();

View File

@ -77,7 +77,25 @@ public abstract class LogFactory {
*/
@Deprecated
public static LogFactory getFactory() {
return new LogFactory() {};
return new LogFactory() {
@Override
public Object getAttribute(String name) {
return null;
}
@Override
public String[] getAttributeNames() {
return new String[0];
}
@Override
public void removeAttribute(String name) {
}
@Override
public void setAttribute(String name, Object value) {
}
@Override
public void release() {
}
};
}
/**
@ -106,29 +124,19 @@ public abstract class LogFactory {
// Just in case some code happens to call uncommon Commons Logging methods...
@Deprecated
public Object getAttribute(String name) {
return null;
}
public abstract Object getAttribute(String name);
@Deprecated
public String[] getAttributeNames() {
return new String[0];
}
public abstract String[] getAttributeNames();
@Deprecated
public void removeAttribute(String name) {
// do nothing
}
public abstract void removeAttribute(String name);
@Deprecated
public void setAttribute(String name, Object value) {
// do nothing
}
public abstract void setAttribute(String name, Object value);
@Deprecated
public void release() {
// do nothing
}
public abstract void release();
@Deprecated
public static void release(ClassLoader classLoader) {

View File

@ -80,4 +80,8 @@ public class LogFactoryService extends LogFactory {
return this.attributes.keySet().toArray(new String[0]);
}
@Override
public void release() {
}
}

View File

@ -50,10 +50,10 @@ import static org.mockito.Mockito.verify;
*/
public class JdbcTemplateQueryTests {
private Connection connection = mock();
private DataSource dataSource = mock();
private Connection connection = mock();
private Statement statement = mock();
private PreparedStatement preparedStatement = mock();

View File

@ -75,18 +75,18 @@ import static org.mockito.Mockito.verify;
*/
public class JdbcTemplateTests {
private Connection connection = mock();
private DataSource dataSource = mock();
private Connection connection = mock();
private Statement statement = mock();
private PreparedStatement preparedStatement = mock();
private ResultSet resultSet = mock();
private CallableStatement callableStatement = mock();
private ResultSet resultSet = mock();
private JdbcTemplate template = new JdbcTemplate(this.dataSource);
@ -136,9 +136,9 @@ public class JdbcTemplateTests {
given(this.preparedStatement.executeUpdate()).willThrow(sqlException);
Dispatcher d = new Dispatcher(idParam, sql);
assertThatExceptionOfType(UncategorizedSQLException.class).isThrownBy(() ->
this.template.update(d))
.withCause(sqlException);
assertThatExceptionOfType(UncategorizedSQLException.class)
.isThrownBy(() -> this.template.update(d))
.withCause(sqlException);
verify(this.preparedStatement).setInt(1, idParam);
verify(this.preparedStatement).close();
verify(this.connection, atLeastOnce()).close();
@ -371,9 +371,9 @@ public class JdbcTemplateTests {
given(this.statement.executeUpdate(sql)).willThrow(sqlException);
given(this.connection.createStatement()).willReturn(this.statement);
assertThatExceptionOfType(DataAccessException.class).isThrownBy(() ->
this.template.update(sql))
.withCause(sqlException);
assertThatExceptionOfType(DataAccessException.class)
.isThrownBy(() -> this.template.update(sql))
.withCause(sqlException);
verify(this.statement).close();
verify(this.connection, atLeastOnce()).close();
}
@ -700,9 +700,9 @@ public class JdbcTemplateTests {
};
try {
assertThatExceptionOfType(DataAccessException.class).isThrownBy(() ->
this.template.batchUpdate(sql, setter))
.withCause(sqlException);
assertThatExceptionOfType(DataAccessException.class)
.isThrownBy(() -> this.template.batchUpdate(sql, setter))
.withCause(sqlException);
}
finally {
verify(this.preparedStatement, times(2)).addBatch();
@ -804,9 +804,9 @@ public class JdbcTemplateTests {
JdbcTemplate template = new JdbcTemplate(this.dataSource, false);
RowCountCallbackHandler rcch = new RowCountCallbackHandler();
assertThatExceptionOfType(CannotGetJdbcConnectionException.class).isThrownBy(() ->
template.query("SELECT ID, FORENAME FROM CUSTMR WHERE ID < 3", rcch))
.withCause(sqlException);
assertThatExceptionOfType(CannotGetJdbcConnectionException.class)
.isThrownBy(() -> template.query("SELECT ID, FORENAME FROM CUSTMR WHERE ID < 3", rcch))
.withCause(sqlException);
}
@Test
@ -818,9 +818,9 @@ public class JdbcTemplateTests {
this.template.afterPropertiesSet();
RowCountCallbackHandler rcch = new RowCountCallbackHandler();
assertThatExceptionOfType(CannotGetJdbcConnectionException.class).isThrownBy(() ->
this.template.query("SELECT ID, FORENAME FROM CUSTMR WHERE ID < 3", rcch))
.withCause(sqlException);
assertThatExceptionOfType(CannotGetJdbcConnectionException.class)
.isThrownBy(() -> this.template.query("SELECT ID, FORENAME FROM CUSTMR WHERE ID < 3", rcch))
.withCause(sqlException);
}
@Test
@ -858,9 +858,9 @@ public class JdbcTemplateTests {
this.template.afterPropertiesSet();
}
RowCountCallbackHandler rcch = new RowCountCallbackHandler();
assertThatExceptionOfType(CannotGetJdbcConnectionException.class).isThrownBy(() ->
this.template.query("SELECT ID, FORENAME FROM CUSTMR WHERE ID < 3", rcch))
.withCause(sqlException);
assertThatExceptionOfType(CannotGetJdbcConnectionException.class)
.isThrownBy(() -> this.template.query("SELECT ID, FORENAME FROM CUSTMR WHERE ID < 3", rcch))
.withCause(sqlException);
}
@Test
@ -887,9 +887,9 @@ public class JdbcTemplateTests {
given(this.preparedStatement.executeUpdate()).willThrow(sqlException);
PreparedStatementSetter pss = ps -> ps.setString(1, name);
assertThatExceptionOfType(DataAccessException.class).isThrownBy(() ->
new JdbcTemplate(this.dataSource).update(sql, pss))
.withCause(sqlException);
assertThatExceptionOfType(DataAccessException.class)
.isThrownBy(() -> new JdbcTemplate(this.dataSource).update(sql, pss))
.withCause(sqlException);
verify(this.preparedStatement).setString(1, name);
verify(this.preparedStatement).close();
verify(this.connection, atLeastOnce()).close();
@ -925,9 +925,9 @@ public class JdbcTemplateTests {
t.setIgnoreWarnings(false);
ResultSetExtractor<Byte> extractor = rs -> rs.getByte(1);
assertThatExceptionOfType(SQLWarningException.class).isThrownBy(() ->
t.query(sql, extractor))
.withCause(warnings);
assertThatExceptionOfType(SQLWarningException.class)
.isThrownBy(() -> t.query(sql, extractor))
.withCause(warnings);
verify(this.resultSet).close();
verify(this.preparedStatement).close();
verify(this.connection).close();
@ -968,7 +968,7 @@ public class JdbcTemplateTests {
this.template.query(sql, (RowCallbackHandler) rs -> {
throw sqlException;
}))
.withCause(sqlException);
.withCause(sqlException);
verify(this.resultSet).close();
verify(this.preparedStatement).close();
verify(this.connection, atLeastOnce()).close();
@ -988,7 +988,7 @@ public class JdbcTemplateTests {
this.template.query(sql, (RowCallbackHandler) rs -> {
throw sqlException;
}))
.withCause(sqlException);
.withCause(sqlException);
verify(this.resultSet).close();
verify(this.preparedStatement).close();
verify(this.connection).close();
@ -1018,7 +1018,7 @@ public class JdbcTemplateTests {
template.query(sql, (RowCallbackHandler) rs -> {
throw sqlException;
}))
.withCause(sqlException);
.withCause(sqlException);
verify(this.resultSet).close();
verify(this.preparedStatement).close();
verify(this.connection).close();

View File

@ -338,8 +338,8 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) transaction;
if (txObject.hasSavepoint()) {
// Just release the savepoint
return Mono.defer(txObject::releaseSavepoint);
// Just release the savepoint, keeping the transactional connection.
return txObject.releaseSavepoint();
}
// Remove the connection holder from the context, if exposed.
@ -348,30 +348,25 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
}
// Reset connection.
Connection con = txObject.getConnectionHolder().getConnection();
Mono<Void> afterCleanup = Mono.empty();
Mono<Void> releaseConnectionStep = Mono.defer(() -> {
try {
if (txObject.isNewConnectionHolder()) {
if (logger.isDebugEnabled()) {
logger.debug("Releasing R2DBC Connection [" + con + "] after transaction");
}
Mono<Void> releaseMono = ConnectionFactoryUtils.releaseConnection(con, obtainConnectionFactory());
if (logger.isDebugEnabled()) {
releaseMono = releaseMono.doOnError(
ex -> logger.debug(String.format("Error ignored during cleanup: %s", ex)));
}
return releaseMono.onErrorComplete();
try {
if (txObject.isNewConnectionHolder()) {
Connection con = txObject.getConnectionHolder().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Releasing R2DBC Connection [" + con + "] after transaction");
}
Mono<Void> releaseMono = ConnectionFactoryUtils.releaseConnection(con, obtainConnectionFactory());
if (logger.isDebugEnabled()) {
releaseMono = releaseMono.doOnError(
ex -> logger.debug(String.format("Error ignored during cleanup: %s", ex)));
}
return releaseMono.onErrorComplete();
}
finally {
txObject.getConnectionHolder().clear();
}
return Mono.empty();
});
return afterCleanup.then(releaseConnectionStep);
}
finally {
txObject.getConnectionHolder().clear();
}
return Mono.empty();
});
}
@ -517,30 +512,35 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
}
public boolean hasSavepoint() {
return this.savepointName != null;
return (this.savepointName != null);
}
public Mono<Void> createSavepoint() {
ConnectionHolder holder = getConnectionHolder();
this.savepointName = holder.nextSavepoint();
return Mono.from(holder.getConnection().createSavepoint(this.savepointName));
String currentSavepoint = holder.nextSavepoint();
this.savepointName = currentSavepoint;
return Mono.from(holder.getConnection().createSavepoint(currentSavepoint));
}
public Mono<Void> releaseSavepoint() {
String currentSavepointName = this.savepointName;
String currentSavepoint = this.savepointName;
if (currentSavepoint == null) {
return Mono.empty();
}
this.savepointName = null;
return Mono.from(getConnectionHolder().getConnection().releaseSavepoint(currentSavepointName));
return Mono.from(getConnectionHolder().getConnection().releaseSavepoint(currentSavepoint));
}
public Mono<Void> commit() {
Connection connection = getConnectionHolder().getConnection();
return (this.savepointName != null ? Mono.empty() : Mono.from(connection.commitTransaction()));
return (hasSavepoint() ? Mono.empty() :
Mono.from(getConnectionHolder().getConnection().commitTransaction()));
}
public Mono<Void> rollback() {
Connection connection = getConnectionHolder().getConnection();
return (this.savepointName != null ?
Mono.from(connection.rollbackTransactionToSavepoint(this.savepointName)) :
String currentSavepoint = this.savepointName;
return (currentSavepoint != null ?
Mono.from(connection.rollbackTransactionToSavepoint(currentSavepoint)) :
Mono.from(connection.rollbackTransaction()));
}