diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java index 1b4f69c8cc..3244f01e19 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java @@ -23,14 +23,15 @@ import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Map; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.core.Constants; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Proxy for a target DataSource, fetching actual JDBC Connections lazily, @@ -75,13 +76,22 @@ import org.springframework.lang.Nullable; * to retrieve the native JDBC Connection. * * @author Juergen Hoeller + * @author Sam Brannen * @since 1.1.4 * @see DataSourceTransactionManager */ public class LazyConnectionDataSourceProxy extends DelegatingDataSource { - /** Constants instance for TransactionDefinition. */ - private static final Constants constants = new Constants(Connection.class); + /** + * Map of constant names to constant values for the isolation constants + * defined in {@link java.sql.Connection}. + */ + static final Map constants = Map.of( + "TRANSACTION_READ_UNCOMMITTED", Connection.TRANSACTION_READ_UNCOMMITTED, + "TRANSACTION_READ_COMMITTED", Connection.TRANSACTION_READ_COMMITTED, + "TRANSACTION_REPEATABLE_READ", Connection.TRANSACTION_REPEATABLE_READ, + "TRANSACTION_SERIALIZABLE", Connection.TRANSACTION_SERIALIZABLE + ); private static final Log logger = LogFactory.getLog(LazyConnectionDataSourceProxy.class); @@ -133,7 +143,10 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource { * @see java.sql.Connection#TRANSACTION_SERIALIZABLE */ public void setDefaultTransactionIsolationName(String constantName) { - setDefaultTransactionIsolation(constants.asNumber(constantName).intValue()); + Assert.hasText(constantName, "'constantName' must not be null or blank"); + Integer defaultTransactionIsolation = constants.get(constantName); + Assert.notNull(defaultTransactionIsolation, "Only transaction isolation constants allowed"); + this.defaultTransactionIsolation = defaultTransactionIsolation; } /** diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxyTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxyTests.java new file mode 100644 index 0000000000..7159752118 --- /dev/null +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxyTests.java @@ -0,0 +1,98 @@ +/* + * 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. + * 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.datasource; + +import java.lang.reflect.Field; +import java.sql.Connection; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +import org.springframework.util.ReflectionUtils; + +import static java.sql.Connection.TRANSACTION_NONE; +import static java.sql.Connection.TRANSACTION_READ_COMMITTED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link LazyConnectionDataSourceProxy}. + * + * @author Sam Brannen + * @since 6.1 + */ +class LazyConnectionDataSourceProxyTests { + + private final LazyConnectionDataSourceProxy proxy = new LazyConnectionDataSourceProxy(); + + + @Test + void setDefaultTransactionIsolationNameToUnsupportedValues() { + assertThatIllegalArgumentException().isThrownBy(() -> proxy.setDefaultTransactionIsolationName(null)); + assertThatIllegalArgumentException().isThrownBy(() -> proxy.setDefaultTransactionIsolationName(" ")); + assertThatIllegalArgumentException().isThrownBy(() -> proxy.setDefaultTransactionIsolationName("bogus")); + } + + /** + * Verify that the internal 'constants' map is properly configured for all + * TRANSACTION_ constants defined in {@link java.sql.Connection}. + */ + @Test + void setDefaultTransactionIsolationNameToAllSupportedValues() { + Set uniqueValues = new HashSet<>(); + streamIsolationConstants() + .forEach(name -> { + if ("TRANSACTION_NONE".equals(name)) { + assertThatIllegalArgumentException().isThrownBy(() -> proxy.setDefaultTransactionIsolationName(name)); + } + else { + proxy.setDefaultTransactionIsolationName(name); + Integer defaultTransactionIsolation = proxy.defaultTransactionIsolation(); + Integer expected = LazyConnectionDataSourceProxy.constants.get(name); + assertThat(defaultTransactionIsolation).isEqualTo(expected); + uniqueValues.add(defaultTransactionIsolation); + } + }); + assertThat(uniqueValues).containsExactlyInAnyOrderElementsOf(LazyConnectionDataSourceProxy.constants.values()); + } + + @Test + void setDefaultTransactionIsolation() { + // unsupported values are not checked: + proxy.setDefaultTransactionIsolation(-999); + assertThat(proxy.defaultTransactionIsolation()).isEqualTo(-999); + + // TRANSACTION_NONE should not be supported, but we currently do not check that. + proxy.setDefaultTransactionIsolation(TRANSACTION_NONE); + assertThat(proxy.defaultTransactionIsolation()).isEqualTo(TRANSACTION_NONE); + + proxy.setDefaultTransactionIsolation(TRANSACTION_READ_COMMITTED); + assertThat(proxy.defaultTransactionIsolation()).isEqualTo(TRANSACTION_READ_COMMITTED); + } + + + private static Stream streamIsolationConstants() { + return Arrays.stream(Connection.class.getFields()) + .filter(ReflectionUtils::isPublicStaticFinal) + .map(Field::getName) + .filter(name -> name.startsWith("TRANSACTION_")); + } + +}