or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

inbound-adapter.mdindex.mdjpa-executor.mdjpa-operations.mdoutbound-adapter.mdparameter-sources.mdretrieving-gateway.mdupdating-gateway.mdxml-configuration.md
tile.json

jpa-executor.mddocs/

JPA Executor

The JpaExecutor is the central execution engine that performs JPA operations for all Spring Integration JPA components. It encapsulates query execution, parameter binding, entity persistence, and result processing. All inbound adapters, outbound adapters, and gateways delegate their JPA operations to a JpaExecutor instance.

Key Information for Agents

Required Dependencies:

  • spring-integration-jpa (this package)
  • spring-integration-core is required
  • EntityManagerFactory, EntityManager, or JpaOperations must be provided
  • BeanFactory required when using SpEL expressions

Default Behaviors:

  • persistMode=MERGE (for persistence operations)
  • flush=false (no immediate flush)
  • flushSize=0 (no batch flushing)
  • clearOnFlush=false (persistence context not cleared)
  • expectSingleResult=false (returns List<?>)
  • maxNumberOfResults=unlimited (no limit)
  • deleteAfterPoll=false (entities not deleted)
  • deleteInBatch=false (individual deletes)
  • Exactly one query type must be configured: entityClass, jpaQuery, nativeQuery, or namedQuery

Threading Model:

  • Thread-safe when using EntityManagerFactory (creates EntityManager per operation)
  • Not thread-safe when using single EntityManager (requires thread confinement)
  • Execution methods (poll(), executeOutboundJpaOperation()) can be called concurrently with EntityManagerFactory
  • Batch operations (flushSize > 0) process sequentially

Lifecycle:

  • Must call afterPropertiesSet() after configuration (validates configuration)
  • BeanFactory must be set when using SpEL expressions (for bean references)
  • Initialization validates that exactly one query type is configured
  • EntityManager lifecycle managed automatically when using EntityManagerFactory

Exceptions:

  • IllegalArgumentException - Invalid configuration (e.g., multiple query types set, missing required config)
  • JpaOperationFailedException - JPA operation failures (contains offending query)
  • PersistenceException - General JPA persistence errors
  • NoResultException - Query returned no results (when expectSingleResult=true)
  • NonUniqueResultException - Query returned multiple results (when expectSingleResult=true)

Edge Cases:

  • Exactly one query type must be configured: entityClass, jpaQuery, nativeQuery, or namedQuery
  • Native queries require entityClass for result type mapping
  • Named queries must be defined on entity classes with @NamedQuery annotation
  • idExpression uses EntityManager.find() for optimal performance (bypasses query)
  • firstResultExpression and maxResultsExpression evaluated against message context
  • parameterSource (static) vs parameterSourceFactory (dynamic per message)
  • usePayloadAsParameterSource=true extracts parameters from payload bean properties
  • poll() (no message) vs poll(Message<?>) (with message context for parameters)
  • executeOutboundJpaOperation() returns entity (PERSIST/MERGE/DELETE) or Integer (update queries)
  • When flushSize > 0, flush happens after every N operations, not per message
  • clearOnFlush=true prevents memory issues with large entity sets

Core Class

class JpaExecutor implements InitializingBean, BeanFactoryAware {
    // Constructors
    JpaExecutor(EntityManagerFactory entityManagerFactory)
    JpaExecutor(EntityManager entityManager)
    JpaExecutor(JpaOperations jpaOperations)

    // Lifecycle
    void setBeanFactory(BeanFactory beanFactory)
    void afterPropertiesSet()

    // Query configuration
    void setEntityClass(Class<?> entityClass)
    void setJpaQuery(String jpaQuery)
    void setNativeQuery(String nativeQuery)
    void setNamedQuery(String namedQuery)

    // Persistence configuration
    void setPersistMode(PersistMode persistMode)
    void setFlush(boolean flush)
    void setFlushSize(int flushSize)
    void setClearOnFlush(boolean clearOnFlush)

    // Retrieval configuration
    void setExpectSingleResult(boolean expectSingleResult)
    void setFirstResultExpression(Expression firstResultExpression)
    void setMaxResultsExpression(Expression maxResultsExpression)
    void setMaxNumberOfResults(int maxNumberOfResults)
    void setIdExpression(Expression idExpression)

    // Delete configuration
    void setDeleteAfterPoll(boolean deleteAfterPoll)
    void setDeleteInBatch(boolean deleteInBatch)

    // Parameter configuration
    void setJpaParameters(List<JpaParameter> jpaParameters)
    void setParameterSourceFactory(ParameterSourceFactory parameterSourceFactory)
    void setParameterSource(ParameterSource parameterSource)
    void setUsePayloadAsParameterSource(boolean usePayloadAsParameterSource)

    // Execution methods
    Object executeOutboundJpaOperation(Message<?> requestMessage)
    Object poll()
    Object poll(Message<?> requestMessage)
}

Usage

Direct Instantiation with EntityManagerFactory

@Bean
public JpaExecutor jpaExecutor(EntityManagerFactory entityManagerFactory) {
    JpaExecutor executor = new JpaExecutor(entityManagerFactory);
    executor.setEntityClass(Student.class);
    executor.setMaxNumberOfResults(100);
    executor.setDeleteAfterPoll(true);
    executor.afterPropertiesSet();
    return executor;
}

Direct Instantiation with EntityManager

@Bean
public JpaExecutor jpaExecutor(EntityManager entityManager) {
    JpaExecutor executor = new JpaExecutor(entityManager);
    executor.setJpaQuery("SELECT s FROM Student s WHERE s.active = true");
    executor.setExpectSingleResult(false);
    executor.afterPropertiesSet();
    return executor;
}

Direct Instantiation with JpaOperations

@Bean
public JpaExecutor jpaExecutor(JpaOperations jpaOperations) {
    JpaExecutor executor = new JpaExecutor(jpaOperations);
    executor.setEntityClass(Student.class);
    executor.setPersistMode(PersistMode.MERGE);
    executor.afterPropertiesSet();
    return executor;
}

Complete Configuration Example

@Bean
public JpaExecutor configuredExecutor(
        EntityManagerFactory entityManagerFactory,
        BeanFactory beanFactory) {
    JpaExecutor executor = new JpaExecutor(entityManagerFactory);

    // Query configuration
    executor.setJpaQuery("SELECT s FROM Student s WHERE s.department = :dept");

    // Parameter configuration
    List<JpaParameter> parameters = new ArrayList<>();
    parameters.add(new JpaParameter("dept", null, "payload"));
    executor.setJpaParameters(parameters);

    // Result configuration
    executor.setMaxNumberOfResults(50);
    executor.setExpectSingleResult(false);

    // Lifecycle
    executor.setBeanFactory(beanFactory);
    executor.afterPropertiesSet();

    return executor;
}

Use with Inbound Adapter

@Bean
public JpaPollingChannelAdapter inboundAdapter(JpaExecutor jpaExecutor) {
    return new JpaPollingChannelAdapter(jpaExecutor);
}

@Bean
public IntegrationFlow inboundFlow(JpaPollingChannelAdapter adapter) {
    return IntegrationFlow
        .from(adapter, e -> e.poller(Pollers.fixedDelay(10000)))
        .channel("outputChannel")
        .get();
}

Use with Outbound Gateway

@Bean
public JpaOutboundGateway outboundGateway(JpaExecutor jpaExecutor) {
    JpaOutboundGateway gateway = new JpaOutboundGateway(jpaExecutor);
    gateway.setGatewayType(OutboundGatewayType.RETRIEVING);
    return gateway;
}

@Bean
public IntegrationFlow gatewayFlow(JpaOutboundGateway gateway) {
    return IntegrationFlow
        .from("requestChannel")
        .handle(gateway)
        .channel("replyChannel")
        .get();
}

Configuration Methods

Query Type Configuration

Exactly one query type must be configured:

Entity Class Query

executor.setEntityClass(Student.class);

Retrieval: Returns all entities of the specified class Persistence: Payload is persisted/merged/deleted as this entity type

JPQL Query

executor.setJpaQuery("SELECT s FROM Student s WHERE s.grade = :grade");

For SELECT queries (retrieval operations) or UPDATE/DELETE queries (update operations)

Native SQL Query

executor.setNativeQuery("SELECT * FROM students WHERE grade = :grade");
executor.setEntityClass(Student.class);  // Required for result mapping

Native SQL queries require entityClass for result type mapping

Named Query

executor.setNamedQuery("Student.findByDepartment");

References a @NamedQuery defined on an entity class

Persistence Mode Configuration

executor.setPersistMode(PersistMode.PERSIST);  // For new entities
executor.setPersistMode(PersistMode.MERGE);    // For updates
executor.setPersistMode(PersistMode.DELETE);   // For deletions

Flush Configuration

Flush After Each Operation

executor.setFlush(true);

Forces immediate synchronization with database after each operation

Batch Flush

executor.setFlushSize(100);  // Flush every 100 operations
executor.setClearOnFlush(true);  // Clear persistence context on flush

Result Configuration

Single vs Multiple Results

executor.setExpectSingleResult(true);  // Returns single entity
executor.setExpectSingleResult(false); // Returns List<?>

Result Limiting

executor.setMaxNumberOfResults(100);  // Static limit

Or use expression for dynamic limit:

Expression maxExpr = new SpelExpressionParser().parseExpression("payload.pageSize");
executor.setMaxResultsExpression(maxExpr);

Pagination

Expression firstResultExpr = new SpelExpressionParser()
    .parseExpression("payload.page * payload.pageSize");
executor.setFirstResultExpression(firstResultExpr);

Expression maxResultsExpr = new SpelExpressionParser()
    .parseExpression("payload.pageSize");
executor.setMaxResultsExpression(maxResultsExpr);

ID-Based Retrieval

Expression idExpr = new SpelExpressionParser().parseExpression("payload");
executor.setIdExpression(idExpr);

Uses EntityManager.find() for optimal performance

Delete After Poll Configuration

executor.setDeleteAfterPoll(true);  // Delete retrieved entities
executor.setDeleteInBatch(true);    // Use batch delete

Parameter Configuration

Static Parameters

List<JpaParameter> parameters = new ArrayList<>();
parameters.add(new JpaParameter("grade", "A", null));
parameters.add(new JpaParameter("year", 2024, null));
executor.setJpaParameters(parameters);

Expression Parameters

List<JpaParameter> parameters = new ArrayList<>();
parameters.add(new JpaParameter("grade", null, "payload.grade"));
parameters.add(new JpaParameter("year", null, "headers['academicYear']"));
executor.setJpaParameters(parameters);

Parameter Source Factory

BeanPropertyParameterSourceFactory factory = new BeanPropertyParameterSourceFactory();
factory.setStaticParameters(Map.of("status", "ACTIVE"));
executor.setParameterSourceFactory(factory);
executor.setUsePayloadAsParameterSource(true);

Direct Parameter Source

ParameterSource paramSource = new BeanPropertyParameterSource(criteriaObject);
executor.setParameterSource(paramSource);

Execution Methods

Poll (No Message)

Object result = executor.poll();

Executes a retrieval query without message context. Used internally by inbound adapters.

Returns:

  • Single entity if expectSingleResult is true
  • List<?> of entities if expectSingleResult is false
  • null if no results found

Poll (With Message)

Message<?> message = MessageBuilder.withPayload(searchCriteria).build();
Object result = executor.poll(message);

Executes a retrieval query with message context for parameter evaluation. Used by retrieving gateways.

Execute Outbound Operation

Message<?> message = MessageBuilder.withPayload(entity).build();
Object result = executor.executeOutboundJpaOperation(message);

Executes a persistence operation (persist/merge/delete) or update query.

For entity operations:

  • PERSIST: Returns persisted entity with generated ID
  • MERGE: Returns managed entity
  • DELETE: Returns deleted entity

For update queries:

  • Returns Integer count of affected rows

Complete Examples

Retrieval Executor

@Bean
public JpaExecutor retrievalExecutor(EntityManagerFactory emf, BeanFactory beanFactory) {
    JpaExecutor executor = new JpaExecutor(emf);

    // Query configuration
    executor.setJpaQuery("SELECT s FROM Student s " +
                        "WHERE s.department = :dept " +
                        "AND s.enrollmentYear = :year");

    // Parameters
    List<JpaParameter> params = Arrays.asList(
        new JpaParameter("dept", null, "payload.department"),
        new JpaParameter("year", null, "payload.year")
    );
    executor.setJpaParameters(params);

    // Result configuration
    executor.setExpectSingleResult(false);
    executor.setMaxNumberOfResults(100);

    // Lifecycle
    executor.setBeanFactory(beanFactory);
    executor.afterPropertiesSet();

    return executor;
}

Persistence Executor

@Bean
public JpaExecutor persistenceExecutor(EntityManagerFactory emf) {
    JpaExecutor executor = new JpaExecutor(emf);

    // Entity configuration
    executor.setEntityClass(Student.class);
    executor.setPersistMode(PersistMode.MERGE);

    // Flush configuration
    executor.setFlush(true);

    executor.afterPropertiesSet();
    return executor;
}

Update Query Executor

@Bean
public JpaExecutor updateExecutor(EntityManagerFactory emf, BeanFactory beanFactory) {
    JpaExecutor executor = new JpaExecutor(emf);

    // Update query
    executor.setJpaQuery("UPDATE Student s " +
                        "SET s.status = :status " +
                        "WHERE s.year = :year");

    // Parameters from payload
    BeanPropertyParameterSourceFactory factory =
        new BeanPropertyParameterSourceFactory();
    executor.setParameterSourceFactory(factory);
    executor.setUsePayloadAsParameterSource(true);

    executor.setBeanFactory(beanFactory);
    executor.afterPropertiesSet();

    return executor;
}

Polling with Delete Executor

@Bean
public JpaExecutor pollingDeleteExecutor(EntityManagerFactory emf) {
    JpaExecutor executor = new JpaExecutor(emf);

    // Query
    executor.setJpaQuery("SELECT o FROM Order o WHERE o.processed = true");

    // Delete configuration
    executor.setDeleteAfterPoll(true);
    executor.setDeleteInBatch(true);

    // Result limiting
    executor.setMaxNumberOfResults(500);

    executor.afterPropertiesSet();
    return executor;
}

Paginated Retrieval Executor

@Bean
public JpaExecutor paginatedExecutor(EntityManagerFactory emf, BeanFactory beanFactory) {
    JpaExecutor executor = new JpaExecutor(emf);

    // Query
    executor.setEntityClass(Student.class);

    // Pagination expressions
    SpelExpressionParser parser = new SpelExpressionParser();
    executor.setFirstResultExpression(
        parser.parseExpression("payload.offset")
    );
    executor.setMaxResultsExpression(
        parser.parseExpression("payload.limit")
    );

    executor.setBeanFactory(beanFactory);
    executor.afterPropertiesSet();

    return executor;
}

ID-Based Retrieval Executor

@Bean
public JpaExecutor idRetrievalExecutor(EntityManagerFactory emf, BeanFactory beanFactory) {
    JpaExecutor executor = new JpaExecutor(emf);

    // Entity class
    executor.setEntityClass(Student.class);

    // ID expression
    SpelExpressionParser parser = new SpelExpressionParser();
    executor.setIdExpression(parser.parseExpression("payload"));

    // Single result
    executor.setExpectSingleResult(true);

    executor.setBeanFactory(beanFactory);
    executor.afterPropertiesSet();

    return executor;
}

Integration with Components

With Inbound Channel Adapter

@Configuration
public class InboundConfig {
    @Bean
    public JpaExecutor inboundExecutor(EntityManagerFactory emf) {
        JpaExecutor executor = new JpaExecutor(emf);
        executor.setEntityClass(Task.class);
        executor.setMaxNumberOfResults(50);
        executor.setDeleteAfterPoll(true);
        executor.afterPropertiesSet();
        return executor;
    }

    @Bean
    public JpaPollingChannelAdapter adapter(JpaExecutor inboundExecutor) {
        return new JpaPollingChannelAdapter(inboundExecutor);
    }

    @Bean
    public IntegrationFlow flow(JpaPollingChannelAdapter adapter) {
        return IntegrationFlow
            .from(adapter, e -> e.poller(Pollers.fixedDelay(5000)))
            .channel("taskChannel")
            .get();
    }
}

With Outbound Adapter

@Configuration
public class OutboundAdapterConfig {
    @Bean
    public JpaExecutor outboundExecutor(EntityManagerFactory emf) {
        JpaExecutor executor = new JpaExecutor(emf);
        executor.setEntityClass(LogEntry.class);
        executor.setPersistMode(PersistMode.PERSIST);
        executor.setFlushSize(100);
        executor.afterPropertiesSet();
        return executor;
    }

    @Bean
    public JpaOutboundGateway adapter(JpaExecutor outboundExecutor) {
        JpaOutboundGateway gateway = new JpaOutboundGateway(outboundExecutor);
        gateway.setProducesReply(false);  // Adapter mode
        return gateway;
    }

    @Bean
    public IntegrationFlow flow(JpaOutboundGateway adapter) {
        return IntegrationFlow
            .from("logChannel")
            .handle(adapter)
            .get();
    }
}

With Retrieving Gateway

@Configuration
public class RetrievingGatewayConfig {
    @Bean
    public JpaExecutor retrievingExecutor(EntityManagerFactory emf, BeanFactory beanFactory) {
        JpaExecutor executor = new JpaExecutor(emf);
        executor.setJpaQuery("SELECT s FROM Student s WHERE s.id = :id");

        List<JpaParameter> params = Collections.singletonList(
            new JpaParameter("id", null, "payload")
        );
        executor.setJpaParameters(params);
        executor.setExpectSingleResult(true);

        executor.setBeanFactory(beanFactory);
        executor.afterPropertiesSet();
        return executor;
    }

    @Bean
    public JpaOutboundGateway retrievingGateway(JpaExecutor retrievingExecutor) {
        JpaOutboundGateway gateway = new JpaOutboundGateway(retrievingExecutor);
        gateway.setGatewayType(OutboundGatewayType.RETRIEVING);
        return gateway;
    }

    @Bean
    public IntegrationFlow flow(JpaOutboundGateway retrievingGateway) {
        return IntegrationFlow
            .from("findChannel")
            .handle(retrievingGateway)
            .channel("resultChannel")
            .get();
    }
}

With Updating Gateway

@Configuration
public class UpdatingGatewayConfig {
    @Bean
    public JpaExecutor updatingExecutor(EntityManagerFactory emf) {
        JpaExecutor executor = new JpaExecutor(emf);
        executor.setEntityClass(Student.class);
        executor.setPersistMode(PersistMode.MERGE);
        executor.setFlush(true);
        executor.afterPropertiesSet();
        return executor;
    }

    @Bean
    public JpaOutboundGateway updatingGateway(JpaExecutor updatingExecutor) {
        JpaOutboundGateway gateway = new JpaOutboundGateway(updatingExecutor);
        gateway.setGatewayType(OutboundGatewayType.UPDATING);
        return gateway;
    }

    @Bean
    public IntegrationFlow flow(JpaOutboundGateway updatingGateway) {
        return IntegrationFlow
            .from("updateChannel")
            .handle(updatingGateway)
            .channel("updatedChannel")
            .get();
    }
}

Lifecycle Management

Bean Factory Awareness

The JpaExecutor requires access to the BeanFactory when using SpEL expressions:

executor.setBeanFactory(beanFactory);

This allows expressions to reference Spring beans (e.g., @beanName.property).

Initialization

Call afterPropertiesSet() after configuration:

executor.afterPropertiesSet();

This validates the configuration and initializes internal state.

Working with JpaOperations

For advanced use cases, you can provide a custom JpaOperations implementation:

@Bean
public JpaOperations customJpaOperations(EntityManagerFactory emf) {
    DefaultJpaOperations operations = new DefaultJpaOperations();
    operations.setEntityManagerFactory(emf);
    // Custom configuration
    operations.afterPropertiesSet();
    return operations;
}

@Bean
public JpaExecutor executor(JpaOperations customJpaOperations) {
    return new JpaExecutor(customJpaOperations);
}

Error Handling

JpaExecutor throws JpaOperationFailedException when operations fail:

try {
    Object result = executor.executeOutboundJpaOperation(message);
} catch (JpaOperationFailedException e) {
    String query = e.getOffendingJPAQl();
    logger.error("JPA operation failed for query: {}", query, e);
}

Thread Safety

JpaExecutor is thread-safe after initialization. Multiple threads can safely call execution methods concurrently when using an EntityManagerFactory (recommended). When using a single EntityManager, ensure proper thread confinement.

Performance Considerations

EntityManagerFactory vs EntityManager

Prefer EntityManagerFactory:

  • Thread-safe
  • Manages EntityManager lifecycle per operation
  • Suitable for concurrent access

Use EntityManager only when:

  • You need specific EntityManager configuration
  • Operating in single-threaded context
  • Managing lifecycle externally

Batch Operations

For high-volume operations, configure batch flushing:

executor.setFlushSize(100);
executor.setClearOnFlush(true);

Query Optimization

Use ID-based retrieval when possible:

// Faster
executor.setEntityClass(Student.class);
executor.setIdExpression(parser.parseExpression("payload"));

// Slower
executor.setJpaQuery("SELECT s FROM Student s WHERE s.id = :id");