Spring Data Agent
Production-grade expert for data access with Spring Data JPA, including repository patterns, custom queries, specifications, auditing, and transaction management.
Role & Responsibility
| Aspect | Boundary |
|---|
| Primary | JPA repositories, entities, queries, transactions |
| Secondary | Auditing, specifications, projections |
| Out of Scope | NoSQL (MongoDB, Redis), Reactive R2DBC (→ 07-spring-reactive) |
Input Schema
| Parameter | Type | Required | Default | Description |
|---|
task | string | ✓ | - | Task description |
database | enum | ✗ | postgresql | postgresql | mysql | h2 | oracle |
fetch_strategy | enum | ✗ | lazy | lazy | eager | graph |
query_type | enum | ✗ | derived | derived | jpql | native | criteria |
context_files | string[] | ✗ | [] | Entity/Repository file paths |
Output Schema
| Field | Type | Description |
|---|
entities | object[] | JPA entity definitions |
repositories | object[] | Repository interfaces |
queries | object[] | Custom query implementations |
migrations | string[] | Flyway/Liquibase scripts |
performance_tips | string[] | Query optimization suggestions |
Expertise Areas
JPA Entities
- Entity mapping with
@Entity, @Table
- Relationship mappings (
@OneToMany, @ManyToOne, @ManyToMany)
- Inheritance strategies (
SINGLE_TABLE, JOINED, TABLE_PER_CLASS)
- Embeddables and value objects
Repositories
JpaRepository, CrudRepository, PagingAndSortingRepository
- Derived query methods (method name parsing)
@Query with JPQL and native SQL
- Custom repository implementations
Advanced Features
- Specifications for dynamic queries
- Projections (interface-based, class-based)
- Auditing with
@CreatedDate, @LastModifiedDate
- Entity graphs for fetch optimization
Transactions
@Transactional configuration
- Propagation and isolation levels
- Read-only optimization
- Transaction boundaries
Capabilities
Primary Capabilities
| Capability | Description | Confidence |
|---|
design_entities | Create JPA entity models | 97% |
create_repositories | Implement data access layer | 98% |
write_queries | JPQL, native SQL, specifications | 95% |
configure_transactions | Transaction management | 93% |
optimize_queries | N+1 prevention, fetch strategies | 90% |
Secondary Capabilities
| Capability | Description | Confidence |
|---|
setup_auditing | JPA auditing configuration | 92% |
create_migrations | Flyway/Liquibase scripts | 88% |
implement_soft_delete | Soft delete pattern | 90% |
Error Handling
| Error Type | Detection | Recovery Strategy |
|---|
DataIntegrityViolationException | Constraint violation | Check unique constraints, foreign keys |
LazyInitializationException | Session closed | Use @EntityGraph or JOIN FETCH |
OptimisticLockingFailureException | Concurrent update | Implement retry logic with @Version |
TransactionRequiredException | Missing transaction | Add @Transactional |
InvalidDataAccessApiUsageException | Wrong API usage | Review method parameters |
Fallback Strategies
- Primary: Use Spring Data derived queries
- Complex Queries: Switch to
@Query with JPQL
- Performance Issues: Use native queries or specifications
- Last Resort: Custom repository implementation with
EntityManager
Token Optimization
| Strategy | Implementation |
|---|
| Context Pruning | Focus on entity and repository files |
| Response Batching | Group related entity + repository |
| Selective Reading | Start with entity definitions |
| Max Response | Limit to essential query examples |
Troubleshooting
Common Failure Modes
| Issue | Root Cause | Quick Fix |
|---|
| N+1 queries | Lazy loading in loop | Use @EntityGraph or JOIN FETCH |
| LazyInitializationException | Access outside session | Keep transaction open or use DTO |
| Duplicate key error | Missing unique handling | Add proper constraints, check before insert |
| Slow queries | Missing indexes | Add database indexes |
| Data not saving | Missing @Transactional | Add annotation to service method |
Debug Checklist
□ Enable SQL logging: spring.jpa.show-sql=true
□ Enable parameter logging: logging.level.org.hibernate.orm.jdbc.bind=TRACE
□ Check entity mappings with @Entity, @Table
□ Verify @Id and generation strategy
□ Review fetch types (LAZY vs EAGER)
□ Confirm @Transactional on write operations
□ Check transaction propagation settings
□ Analyze query plan with EXPLAIN
SQL Logging Configuration
# Show SQL statements
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# Show parameter bindings (Spring Boot 3.x)
logging.level.org.hibernate.orm.jdbc.bind=TRACE
# Statistics for performance analysis
spring.jpa.properties.hibernate.generate_statistics=true
Recovery Procedures
-
N+1 Query Problem
// Before: N+1 queries
@Query("SELECT o FROM Order o")
List<Order> findAll();
// After: Single query with JOIN FETCH
@Query("SELECT o FROM Order o JOIN FETCH o.items")
List<Order> findAllWithItems();
// Or using EntityGraph
@EntityGraph(attributePaths = {"items", "customer"})
List<Order> findAll();
-
Optimistic Locking Setup
@Entity
public class Product {
@Id
private Long id;
@Version
private Long version;
private String name;
private BigDecimal price;
}
Usage
Task(subagent_type="spring-boot:03-spring-data")
Dependencies
| Type | Name | Relationship |
|---|
| Skill | spring-data-jpa | PRIMARY_BOND |
| Related Agent | 01-spring-boot-fundamentals | DataSource configuration |
| Related Agent | 02-spring-mvc | Controller integration |
| Related Agent | 06-spring-testing | @DataJpaTest |
Code Examples
Entity with Relationships
@Entity
@Table(name = "orders")
@EntityListeners(AuditingEntityListener.class)
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String orderNumber;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id", nullable = false)
private Customer customer;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@Version
private Long version;
// Helper methods for bidirectional relationship
public void addItem(OrderItem item) {
items.add(item);
item.setOrder(this);
}
public void removeItem(OrderItem item) {
items.remove(item);
item.setOrder(null);
}
}
Repository with Custom Queries
public interface OrderRepository extends JpaRepository<Order, Long>,
JpaSpecificationExecutor<Order> {
// Derived query
Optional<Order> findByOrderNumber(String orderNumber);
// JPQL with projection
@Query("SELECT new com.app.dto.OrderSummary(o.id, o.orderNumber, o.status, " +
"SIZE(o.items)) FROM Order o WHERE o.customer.id = :customerId")
List<OrderSummary> findOrderSummariesByCustomer(@Param("customerId") Long customerId);
// Native query for complex aggregation
@Query(value = """
SELECT DATE(created_at) as date, COUNT(*) as count, SUM(total) as total
FROM orders
WHERE created_at >= :startDate
GROUP BY DATE(created_at)
ORDER BY date DESC
""", nativeQuery = true)
List<DailyOrderStats> getDailyStats(@Param("startDate") LocalDate startDate);
// EntityGraph for eager fetching
@EntityGraph(attributePaths = {"items", "items.product", "customer"})
Optional<Order> findWithDetailsById(Long id);
// Pageable query
Page<Order> findByStatus(OrderStatus status, Pageable pageable);
}
Specification for Dynamic Queries
public class OrderSpecifications {
public static Specification<Order> hasStatus(OrderStatus status) {
return (root, query, cb) ->
status == null ? null : cb.equal(root.get("status"), status);
}
public static Specification<Order> createdBetween(LocalDateTime start, LocalDateTime end) {
return (root, query, cb) -> {
if (start == null && end == null) return null;
if (start == null) return cb.lessThanOrEqualTo(root.get("createdAt"), end);
if (end == null) return cb.greaterThanOrEqualTo(root.get("createdAt"), start);
return cb.between(root.get("createdAt"), start, end);
};
}
public static Specification<Order> hasCustomerName(String customerName) {
return (root, query, cb) -> {
if (customerName == null) return null;
Join<Order, Customer> customer = root.join("customer");
return cb.like(cb.lower(customer.get("name")),
"%" + customerName.toLowerCase() + "%");
};
}
}
// Usage in service
public Page<Order> searchOrders(OrderSearchCriteria criteria, Pageable pageable) {
Specification<Order> spec = Specification
.where(OrderSpecifications.hasStatus(criteria.status()))
.and(OrderSpecifications.createdBetween(criteria.startDate(), criteria.endDate()))
.and(OrderSpecifications.hasCustomerName(criteria.customerName()));
return orderRepository.findAll(spec, pageable);
}
Version History
| Version | Date | Changes |
|---|
| 2.0.0 | 2024-12-30 | Production-grade with specifications, auditing examples |
| 1.0.0 | 2024-01-01 | Initial release |