# spring-data-jdbc
> Spring Data JDBC for simple, lightweight database access without JPA complexity.
Covers aggregates, repositories, custom queries, and DDD patterns.
USE WHEN: user mentions "spring data jdbc", "simple database access", "no JPA",
"aggregate roots", "DDD with JDBC", "lightweight ORM", "@MappedCollection"
DO NOT USE FOR: JPA/Hibernate features - use `spring-data-jpa` instead,
reactive database - use `spring-r2dbc` instead, NoSQL - use respective skills
- Author: mariepellegrino89
- Repository: claude-dev-suite/claude-dev-suite
- Version: 20260206202537
- Stars: 0
- Forks: 0
- Last Updated: 2026-02-06
- Source: https://github.com/claude-dev-suite/claude-dev-suite
- Web: https://mule.run/skillshub/@@claude-dev-suite/claude-dev-suite~spring-data-jdbc:20260206202537
---
---
name: spring-data-jdbc
description: |
Spring Data JDBC for simple, lightweight database access without JPA complexity.
Covers aggregates, repositories, custom queries, and DDD patterns.
USE WHEN: user mentions "spring data jdbc", "simple database access", "no JPA",
"aggregate roots", "DDD with JDBC", "lightweight ORM", "@MappedCollection"
DO NOT USE FOR: JPA/Hibernate features - use `spring-data-jpa` instead,
reactive database - use `spring-r2dbc` instead, NoSQL - use respective skills
allowed-tools: Read, Grep, Glob, Write, Edit
---
# Spring Data JDBC - Quick Reference
> **Full Reference**: See [advanced.md](advanced.md) for custom row mappers, ID generation, auditing, event listeners, and Testcontainers integration.
> **Deep Knowledge**: Use `mcp__documentation__fetch_docs` with technology: `spring-data-jdbc` for comprehensive documentation.
## Why Spring Data JDBC over JPA?
| Spring Data JDBC | Spring Data JPA |
|------------------|-----------------|
| No lazy loading | Lazy loading |
| No dirty checking | Auto dirty checking |
| No session/cache | First-level cache |
| Explicit SQL | Generated SQL |
| Fast startup | Slower startup |
## Dependencies
```xml
org.springframework.boot
spring-boot-starter-data-jdbc
```
## Entity Mapping
```java
@Table("users")
public class User {
@Id
private Long id;
private String username;
private String email;
@Column("created_at")
private LocalDateTime createdAt;
}
```
## Aggregate Design
```java
@Table("orders")
public class Order {
@Id
private Long id;
private Long customerId; // Reference by ID, not entity
private OrderStatus status;
@MappedCollection(idColumn = "order_id")
private Set items = new HashSet<>();
public void addItem(Long productId, int quantity, BigDecimal price) {
items.add(new OrderItem(productId, quantity, price));
}
public BigDecimal getTotal() {
return items.stream()
.map(OrderItem::getSubtotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
// Aggregate member (no @Id - lifecycle managed by root)
public class OrderItem {
private Long productId;
private int quantity;
private BigDecimal unitPrice;
public BigDecimal getSubtotal() {
return unitPrice.multiply(BigDecimal.valueOf(quantity));
}
}
```
## Repository Pattern
```java
public interface OrderRepository extends CrudRepository {
List findByCustomerId(Long customerId);
List findByStatus(OrderStatus status);
@Query("SELECT * FROM orders WHERE status = :status ORDER BY created_at DESC LIMIT :limit")
List findRecentByStatus(OrderStatus status, int limit);
@Modifying
@Query("UPDATE orders SET status = :newStatus WHERE status = :oldStatus AND created_at < :before")
int updateOldOrders(OrderStatus oldStatus, OrderStatus newStatus, LocalDateTime before);
boolean existsByCustomerIdAndStatus(Long customerId, OrderStatus status);
}
```
## Schema
```sql
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL,
status VARCHAR(50) NOT NULL
);
CREATE TABLE order_item (
order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL
);
```
## When NOT to Use This Skill
- **Need lazy loading** - Use `spring-data-jpa` for complex entity graphs
- **Reactive applications** - Use `spring-r2dbc` for non-blocking access
- **Complex ORM features** - Use JPA for second-level cache, dirty checking
## Anti-Patterns
| Anti-Pattern | Problem | Solution |
|--------------|---------|----------|
| JPA-style entity graphs | Not supported | Design proper aggregates |
| Embedding other aggregates | Coupling | Reference by ID only |
| Large aggregates | Performance issues | Keep aggregates small |
| Missing CASCADE on FK | Orphan records | Add ON DELETE CASCADE |
## Quick Troubleshooting
| Problem | Diagnostic | Fix |
|---------|------------|-----|
| Entity not saved | Check @Id generation | Configure ID callback or auto-increment |
| Children not persisted | Check @MappedCollection | Add idColumn properly |
| Column not mapped | Check naming | Use @Column for custom names |
| Transaction not working | Check @Transactional | Ensure Spring proxy |
## Best Practices
| Do | Don't |
|----|-------|
| Design proper aggregates | Use JPA-style entity graphs |
| Reference other aggregates by ID | Embed other aggregate roots |
| Keep aggregates small | Create huge aggregate graphs |
| Use immutable value objects | Mutate embedded objects directly |
## Production Checklist
- [ ] Aggregate boundaries defined
- [ ] Schema matches entity mapping
- [ ] Indexes on query columns
- [ ] Foreign keys with CASCADE
- [ ] Transaction boundaries clear
- [ ] Connection pool configured
## Reference Documentation
- [Spring Data JDBC Reference](https://docs.spring.io/spring-data/jdbc/reference/)