---
description: Java Persistence
alwaysApply: false
---

# Java Persistence

JPA/Hibernate done right, JDBC when you need control.

## JPA Entity Design

```java
@Entity @Table(name = "orders")
public class Order {
    @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id;
    @Column(nullable = false) private String customerId;
    @Enumerated(EnumType.STRING) private OrderStatus status;
    @OneToMany(mappedBy = "order", cascade = ALL, orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>();
    @Version private Long version; // Optimistic locking
    protected Order() {} // JPA no-arg
    public static Order create(String cid, List<OrderItem> items) { ... }
    public List<OrderItem> getItems() { return Collections.unmodifiableList(items); }
}
```

- Factory methods, protected no-arg constructors, `@Version`, `EnumType.STRING`
- No public setters for domain fields

## Repository

```java
public interface OrderRepository extends JpaRepository<Order, UUID> {
    @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.customerId = :cid")
    List<Order> findWithItems(@Param("cid") String cid);

    @Query("SELECT new com.example.dto.OrderSummary(o.id, o.status, SIZE(o.items)) FROM Order o")
    Page<OrderSummary> findSummaries(Pageable pageable);
}
```

## Transactions

```java
@Transactional(readOnly = true)  // Class default for reads
public Optional<Order> findById(UUID id) { ... }

@Transactional  // Override for writes
public Order create(CreateOrderRequest req) { ... }
```

## N+1 Prevention

```java
// Bad: lazy loading in loop → N queries
// Good: JOIN FETCH, @EntityGraph(attributePaths={"items"}), or DTO projections
```

## Migrations

Flyway or Liquibase. Every schema change versioned. No auto-DDL in production.

## JDBC (When JPA Is Overkill)

```java
jdbc.query("SELECT ... WHERE created_at BETWEEN ? AND ?",
    (rs, n) -> new Revenue(rs.getDate("day").toLocalDate(), rs.getBigDecimal("rev")), from, to);
```

## Anti-Patterns

- **Never** `open-in-view=true` — masks N+1, unclear boundaries
- **Never** entities as API responses — map to DTOs
- **Never** long-running transactions — batch process instead
