Parameter sources provide flexible parameter binding for JPA queries using static values, bean properties, or SpEL expressions. They allow queries to be parameterized dynamically based on message content, headers, or other runtime values.
Required Dependencies:
spring-integration-jpa (this package)spring-integration-core is requiredBeanFactory required for ExpressionEvaluatingParameterSourceFactory (for bean references in expressions)Default Behaviors:
BeanPropertyParameterSource extracts values from bean propertiesBeanPropertyParameterSourceFactory can mix static parameters with bean propertiesExpressionEvaluatingParameterSourceFactory evaluates SpEL expressions:paramName in query)usePayloadAsParameterSource=true extracts parameters from payload bean propertiesThreading Model:
ParameterSourceFactory instances should be thread-safe (reusable)Lifecycle:
ParameterSourceFactory instances are typically Spring beans (singleton)ParameterSource instances are created per message (short-lived)Exceptions:
IllegalArgumentException - Invalid parameter name or configurationEvaluationException - SpEL expression evaluation failuresPropertyAccessException - Bean property access failuresNoSuchParameterException - Parameter not found in source (when accessing)Edge Cases:
hasValue() returns false, getValue() may return null or throw exceptionBeanPropertyParameterSource uses Spring's BeanWrapper for property accessExpressionEvaluatingParameterSourceFactory evaluates expressions against message contextusePayloadAsParameterSource=true requires payload to be a bean (not Map or primitive)payload, headers, #pathVariables, #requestParams, Spring beans (@beanName)interface ParameterSource {
boolean hasValue(String paramName)
Object getValue(String paramName)
}
interface PositionSupportingParameterSource extends ParameterSource {
Object getValueByPosition(int position)
}
interface ParameterSourceFactory {
ParameterSource createParameterSource(Object input)
}class JpaParameter {
// Constructors
JpaParameter()
JpaParameter(String name, Object value, String expression)
JpaParameter(Object value, String expression)
// Accessors
String getName()
void setName(String name)
Object getValue()
void setValue(Object value)
String getExpression()
void setExpression(String expression)
// Parsed expressions
Expression getSpelExpression()
Expression getProjectionExpression()
}Extracts parameter values from properties of a bean.
class BeanPropertyParameterSource implements ParameterSource {
BeanPropertyParameterSource(Object object)
boolean hasValue(String paramName)
Object getValue(String paramName)
String[] getReadablePropertyNames()
}public class SearchCriteria {
private String lastName;
private String department;
private Integer year;
// getters and setters
}
SearchCriteria criteria = new SearchCriteria();
criteria.setLastName("Smith");
criteria.setDepartment("Engineering");
criteria.setYear(2024);
BeanPropertyParameterSource paramSource = new BeanPropertyParameterSource(criteria);
// Extract parameters
String lastName = (String) paramSource.getValue("lastName"); // "Smith"
String dept = (String) paramSource.getValue("department"); // "Engineering"
Integer year = (Integer) paramSource.getValue("year"); // 2024
boolean hasEmail = paramSource.hasValue("email"); // falseSearchCriteria criteria = new SearchCriteria();
criteria.setLastName("Smith");
criteria.setDepartment("Engineering");
JpaExecutor executor = new JpaExecutor(entityManagerFactory);
executor.setJpaQuery("SELECT s FROM Student s " +
"WHERE s.lastName = :lastName " +
"AND s.department = :department");
executor.setParameterSource(new BeanPropertyParameterSource(criteria));
executor.afterPropertiesSet();
Object result = executor.poll();BeanPropertyParameterSource paramSource = new BeanPropertyParameterSource(student);
String[] properties = paramSource.getReadablePropertyNames();
// Returns: ["id", "firstName", "lastName", "email", "department", etc.]Factory for creating BeanPropertyParameterSource instances with optional static parameters.
class BeanPropertyParameterSourceFactory implements ParameterSourceFactory {
void setStaticParameters(Map<String, ?> staticParameters)
ParameterSource createParameterSource(Object input)
}BeanPropertyParameterSourceFactory factory = new BeanPropertyParameterSourceFactory();
SearchCriteria criteria = new SearchCriteria();
criteria.setLastName("Smith");
ParameterSource paramSource = factory.createParameterSource(criteria);
String lastName = (String) paramSource.getValue("lastName"); // "Smith"BeanPropertyParameterSourceFactory factory = new BeanPropertyParameterSourceFactory();
factory.setStaticParameters(Map.of(
"status", "ACTIVE",
"minGpa", 3.0
));
SearchCriteria criteria = new SearchCriteria();
criteria.setDepartment("Engineering");
ParameterSource paramSource = factory.createParameterSource(criteria);
// Can access both bean properties and static parameters
String dept = (String) paramSource.getValue("department"); // "Engineering"
String status = (String) paramSource.getValue("status"); // "ACTIVE"
Double minGpa = (Double) paramSource.getValue("minGpa"); // 3.0@Bean
public IntegrationFlow queryFlow(EntityManagerFactory entityManagerFactory) {
BeanPropertyParameterSourceFactory factory = new BeanPropertyParameterSourceFactory();
factory.setStaticParameters(Map.of("status", "ACTIVE"));
return IntegrationFlow
.from("searchChannel")
.handle(Jpa.retrievingGateway(entityManagerFactory)
.jpaQuery("SELECT s FROM Student s " +
"WHERE s.department = :department " +
"AND s.status = :status")
.parameterSourceFactory(factory)
.usePayloadAsParameterSource(true))
.channel("resultChannel")
.get();
}
// Usage
SearchCriteria criteria = new SearchCriteria();
criteria.setDepartment("Engineering");
messagingTemplate.convertAndSend("searchChannel", criteria);@Bean
public JpaExecutor executor(EntityManagerFactory emf, BeanFactory beanFactory) {
JpaExecutor executor = new JpaExecutor(emf);
executor.setJpaQuery("SELECT s FROM Student s " +
"WHERE s.lastName = :lastName " +
"AND s.status = :status");
BeanPropertyParameterSourceFactory factory = new BeanPropertyParameterSourceFactory();
factory.setStaticParameters(Map.of("status", "ACTIVE"));
executor.setParameterSourceFactory(factory);
executor.setUsePayloadAsParameterSource(true);
executor.setBeanFactory(beanFactory);
executor.afterPropertiesSet();
return executor;
}Creates parameter sources that evaluate SpEL expressions to determine parameter values.
class ExpressionEvaluatingParameterSourceFactory implements ParameterSourceFactory {
ExpressionEvaluatingParameterSourceFactory()
ExpressionEvaluatingParameterSourceFactory(BeanFactory beanFactory)
void setParameters(List<JpaParameter> parameters)
ParameterSource createParameterSource(Object input)
}Note: The setParameters method accepts a List<JpaParameter> which provides more flexibility than the Map-based approach. Each JpaParameter can contain a name, static value, and/or SpEL expression.
ExpressionEvaluatingParameterSourceFactory factory =
new ExpressionEvaluatingParameterSourceFactory();
List<JpaParameter> params = Arrays.asList(
new JpaParameter("lastName", null, "payload.lastName"),
new JpaParameter("year", null, "payload.enrollmentYear"),
new JpaParameter("today", null, "T(java.time.LocalDate).now()")
);
factory.setParameters(params);
Message<?> message = MessageBuilder
.withPayload(searchCriteria)
.build();
ParameterSource paramSource = factory.createParameterSource(message);For convenience, you can also configure parameters using a Map in DSL contexts, which internally creates JpaParameter objects:
// Note: This Map-based approach is available through DSL helpers
// The actual setParameters method uses List<JpaParameter>
factory.setParameters(Map.of(
"lastName", "payload.lastName",
"year", "payload.enrollmentYear",
"today", "T(java.time.LocalDate).now()"
));ExpressionEvaluatingParameterSourceFactory factory =
new ExpressionEvaluatingParameterSourceFactory();
factory.setParameters(Map.of(
"studentId", "payload",
"department", "headers['department']",
"requestTime", "headers['timestamp']"
));
Message<?> message = MessageBuilder
.withPayload(123L)
.setHeader("department", "Engineering")
.setHeader("timestamp", System.currentTimeMillis())
.build();
ParameterSource paramSource = factory.createParameterSource(message);ExpressionEvaluatingParameterSourceFactory factory =
new ExpressionEvaluatingParameterSourceFactory();
factory.setParameters(Map.of(
"fullName", "payload.firstName + ' ' + payload.lastName",
"upperDept", "payload.department.toUpperCase()",
"ageCheck", "payload.birthDate != null ? " +
"T(java.time.Period).between(payload.birthDate, T(java.time.LocalDate).now()).getYears() : null",
"isEligible", "payload.credits >= 120 and payload.gpa >= 3.0"
));@Component
public class QueryConfig {
public String getDefaultStatus() {
return "ACTIVE";
}
public int getMaxResults() {
return 100;
}
}
ExpressionEvaluatingParameterSourceFactory factory =
new ExpressionEvaluatingParameterSourceFactory(beanFactory);
factory.setParameters(Map.of(
"status", "@queryConfig.getDefaultStatus()",
"maxCount", "@queryConfig.getMaxResults()",
"department", "payload.department"
));@Bean
public IntegrationFlow expressionQueryFlow(
EntityManagerFactory entityManagerFactory,
BeanFactory beanFactory) {
ExpressionEvaluatingParameterSourceFactory factory =
new ExpressionEvaluatingParameterSourceFactory(beanFactory);
factory.setParameters(Map.of(
"lastName", "payload.lastName",
"minYear", "payload.yearFrom",
"maxYear", "payload.yearTo"
));
return IntegrationFlow
.from("advancedSearchChannel")
.handle(Jpa.retrievingGateway(entityManagerFactory)
.jpaQuery("SELECT s FROM Student s " +
"WHERE s.lastName = :lastName " +
"AND s.enrollmentYear BETWEEN :minYear AND :maxYear")
.parameterSourceFactory(factory)
.usePayloadAsParameterSource(true))
.channel("searchResultsChannel")
.get();
}Helper class for evaluating parameter expressions.
class ParameterExpressionEvaluator extends AbstractExpressionEvaluator {
StandardEvaluationContext getEvaluationContext()
<T> T evaluateExpression(String expression, Object input, Class<T> expectedType)
}ParameterExpressionEvaluator evaluator = new ParameterExpressionEvaluator();
Message<?> message = MessageBuilder.withPayload(student).build();
String result = evaluator.evaluateExpression(
"payload.lastName",
message,
String.class
);The JpaParameter class combines static values and expressions for query parameters.
JpaParameter param = new JpaParameter();
param.setName("status");
param.setValue("ACTIVE");Or using constructor:
JpaParameter param = new JpaParameter("status", "ACTIVE", null);JpaParameter param = new JpaParameter();
param.setName("studentId");
param.setExpression("payload");Or using constructor:
JpaParameter param = new JpaParameter("studentId", null, "payload");List<JpaParameter> parameters = Arrays.asList(
new JpaParameter("status", "ACTIVE", null), // Static
new JpaParameter("department", null, "payload.dept"), // Expression
new JpaParameter("year", null, "headers['academicYear']") // From header
);
executor.setJpaParameters(parameters);@Bean
public IntegrationFlow mixedParamsFlow(EntityManagerFactory entityManagerFactory) {
return IntegrationFlow
.from("queryChannel")
.handle(Jpa.retrievingGateway(entityManagerFactory)
.jpaQuery("SELECT s FROM Student s " +
"WHERE s.status = :status " +
"AND s.department = :department " +
"AND s.year = :year")
.parameter("status", "ACTIVE") // Static
.parameterExpression("department", "payload.dept") // Expression
.parameterExpression("year", "headers['year']")) // From header
.channel("resultsChannel")
.get();
}@Bean
public IntegrationFlow studentSearchFlow(EntityManagerFactory entityManagerFactory) {
BeanPropertyParameterSourceFactory factory = new BeanPropertyParameterSourceFactory();
factory.setStaticParameters(Map.of(
"status", "ACTIVE",
"minGpa", 2.0
));
return IntegrationFlow
.from("studentSearchChannel")
.handle(Jpa.retrievingGateway(entityManagerFactory)
.jpaQuery("SELECT s FROM Student s " +
"WHERE s.department = :department " +
"AND s.status = :status " +
"AND s.gpa >= :minGpa")
.parameterSourceFactory(factory)
.usePayloadAsParameterSource(true))
.channel("searchResultsChannel")
.get();
}
// Usage
public class StudentSearch {
private String department;
// No need for status or minGpa - they have defaults
}
StudentSearch search = new StudentSearch();
search.setDepartment("Engineering");
messagingTemplate.convertAndSend("studentSearchChannel", search);@Bean
public IntegrationFlow dynamicQueryFlow(
EntityManagerFactory entityManagerFactory,
BeanFactory beanFactory) {
ExpressionEvaluatingParameterSourceFactory factory =
new ExpressionEvaluatingParameterSourceFactory(beanFactory);
factory.setParameters(Map.of(
"searchTerm", "payload.search",
"searchPattern", "'%' + payload.search + '%'",
"department", "payload.department ?: 'ALL'",
"startDate", "payload.dateFrom ?: T(java.time.LocalDate).now().minusMonths(1)",
"endDate", "payload.dateTo ?: T(java.time.LocalDate).now()"
));
return IntegrationFlow
.from("dynamicSearchChannel")
.handle(Jpa.retrievingGateway(entityManagerFactory)
.jpaQuery("SELECT s FROM Student s " +
"WHERE (s.firstName LIKE :searchPattern " +
" OR s.lastName LIKE :searchPattern) " +
"AND (:department = 'ALL' OR s.department = :department) " +
"AND s.enrollmentDate BETWEEN :startDate AND :endDate")
.parameterSourceFactory(factory)
.usePayloadAsParameterSource(true))
.channel("dynamicResultsChannel")
.get();
}@Bean
public IntegrationFlow combinedParamsFlow(EntityManagerFactory entityManagerFactory) {
BeanPropertyParameterSourceFactory beanFactory = new BeanPropertyParameterSourceFactory();
beanFactory.setStaticParameters(Map.of("defaultStatus", "ACTIVE"));
return IntegrationFlow
.from("combinedChannel")
.handle(Jpa.retrievingGateway(entityManagerFactory)
.jpaQuery("SELECT s FROM Student s " +
"WHERE s.lastName = :lastName " +
"AND s.status = :status " +
"AND s.year = :year " +
"AND s.credits >= :minCredits")
.parameterSourceFactory(beanFactory)
.usePayloadAsParameterSource(true)
.parameter("minCredits", 30) // Direct static parameter
.parameterExpression("year", "headers['academicYear']")) // Header
.channel("combinedResultsChannel")
.get();
}@Bean
public IntegrationFlow inboundWithParamsFlow(EntityManagerFactory entityManagerFactory) {
return IntegrationFlow
.from(Jpa.inboundAdapter(entityManagerFactory)
.jpaQuery("SELECT t FROM Task t " +
"WHERE t.status = :status " +
"AND t.priority >= :minPriority")
.parameterSource(new BeanPropertyParameterSource(
Map.of("status", "PENDING", "minPriority", 5)))
.maxResults(100)
.deleteAfterPoll(true),
e -> e.poller(Pollers.fixedDelay(10000)))
.channel("taskChannel")
.get();
}final class ExpressionEvaluatingParameterSourceUtils {
static List<JpaParameter> convertStaticParameters(Map<String, ?> staticParameters)
}Map<String, Object> staticParams = Map.of(
"status", "ACTIVE",
"type", "STUDENT",
"year", 2024
);
List<JpaParameter> jpaParams =
ExpressionEvaluatingParameterSourceUtils.convertStaticParameters(staticParams);
executor.setJpaParameters(jpaParams);JpaParameter with valuesBeanPropertyParameterSourceFactoryExpressionEvaluatingParameterSourceFactoryWhen using expressions, be cautious of:
?.)// Good - safe navigation
"payload?.department"
// Good - null coalescing
"payload.year ?: 2024"
// Good - type checking
"payload instanceof T(com.example.SearchCriteria) ? payload.department : null"ParameterSourceFactory instancesUse consistent, descriptive parameter names:
// Good
.parameter("studentId", "payload.id")
.parameter("enrollmentYear", "payload.year")
// Avoid
.parameter("id", "payload.id")
.parameter("year", "payload.year")@Bean
public ExpressionEvaluatingParameterSourceFactory safeFactory(BeanFactory beanFactory) {
ExpressionEvaluatingParameterSourceFactory factory =
new ExpressionEvaluatingParameterSourceFactory(beanFactory);
factory.setParameters(Map.of(
"safeDept", "payload.department != null ? payload.department : 'UNKNOWN'",
"safeYear", "payload.year != null ? payload.year : T(java.time.Year).now().getValue()"
));
return factory;
}