Polish "Prevent further configuration once SqlCall is compiled"

See gh-33729
This commit is contained in:
Stéphane Nicoll 2025-02-04 15:43:24 +01:00
parent 8810913f30
commit 35dea59ce4
2 changed files with 40 additions and 78 deletions

View File

@ -248,16 +248,18 @@ public abstract class AbstractJdbcCall {
* @param parameter the {@link SqlParameter} to add * @param parameter the {@link SqlParameter} to add
*/ */
public void addDeclaredParameter(SqlParameter parameter) { public void addDeclaredParameter(SqlParameter parameter) {
if(!isCompiled()) { if (isCompiled()) {
Assert.notNull(parameter, "The supplied parameter must not be null"); throw new IllegalStateException("SqlCall for " + (isFunction() ? "function" : "procedure") +
if (!StringUtils.hasText(parameter.getName())) { " is already compiled");
throw new InvalidDataAccessApiUsageException( }
"You must specify a parameter name when declaring parameters for \"" + getProcedureName() + "\""); Assert.notNull(parameter, "The supplied parameter must not be null");
} if (!StringUtils.hasText(parameter.getName())) {
this.declaredParameters.add(parameter); throw new InvalidDataAccessApiUsageException(
if (logger.isDebugEnabled()) { "You must specify a parameter name when declaring parameters for \"" + getProcedureName() + "\"");
logger.debug("Added declared parameter for [" + getProcedureName() + "]: " + parameter.getName()); }
} this.declaredParameters.add(parameter);
if (logger.isDebugEnabled()) {
logger.debug("Added declared parameter for [" + getProcedureName() + "]: " + parameter.getName());
} }
} }
@ -267,11 +269,13 @@ public abstract class AbstractJdbcCall {
* @param rowMapper the RowMapper implementation to use * @param rowMapper the RowMapper implementation to use
*/ */
public void addDeclaredRowMapper(String parameterName, RowMapper<?> rowMapper) { public void addDeclaredRowMapper(String parameterName, RowMapper<?> rowMapper) {
if(!isCompiled()) { if (isCompiled()) {
this.declaredRowMappers.put(parameterName, rowMapper); throw new IllegalStateException("SqlCall for " + (isFunction() ? "function" : "procedure") +
if (logger.isDebugEnabled()) { " is already compiled");
logger.debug("Added row mapper for [" + getProcedureName() + "]: " + parameterName); }
} this.declaredRowMappers.put(parameterName, rowMapper);
if (logger.isDebugEnabled()) {
logger.debug("Added row mapper for [" + getProcedureName() + "]: " + parameterName);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,18 +16,16 @@
package org.springframework.jdbc.core.simple; package org.springframework.jdbc.core.simple;
import java.lang.reflect.Field;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DatabaseMetaData; import java.sql.DatabaseMetaData;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -40,6 +38,7 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -364,78 +363,37 @@ class SimpleJdbcCallTests {
verifyStatement(adder, "{call ADD_INVOICE(@AMOUNT = ?, @CUSTID = ?)}"); verifyStatement(adder, "{call ADD_INVOICE(@AMOUNT = ?, @CUSTID = ?)}");
} }
/**
* This test verifies that when declaring a parameter for a SimpleJdbcCall,
* then the parameter is added as expected.
*/
@SuppressWarnings("unchecked")
@Test @Test
void verifyUncompiledDeclareParameterIsAdded() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { void declareParametersCannotBeInvokedWhenCompiled() {
SimpleJdbcCall call = new SimpleJdbcCall(dataSource)
.withProcedureName("procedure_name")
.declareParameters(new SqlParameter("PARAM", Types.VARCHAR));
Field params = AbstractJdbcCall.class.getDeclaredField("declaredParameters");
params.setAccessible(true);
List<SqlParameter> paramList = (List<SqlParameter>) params.get(call);
assertThat(paramList).hasSize(1).allMatch(sqlParam -> sqlParam.getName().equals("PARAM"));
}
/**
* This verifies that once the SimpleJdbcCall is compiled, then adding
* a parameter is ignored
*/
@SuppressWarnings("unchecked")
@Test
void verifyWhenCompiledThenDeclareParameterIsIgnored() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
SimpleJdbcCall call = new SimpleJdbcCall(dataSource) SimpleJdbcCall call = new SimpleJdbcCall(dataSource)
.withProcedureName("procedure_name") .withProcedureName("procedure_name")
.declareParameters(new SqlParameter("PARAM", Types.VARCHAR)); .declareParameters(new SqlParameter("PARAM", Types.VARCHAR));
call.compile(); call.compile();
assertThatIllegalStateException()
call.declareParameters(new SqlParameter("Ignored Param", Types.VARCHAR)); .isThrownBy(() -> call.declareParameters(new SqlParameter("Ignored Param", Types.VARCHAR)))
.withMessage("SqlCall for procedure is already compiled");
Field params = AbstractJdbcCall.class.getDeclaredField("declaredParameters");
params.setAccessible(true);
List<SqlParameter> paramList = (List<SqlParameter>) params.get(call);
assertThat(paramList).hasSize(1).allMatch(sqlParam -> sqlParam.getName().equals("PARAM"));
} }
/**
* When adding a declared row mapper, this verifies that the declaredRowMappers
* gets the new mapper
*/
@SuppressWarnings("unchecked")
@Test @Test
void verifyUncompiledDeclareRowMapperIsAdded() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { void addDeclaredRowMapperCanBeConfigured() {
SimpleJdbcCall call = new SimpleJdbcCall(dataSource) SimpleJdbcCall call = new SimpleJdbcCall(dataSource)
.withProcedureName("procedure_name") .withProcedureName("procedure_name")
.returningResultSet("result_set", (rs,i) -> new Object()); .returningResultSet("result_set", (rs, i) -> new Object());
Field rowMappers = AbstractJdbcCall.class.getDeclaredField("declaredRowMappers"); assertThat(call).extracting("declaredRowMappers")
rowMappers.setAccessible(true); .asInstanceOf(InstanceOfAssertFactories.map(String.class, RowMapper.class))
Map<String, RowMapper<?>> mappers = (Map<String, RowMapper<?>>) rowMappers.get(call); .containsOnlyKeys("result_set");
assertThat(mappers).hasSize(1).allSatisfy((key,value) -> key.equals("result_set"));
} }
/**
* This verifies that when adding a row mapper after the call is compiled
* then the request is ignored
*/
@SuppressWarnings("unchecked")
@Test @Test
void verifyWhenCompiledThenDeclareRowMapperIsIgnored() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { void addDeclaredRowMapperWhenCompiled() {
SimpleJdbcCall call = new SimpleJdbcCall(dataSource) SimpleJdbcCall call = new SimpleJdbcCall(dataSource)
.withProcedureName("procedure_name") .withProcedureName("procedure_name")
.returningResultSet("result_set", (rs,i) -> new Object()); .returningResultSet("result_set", (rs, i) -> new Object());
call.compile(); call.compile();
assertThatIllegalStateException()
call.returningResultSet("not added", (rs,i) -> new Object()); .isThrownBy(() -> call.returningResultSet("not added", (rs, i) -> new Object()))
.withMessage("SqlCall for procedure is already compiled");
Field rowMappers = AbstractJdbcCall.class.getDeclaredField("declaredRowMappers");
rowMappers.setAccessible(true);
Map<String, RowMapper<?>> mappers = (Map<String, RowMapper<?>>) rowMappers.get(call);
assertThat(mappers).hasSize(1).allSatisfy((key,value) -> key.equals("result_set"));
} }
} }