Spring Integration JPA Support - provides components for performing database operations using JPA
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.
Required Dependencies:
spring-integration-jpa (this package)spring-orm for JPA supportEntityManagerFactory or EntityManager must be configuredDefault Behaviors:
DefaultJpaOperations is the standard implementationflush() must be called explicitly (not automatic)persist() and merge() accept Object (can be Iterable for batch operations)List<?> or Object (nullable for single results)int (affected row count)Threading Model:
DefaultJpaOperations is thread-safe when using EntityManagerFactoryEntityManager, ensure proper thread confinementLifecycle:
afterPropertiesSet() after configurationExceptions:
PersistenceException - General JPA persistence errorsEntityExistsException - 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 configurationEdge Cases:
persist() and merge() accept Object which can be Iterable - each element is processeddeleteInBatch() 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 foundgetResultListForQuery() returns empty list if no results (not null)firstResult is 0-based, maxNumberOfResults limits result sizeentityClass for result type mapping@NamedQueryflush() forces immediate database synchronizationflushSize > 0 flushes every N operations, clearOnFlush clears contextinterface 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()
}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()
}@Bean
public JpaOperations jpaOperations(EntityManagerFactory entityManagerFactory) {
DefaultJpaOperations operations = new DefaultJpaOperations();
operations.setEntityManagerFactory(entityManagerFactory);
operations.afterPropertiesSet();
return operations;
}@Bean
public JpaOperations jpaOperations(EntityManager entityManager) {
DefaultJpaOperations operations = new DefaultJpaOperations();
operations.setEntityManager(entityManager);
operations.afterPropertiesSet();
return operations;
}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());Student detached = ...; // Detached entity
Student managed = jpaOps.merge(detached);
jpaOps.flush();Student student = ...;
jpaOps.delete(student);
jpaOps.flush();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
}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);List<Student> students = ...;
jpaOps.deleteInBatch(students);
System.out.println("Deleted students in batch");Student student = jpaOps.find(Student.class, 123L);
if (student != null) {
System.out.println("Found: " + student.getLastName());
}List<?> allStudents = jpaOps.getResultListForClass(
Student.class,
0, // firstResult
100 // maxResults
);String query = "SELECT s FROM Student s WHERE s.department = 'Engineering'";
List<?> engineers = jpaOps.getResultListForQuery(query, 0, 50);String query = "SELECT s FROM Student s WHERE s.id = 123";
Object student = jpaOps.getSingleResultForQuery(query);// Assuming @NamedQuery defined on Student entity
List<?> active = jpaOps.getResultListForNamedQuery(
"Student.findActive",
0,
100
);String nativeQuery = "SELECT * FROM students WHERE grade = 'A'";
List<?> topStudents = jpaOps.getResultListForNativeQuery(
nativeQuery,
Student.class, // Result type
0,
50
);String updateQuery = "UPDATE Student s SET s.status = 'GRADUATED' " +
"WHERE s.credits >= 120";
int updatedCount = jpaOps.executeUpdate(updateQuery);
System.out.println("Updated " + updatedCount + " students");int count = jpaOps.executeUpdateWithNamedQuery("Student.activateAll");String nativeUpdate = "UPDATE students SET last_modified = CURRENT_TIMESTAMP";
int count = jpaOps.executeUpdateWithNativeQuery(nativeUpdate);@Bean
public JpaExecutor customExecutor(JpaOperations jpaOperations) {
JpaExecutor executor = new JpaExecutor(jpaOperations);
executor.setEntityClass(Student.class);
executor.setPersistMode(PersistMode.PERSIST);
executor.afterPropertiesSet();
return executor;
}@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();
}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;
}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
);
}
}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);
}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// 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// 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
);Always use pagination for potentially large result sets:
List<?> page = jpaOperations.getResultListForQuery(
"SELECT s FROM Student s",
pageNum * pageSize, // firstResult
pageSize // maxResults
);DefaultJpaOperations is thread-safe when using EntityManagerFactory. When using a single EntityManager, ensure proper thread confinement.
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