or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

channel-message-store.mdinbound-polling.mdindex.mdjava-dsl.mdlock-registry.mdmessage-store.mdmetadata-store.mdoutbound-gateway.mdoutbound-updates.mdparameter-factories.mdpostgres-channels.mdstored-procedures.md
tile.json

stored-procedures.mddocs/

Stored Procedures

Stored procedure support enables applications to execute database stored procedures and functions from Spring Integration flows. This is useful for encapsulating complex database logic, leveraging existing database code, and performing operations that require database-side processing.

Key Information for Agents

Required Dependencies:

  • spring-integration-jdbc (this package)
  • spring-integration-core is required
  • DataSource bean must be configured
  • BeanFactory required for ExpressionEvaluatingSqlParameterSourceFactory (for bean references in expressions)

Default Behaviors:

  • ignoreColumnMetaData=false (uses JDBC metadata lookup)
  • isFunction=false (executes as procedure, not function)
  • returnValueRequired=false (function return value not required)
  • usePayloadAsParameterSource=true (only payload used for parameters)
  • skipUndeclaredResults=false (all result sets included)
  • jdbcCallOperationsCacheSize=10 (caches compiled calls for dynamic procedure names)
  • Returns Map<String, Object> with result sets, OUT parameters, and return values
  • Result set keys: default names or custom names via row mapper configuration
  • OUT parameter keys: parameter names
  • Return value key: "return" or "#result-set-1" (database-dependent)

Threading Model:

  • Executes on message handling thread (for gateways/handlers) or poller thread (for adapters)
  • Thread-safe when using DataSource (connection pooling)
  • SimpleJdbcCall instances cached for dynamic procedure names

Lifecycle:

  • Must call afterPropertiesSet() after configuration (validates configuration)
  • BeanFactory must be set when using SpEL expressions (for bean references)
  • Initialization validates procedure name or expression is configured

Exceptions:

  • DataAccessException - Database access failures
  • IllegalArgumentException - Invalid configuration (e.g., missing procedure name)
  • BadSqlGrammarException - SQL syntax errors or procedure not found
  • InvalidDataAccessApiUsageException - Incorrect parameter usage

Edge Cases:

  • Procedure name can be static (via setStoredProcedureName()) or dynamic (via setStoredProcedureNameExpression())
  • When ignoreColumnMetaData=true, must explicitly define all SQL parameters
  • H2 database always returns count parameter even for procedures with no parameters
  • expectSingleResult=true extracts single result from map (throws MessagingException if map has >1 element)
  • Multiple result sets require row mapper configuration for each named result set
  • Functions return value appears in result map with key "return" or database-specific key
  • Static parameters (via ProcedureParameter value) take precedence over dynamic parameters
  • SpEL expressions can reference Spring beans with @beanName syntax
  • Parameter names are case-sensitive (must match procedure parameter names exactly)
  • skipUndeclaredResults=true ignores result sets not configured with row mappers

Core Classes

StoredProcExecutor

package org.springframework.integration.jdbc;

public class StoredProcExecutor implements BeanFactoryAware, InitializingBean {
    public StoredProcExecutor(DataSource dataSource);

    public void setStoredProcedureName(String storedProcedureName);
    public String getStoredProcedureName();
    public void setStoredProcedureNameExpression(Expression expression);
    public String getStoredProcedureNameExpressionAsString();

    public void setProcedureParameters(List<ProcedureParameter> procedureParameters);
    public void setSqlParameters(List<SqlParameter> sqlParameters);
    public void setIsFunction(boolean isFunction);
    public void setReturnValueRequired(boolean returnValueRequired);
    public void setUsePayloadAsParameterSource(boolean usePayloadAsParameterSource);
    public void setSqlParameterSourceFactory(SqlParameterSourceFactory factory);
    public void setReturningResultSetRowMappers(Map<String, RowMapper<?>> mappers);
    public void setIgnoreColumnMetaData(boolean ignoreColumnMetaData);
    public void setSkipUndeclaredResults(boolean skipUndeclaredResults);
    public void setJdbcCallOperationsCacheSize(int size);

    public Map<String, @Nullable Object> executeStoredProcedure();
    public Map<String, @Nullable Object> executeStoredProcedure(Message<?> message);
}

ProcedureParameter

package org.springframework.integration.jdbc.storedproc;

public class ProcedureParameter {
    public ProcedureParameter();
    public ProcedureParameter(String name, Object value, String expression);

    public String getName();
    public void setName(String name);
    public Object getValue();
    public void setValue(Object value);
    public String getExpression();
    public void setExpression(String expression);

    public static Map<String, String> convertExpressions(Collection<ProcedureParameter> procedureParameters);
    public static Map<String, Object> convertStaticParameters(Collection<ProcedureParameter> procedureParameters);
}

Usage Examples

Basic Procedure Call

import org.springframework.integration.jdbc.StoredProcExecutor;
import org.springframework.integration.jdbc.storedproc.ProcedureParameter;

// Basic procedure call with IN parameters
StoredProcExecutor executor = new StoredProcExecutor(dataSource);
executor.setStoredProcedureName("CREATE_ORDER");

// Define parameters from message
List<ProcedureParameter> params = List.of(
    new ProcedureParameter("customer_id", null, "payload.customerId"),
    new ProcedureParameter("order_date", null, "payload.orderDate"),
    new ProcedureParameter("total_amount", null, "payload.totalAmount")
);
executor.setProcedureParameters(params);

// Execute with message
Order order = new Order(123L, LocalDate.now(), new BigDecimal("499.99"));
Message<Order> message = MessageBuilder.withPayload(order).build();
Map<String, Object> results = executor.executeStoredProcedure(message);

Procedure with OUT Parameters

import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.SqlOutParameter;
import java.sql.Types;

// Procedure with IN and OUT parameters
StoredProcExecutor executorWithOut = new StoredProcExecutor(dataSource);
executorWithOut.setStoredProcedureName("PROCESS_PAYMENT");

// Define SQL parameters (IN and OUT)
List<SqlParameter> sqlParams = List.of(
    new SqlParameter("order_id", Types.BIGINT),
    new SqlParameter("payment_method", Types.VARCHAR),
    new SqlOutParameter("transaction_id", Types.VARCHAR),
    new SqlOutParameter("status_code", Types.INTEGER),
    new SqlOutParameter("status_message", Types.VARCHAR)
);
executorWithOut.setSqlParameters(sqlParams);

// Define parameter values from message
List<ProcedureParameter> procParams = List.of(
    new ProcedureParameter("order_id", null, "payload.orderId"),
    new ProcedureParameter("payment_method", null, "payload.method")
);
executorWithOut.setProcedureParameters(procParams);

// Execute and extract OUT parameters
Payment payment = new Payment(456L, "CREDIT_CARD");
Message<Payment> paymentMsg = MessageBuilder.withPayload(payment).build();
Map<String, Object> result = executorWithOut.executeStoredProcedure(paymentMsg);

String transactionId = (String) result.get("transaction_id");
Integer statusCode = (Integer) result.get("status_code");
String statusMessage = (String) result.get("status_message");

Procedure Returning Result Sets

import org.springframework.jdbc.core.RowMapper;

// Procedure returning result sets
StoredProcExecutor resultSetExecutor = new StoredProcExecutor(dataSource);
resultSetExecutor.setStoredProcedureName("GET_ORDER_SUMMARY");

// Configure row mappers for multiple result sets
Map<String, RowMapper<?>> rowMappers = Map.of(
    "orders", (rs, rowNum) -> new Order(
        rs.getLong("id"),
        rs.getString("order_number"),
        rs.getBigDecimal("amount")
    ),
    "order_items", (rs, rowNum) -> new OrderItem(
        rs.getLong("id"),
        rs.getString("product_name"),
        rs.getInt("quantity")
    )
);
resultSetExecutor.setReturningResultSetRowMappers(rowMappers);

// Set IN parameter
List<ProcedureParameter> sumParams = List.of(
    new ProcedureParameter("customer_id", null, "payload")
);
resultSetExecutor.setProcedureParameters(sumParams);

// Execute and extract result sets
Message<Long> customerMsg = MessageBuilder.withPayload(789L).build();
Map<String, Object> summaryResults = resultSetExecutor.executeStoredProcedure(customerMsg);

List<Order> orders = (List<Order>) summaryResults.get("orders");
List<OrderItem> items = (List<OrderItem>) summaryResults.get("order_items");

Stored Function

// Calling stored function (returns value)
StoredProcExecutor functionExecutor = new StoredProcExecutor(dataSource);
functionExecutor.setStoredProcedureName("CALCULATE_DISCOUNT");
functionExecutor.setIsFunction(true);
functionExecutor.setReturnValueRequired(true);

// Define function parameters
List<ProcedureParameter> funcParams = List.of(
    new ProcedureParameter("order_amount", null, "payload.amount"),
    new ProcedureParameter("customer_tier", null, "payload.tier")
);
functionExecutor.setProcedureParameters(funcParams);

// Execute function
OrderInfo info = new OrderInfo(new BigDecimal("1000"), "GOLD");
Message<OrderInfo> infoMsg = MessageBuilder.withPayload(info).build();
Map<String, Object> funcResult = functionExecutor.executeStoredProcedure(infoMsg);

// Return value is in the map with key "return" or database-specific key
BigDecimal discount = (BigDecimal) funcResult.get("return");

Dynamic Procedure Name

import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;

// Dynamic procedure name using SpEL
StoredProcExecutor dynamicExecutor = new StoredProcExecutor(dataSource);
SpelExpressionParser parser = new SpelExpressionParser();
dynamicExecutor.setStoredProcedureNameExpression(
    parser.parseExpression("'PROCESS_' + payload.type.toUpperCase()")
);

// Set generic parameters
List<ProcedureParameter> dynParams = List.of(
    new ProcedureParameter("entity_id", null, "payload.id"),
    new ProcedureParameter("action", null, "payload.action")
);
dynamicExecutor.setProcedureParameters(dynParams);

// Execute - procedure name determined at runtime
ProcessRequest request = new ProcessRequest("order", 123L, "CANCEL");
Message<ProcessRequest> dynMsg = MessageBuilder.withPayload(request).build();
// Calls PROCESS_ORDER procedure
Map<String, Object> dynResult = dynamicExecutor.executeStoredProcedure(dynMsg);

Performance Optimization

// Performance optimization: disable metadata lookup
StoredProcExecutor optimizedExecutor = new StoredProcExecutor(dataSource);
optimizedExecutor.setStoredProcedureName("BULK_PROCESS");
optimizedExecutor.setIgnoreColumnMetaData(true); // Skip JDBC metadata

// Must explicitly define SQL parameters when metadata disabled
List<SqlParameter> explicitParams = List.of(
    new SqlParameter("batch_id", Types.BIGINT),
    new SqlParameter("process_date", Types.DATE),
    new SqlOutParameter("processed_count", Types.INTEGER)
);
optimizedExecutor.setSqlParameters(explicitParams);

Integration with Spring Integration

StoredProcExecutor is used by polling adapters, message handlers, and gateways:

import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.jdbc.StoredProcExecutor;
import org.springframework.integration.jdbc.outbound.StoredProcOutboundGateway;
import static org.springframework.integration.jdbc.dsl.Jdbc.*;

@Bean
public IntegrationFlow storedProcFlow(DataSource dataSource) {
    return IntegrationFlow
        .from("orderChannel")
        .handle(storedProcOutboundGateway(dataSource)
            .configurerStoredProcExecutor(spec -> spec
                .storedProcedureName("PROCESS_ORDER")
                .procedureParameter(new ProcedureParameter("order_id", null, "payload"))
                .sqlParameter(new SqlOutParameter("result_code", Types.INTEGER))
            ))
        .handle(msg -> {
            Map<String, Object> result = (Map<String, Object>) msg.getPayload();
            Integer resultCode = (Integer) result.get("result_code");
            System.out.println("Result code: " + resultCode);
        })
        .get();
}

Deprecation Notice

Important: The following classes in org.springframework.integration.jdbc package are deprecated as of version 7.0:

  • org.springframework.integration.jdbc.StoredProcMessageHandler - deprecated since 7.0 (marked for removal)
  • org.springframework.integration.jdbc.StoredProcOutboundGateway - deprecated since 7.0 (marked for removal)

Migration Path: Use the non-deprecated classes from org.springframework.integration.jdbc.outbound package instead:

// Old (deprecated):
import org.springframework.integration.jdbc.StoredProcMessageHandler;
import org.springframework.integration.jdbc.StoredProcOutboundGateway;

// New (recommended):
import org.springframework.integration.jdbc.outbound.StoredProcMessageHandler;
import org.springframework.integration.jdbc.outbound.StoredProcOutboundGateway;

The deprecated classes extend the new implementations, so they provide identical functionality. Simply update your import statements to complete the migration.

Key Considerations

  • Parameter Types: Use ProcedureParameter for parameter values; use SqlParameter/SqlOutParameter for type definitions
  • Result Structure: Returns Map<String, Object> with keys for result sets, OUT parameters, and return values
  • Metadata Caching: JDBC metadata is cached by default; disable with setIgnoreColumnMetaData(true) for better performance
  • Dynamic Names: Use setStoredProcedureNameExpression() for runtime procedure selection
  • Transaction Management: Executes within existing transaction or creates new one if transaction manager configured
  • Multiple Result Sets: Configure row mappers for each named result set
  • Functions vs Procedures: Set isFunction=true for functions; return value appears in result map
  • Performance: Set appropriate jdbcCallOperationsCacheSize for dynamic procedure names
  • NULL Handling: SpEL expressions and row mappers should handle NULL values appropriately
  • Error Handling: SQL exceptions propagate as Spring JDBC exceptions
  • H2 Compatibility: H2 may return unexpected parameters; use skipUndeclaredResults=true if needed