CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-spring-integration-jpa

Spring Integration JPA Support - provides components for performing database operations using JPA

Overview
Eval results
Files

jpa-operations.mddocs/

JPA Operations

The JpaOperations interface defines all JPA operations executed by Spring Integration JPA components. It provides a clean abstraction over JPA EntityManager operations, with a default implementation that can be extended or customized.

Key Information for Agents

Required Dependencies:

  • spring-integration-jpa (this package)
  • spring-orm for JPA support
  • EntityManagerFactory or EntityManager must be configured
  • JPA implementation (Hibernate, EclipseLink, etc.) must be on classpath

Default Behaviors:

  • DefaultJpaOperations is the standard implementation
  • Methods participate in Spring transactions when configured
  • flush() must be called explicitly (not automatic)
  • persist() and merge() accept Object (can be Iterable for batch operations)
  • Query methods return List<?> or Object (nullable for single results)
  • Update methods return int (affected row count)

Threading Model:

  • DefaultJpaOperations is thread-safe when using EntityManagerFactory
  • Each operation creates new EntityManager from factory (recommended)
  • When using single EntityManager, ensure proper thread confinement
  • Batch operations (flushSize > 0) process sequentially

Lifecycle:

  • Must call afterPropertiesSet() after configuration
  • EntityManager lifecycle managed automatically when using EntityManagerFactory
  • When using EntityManager directly, lifecycle must be managed externally

Exceptions:

  • PersistenceException - General JPA persistence errors
  • EntityExistsException - Entity already exists (when using persist())
  • OptimisticLockException - Version conflict (when using optimistic locking)
  • NoResultException - Query returned no results (when expecting single result)
  • NonUniqueResultException - Query returned multiple results (when expecting single result)
  • IllegalArgumentException - Invalid parameters or configuration

Edge Cases:

  • persist() and merge() accept Object which can be Iterable - each element is processed
  • deleteInBatch() uses single DELETE statement (more efficient than individual deletes)
  • find() returns null if entity not found (does not throw exception)
  • getSingleResultForQuery() throws exception if multiple results found
  • getResultListForQuery() returns empty list if no results (not null)
  • Pagination: firstResult is 0-based, maxNumberOfResults limits result size
  • Native queries require entityClass for result type mapping
  • Named queries must be defined on entity classes with @NamedQuery
  • flush() forces immediate database synchronization
  • Batch operations: flushSize > 0 flushes every N operations, clearOnFlush clears context

Core Interface

interface JpaOperations {
    // Single entity persistence
    void persist(Object entity)
    void persist(Object entity, int flushSize, boolean clearOnFlush)
    Object merge(Object entity)
    Object merge(Object entity, int flushSize, boolean clearOnFlush)
    void delete(Object entity)

    // Batch entity persistence
    void deleteInBatch(Iterable<?> entities)

    // Entity retrieval
    <T> T find(Class<T> entityClass, Object primaryKey)

    // Query execution - entity class based
    List<?> getResultListForClass(Class<?> entityClass, int firstResult, int maxNumberOfResults)

    // Query execution - JPQL
    List<?> getResultListForQuery(String query, ParameterSource source)
    List<?> getResultListForQuery(String query, ParameterSource source, int firstResult, int maxNumberOfResults)
    Object getSingleResultForQuery(String query, ParameterSource source)

    // Query execution - named query
    List<?> getResultListForNamedQuery(String namedQuery, ParameterSource source, int firstResult, int maxNumberOfResults)

    // Query execution - native SQL
    List<?> getResultListForNativeQuery(String nativeQuery, Class<?> entityClass,
                                        ParameterSource source, int firstResult, int maxNumberOfResults)

    // Update/delete queries
    int executeUpdate(String updateQuery, ParameterSource source)
    int executeUpdateWithNamedQuery(String namedQuery, ParameterSource source)
    int executeUpdateWithNativeQuery(String nativeQuery, ParameterSource source)

    // EntityManager operations
    void flush()
}

Default Implementation

class DefaultJpaOperations extends AbstractJpaOperations {
    // Implements all JpaOperations methods
}

abstract class AbstractJpaOperations implements JpaOperations, InitializingBean {
    void setEntityManager(EntityManager entityManager)
    void setEntityManagerFactory(EntityManagerFactory entityManagerFactory)
    void afterPropertiesSet()
    void flush()
}

Usage Examples

Basic Setup

@Bean
public JpaOperations jpaOperations(EntityManagerFactory entityManagerFactory) {
    DefaultJpaOperations operations = new DefaultJpaOperations();
    operations.setEntityManagerFactory(entityManagerFactory);
    operations.afterPropertiesSet();
    return operations;
}

With EntityManager

@Bean
public JpaOperations jpaOperations(EntityManager entityManager) {
    DefaultJpaOperations operations = new DefaultJpaOperations();
    operations.setEntityManager(entityManager);
    operations.afterPropertiesSet();
    return operations;
}

Using JpaOperations Directly

Persist Entity

JpaOperations jpaOps = ...;

Student student = new Student("John", "Doe");
jpaOps.persist(student);  // Entity now has generated ID
jpaOps.flush();           // Force sync to database

System.out.println("Student ID: " + student.getId());

Merge Entity

Student detached = ...;  // Detached entity
Student managed = jpaOps.merge(detached);
jpaOps.flush();

Delete Entity

Student student = ...;
jpaOps.delete(student);
jpaOps.flush();

Persist Multiple Entities

Note: persist() accepts Object which can be an Iterable. When an Iterable is passed, each element is persisted.

List<Student> students = Arrays.asList(
    new Student("John", "Doe"),
    new Student("Jane", "Smith"),
    new Student("Bob", "Johnson")
);

// Persist with batch flushing
for (Student student : students) {
    jpaOps.persist(student, 50, true);  // Flush every 50, clear on flush
}

Merge Multiple Entities

Note: merge() accepts Object which can be an Iterable. When an Iterable is passed, each element is merged.

List<Student> detached = ...;
// Merge returns Object (may be different instance)
Object managed = jpaOps.merge(detached);

Delete In Batch

List<Student> students = ...;
jpaOps.deleteInBatch(students);
System.out.println("Deleted students in batch");

Find by Primary Key

Student student = jpaOps.find(Student.class, 123L);
if (student != null) {
    System.out.println("Found: " + student.getLastName());
}

Get All Entities of Class

List<?> allStudents = jpaOps.getResultListForClass(
    Student.class,
    0,      // firstResult
    100     // maxResults
);

Execute JPQL Query

String query = "SELECT s FROM Student s WHERE s.department = 'Engineering'";
List<?> engineers = jpaOps.getResultListForQuery(query, 0, 50);

Execute JPQL Query - Single Result

String query = "SELECT s FROM Student s WHERE s.id = 123";
Object student = jpaOps.getSingleResultForQuery(query);

Execute Named Query

// Assuming @NamedQuery defined on Student entity
List<?> active = jpaOps.getResultListForNamedQuery(
    "Student.findActive",
    0,
    100
);

Execute Native SQL Query

String nativeQuery = "SELECT * FROM students WHERE grade = 'A'";
List<?> topStudents = jpaOps.getResultListForNativeQuery(
    nativeQuery,
    Student.class,  // Result type
    0,
    50
);

Execute Update Query

String updateQuery = "UPDATE Student s SET s.status = 'GRADUATED' " +
                     "WHERE s.credits >= 120";
int updatedCount = jpaOps.executeUpdate(updateQuery);
System.out.println("Updated " + updatedCount + " students");

Execute Named Update Query

int count = jpaOps.executeUpdateWithNamedQuery("Student.activateAll");

Execute Native Update Query

String nativeUpdate = "UPDATE students SET last_modified = CURRENT_TIMESTAMP";
int count = jpaOps.executeUpdateWithNativeQuery(nativeUpdate);

Integration with JpaExecutor

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

Integration with Spring Integration Components

@Bean
public IntegrationFlow flowWithCustomOperations(JpaOperations jpaOperations) {
    JpaExecutor executor = new JpaExecutor(jpaOperations);
    executor.setJpaQuery("SELECT s FROM Student s WHERE s.active = true");
    executor.setMaxNumberOfResults(100);
    executor.afterPropertiesSet();

    JpaPollingChannelAdapter adapter = new JpaPollingChannelAdapter(executor);

    return IntegrationFlow
        .from(adapter, e -> e.poller(Pollers.fixedDelay(10000)))
        .channel("studentChannel")
        .get();
}

Custom JpaOperations Implementation

You can create custom implementations for specialized behavior:

public class CustomJpaOperations extends AbstractJpaOperations {

    @Override
    public void persist(Object entity) {
        // Custom pre-persist logic
        validateEntity(entity);
        super.persist(entity);
        // Custom post-persist logic
        auditPersistence(entity);
    }

    @Override
    public <T> T merge(T entity) {
        // Custom pre-merge logic
        validateEntity(entity);
        T result = super.merge(entity);
        // Custom post-merge logic
        auditMerge(result);
        return result;
    }

    private void validateEntity(Object entity) {
        // Validation logic
    }

    private void auditPersistence(Object entity) {
        // Audit logging
    }

    private void auditMerge(Object entity) {
        // Audit logging
    }
}

Usage:

@Bean
public JpaOperations customJpaOperations(EntityManagerFactory emf) {
    CustomJpaOperations operations = new CustomJpaOperations();
    operations.setEntityManagerFactory(emf);
    operations.afterPropertiesSet();
    return operations;
}

Transaction Management

JpaOperations methods participate in transactions. Use Spring's transaction management:

@Service
public class StudentService {

    private final JpaOperations jpaOperations;

    public StudentService(JpaOperations jpaOperations) {
        this.jpaOperations = jpaOperations;
    }

    @Transactional
    public void enrollStudent(Student student) {
        jpaOperations.persist(student);
        jpaOperations.flush();
        // Transaction commits on method exit
    }

    @Transactional
    public void updateGrades(List<Student> students) {
        List<?> merged = jpaOperations.merge(students);
        jpaOperations.flush();
    }

    @Transactional(readOnly = true)
    public List<?> findActiveStudents() {
        return jpaOperations.getResultListForQuery(
            "SELECT s FROM Student s WHERE s.active = true",
            0,
            100
        );
    }
}

Method Details

Persistence Methods

persist(Object entity)

  • Persists new entity
  • Throws exception if entity already exists
  • Entity becomes managed
  • ID is generated if applicable
  • Note: entity can be an Iterable, in which case each element is persisted

persist(Object entity, int flushSize, boolean clearOnFlush)

  • Persists new entity with flush and clear options
  • flushSize: if > 0, flush after every N operations
  • clearOnFlush: if true, clear persistence context after flush
  • Note: entity can be an Iterable, in which case each element is persisted

merge(Object entity)

  • Updates existing entity or creates new one
  • Returns managed instance (may be different from input)
  • Works with detached entities
  • Returns Object (nullable)
  • Note: entity can be an Iterable, in which case each element is merged and returned collection contains merged entities

merge(Object entity, int flushSize, boolean clearOnFlush)

  • Merges entity with flush and clear options
  • flushSize: if > 0, flush after every N operations
  • clearOnFlush: if true, clear persistence context after flush
  • Returns managed instance (nullable)
  • Note: entity can be an Iterable, in which case each element is merged

delete(Object entity)

  • Removes entity from database
  • Entity must be managed or merged first

deleteInBatch(Iterable<?> entities)

  • Batch delete using single DELETE statement
  • More efficient than individual deletes
  • Returns void (does not return count)

Retrieval Methods

find(Class<T> entityClass, Object primaryKey)

  • Finds entity by primary key
  • Returns null if not found
  • Most efficient retrieval method

getResultListForClass(Class<?> entityClass, int firstResult, int maxNumberOfResults)

  • Retrieves all entities of specified class
  • Supports pagination via firstResult and maxNumberOfResults

getResultListForQuery(String query, ParameterSource source)

  • Executes JPQL SELECT query with parameters
  • Returns list of results
  • No pagination applied

getResultListForQuery(String query, ParameterSource source, int firstResult, int maxNumberOfResults)

  • Executes JPQL SELECT query with parameters and pagination
  • Returns list of results
  • Supports pagination via firstResult and maxNumberOfResults

getSingleResultForQuery(String query, ParameterSource source)

  • Executes JPQL query expecting single result
  • Throws exception if multiple results found
  • Returns the single result

getResultListForNamedQuery(String namedQuery, ParameterSource source, int firstResult, int maxNumberOfResults)

  • Executes named query defined on entity
  • source: parameter source for query parameters
  • Supports pagination

getResultListForNativeQuery(String nativeQuery, Class<?> entityClass, ParameterSource source, int firstResult, int maxNumberOfResults)

  • Executes native SQL query
  • Requires entityClass for result mapping
  • source: parameter source for query parameters
  • Supports pagination

Update Methods

executeUpdate(String updateQuery, ParameterSource source)

  • Executes JPQL UPDATE or DELETE query
  • source: parameter source for query parameters
  • Returns count of affected rows

executeUpdateWithNamedQuery(String namedQuery, ParameterSource source)

  • Executes named update query
  • source: parameter source for query parameters
  • Returns count of affected rows

executeUpdateWithNativeQuery(String nativeQuery, ParameterSource source)

  • Executes native SQL UPDATE or DELETE
  • source: parameter source for query parameters
  • Returns count of affected rows

EntityManager Operations

flush()

  • Forces synchronization with database
  • Executes pending INSERT, UPDATE, DELETE statements
  • Use when immediate persistence is required

Error Handling

JpaOperations methods can throw various JPA exceptions:

try {
    jpaOperations.persist(student);
} catch (EntityExistsException e) {
    // Entity already exists
    logger.error("Duplicate entity", e);
} catch (PersistenceException e) {
    // General persistence error
    logger.error("Persistence failed", e);
}

Performance Considerations

Batch Operations

The persist() and merge() methods accept Iterable objects. Use batch flushing for multiple entities:

// Good - persist with batch flushing
for (Student student : students) {
    jpaOperations.persist(student, 100, true);  // Flush every 100, clear on flush
}

// Alternative - pass Iterable directly
jpaOperations.persist(students);  // Students is an Iterable

Flush Strategically

// For bulk operations, flush periodically
List<Student> largeList = ...;
for (int i = 0; i < largeList.size(); i++) {
    jpaOperations.persist(largeList.get(i));
    if (i % 100 == 0) {
        jpaOperations.flush();
        // Consider clearing persistence context here if needed
    }
}
jpaOperations.flush();  // Final flush

Use find() for Single Entity Retrieval

// Good - optimized lookup
Student student = jpaOperations.find(Student.class, studentId);

// Less efficient
Object result = jpaOperations.getSingleResultForQuery(
    "SELECT s FROM Student s WHERE s.id = " + studentId
);

Pagination

Always use pagination for potentially large result sets:

List<?> page = jpaOperations.getResultListForQuery(
    "SELECT s FROM Student s",
    pageNum * pageSize,  // firstResult
    pageSize             // maxResults
);

Thread Safety

DefaultJpaOperations is thread-safe when using EntityManagerFactory. When using a single EntityManager, ensure proper thread confinement.

Testing

Mock JpaOperations for unit testing:

@Test
public void testStudentService() {
    JpaOperations mockOps = Mockito.mock(JpaOperations.class);
    StudentService service = new StudentService(mockOps);

    Student student = new Student("John", "Doe");
    service.enrollStudent(student);

    verify(mockOps).persist(student);
    verify(mockOps).flush();
}

Install with Tessl CLI

npx tessl i tessl/maven-spring-integration-jpa

docs

inbound-adapter.md

index.md

jpa-executor.md

jpa-operations.md

outbound-adapter.md

parameter-sources.md

retrieving-gateway.md

updating-gateway.md

xml-configuration.md

tile.json