AgentStack
SKILL verified MIT Self-run

Spring Data Jpa

skill-vaquarkhan-fullstack-development-agent-skills-spring-data-jpa · by vaquarkhan

Use when generating JPA entities, repositories, queries, or anything touching the persistence layer. Covers entity conventions, N+1 prevention, projections, and query patterns.

No reviews yet
0 installs
19 views
0.0% view→install

Install

$ agentstack add skill-vaquarkhan-fullstack-development-agent-skills-spring-data-jpa

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Spring Data Jpa? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Spring Data Jpa

Use When

  • Working with JPA entities, repositories, projections, or N+1 prevention
  • Spring Boot code generation or refactor where agent defaults would be wrong

Workflow

  1. Confirm the change matches this skill's domain triggers before coding.
  2. Follow the domain guide conventions and gotchas below — not generic Spring Boot defaults.
  3. Apply project-specific response envelopes, DTO boundaries, and dependency injection rules.
  4. Validate with targeted tests (slice, integration, or contract as appropriate).
  5. Capture evidence before merge: tests, migration notes, or observability proof.

Required Checks

  • Constructor injection used; no @Autowired field injection on new code
  • Controllers return DTOs/envelopes — never raw JPA entities
  • Business logic stays in @Service layer, not controllers or repositories
  • Error handling uses project-standard envelope or RFC 9457 ProblemDetail

Domain Guide

Entity Conventions

@Entity
@Table(name = "orders")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // JPA requires no-arg, hide from callers
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Column(updatable = false, nullable = false)
    private UUID id;

    @Column(nullable = false)
    private String customerEmail;

    @Enumerated(EnumType.STRING) // always STRING, never ORDINAL
    @Column(nullable = false)
    private OrderStatus status;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List items = new ArrayList<>();

    @CreationTimestamp
    @Column(updatable = false)
    private Instant createdAt;

    @UpdateTimestamp
    private Instant updatedAt;

    // Static factory, not public constructor
    public static Order create(String customerEmail) {
        Order order = new Order();
        order.customerEmail = customerEmail;
        order.status = OrderStatus.PENDING;
        return order;
    }

    // Behavior on entity, not in service
    public void addItem(Product product, int quantity) {
        items.add(OrderItem.create(this, product, quantity));
    }
}

Rules

  • @Enumerated(EnumType.STRING) always — ORDINAL breaks on enum reordering
  • GenerationType.UUID for IDs — never expose auto-increment integers
  • @NoArgsConstructor(access = PROTECTED) — required by JPA, hidden from app code
  • @Getter from Lombok — no @Setter on entities (use behavior methods)
  • Collections initialized inline (= new ArrayList<>()) — never null

N+1 Prevention

Identify: One query for orders + N queries for each order's items = N+1.

Fix with JOIN FETCH:

@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
Optional findByIdWithItems(@Param("id") UUID id);

// For lists — use @EntityGraph to avoid duplicates
@EntityGraph(attributePaths = {"items", "items.product"})
List findByStatus(OrderStatus status);

Fix with Projections for read-only views:

// Interface projection — no entity loaded
public interface OrderSummary {
    UUID getId();
    String getCustomerEmail();
    OrderStatus getStatus();
    Instant getCreatedAt();
}

List findByStatus(OrderStatus status); // fast, no lazy loading issues

Query Patterns

public interface OrderRepository extends JpaRepository {

    // Derived query — simple conditions
    List findByStatusAndCustomerEmail(OrderStatus status, String email);

    // JPQL — for joins and complex conditions
    @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.status = :status")
    List findActiveOrdersWithItems(@Param("status") OrderStatus status);

    // Native SQL — only when JPQL can't do it
    @Query(value = "SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '7 days'",
           nativeQuery = true)
    List findRecentOrders();

    // Exists check — faster than findById + isPresent
    boolean existsByCustomerEmailAndStatus(String email, OrderStatus status);

    // Projection
    List findByCustomerEmail(String email);
}

Pagination

// Always use Pageable for list endpoints
Page findByStatus(OrderStatus status, Pageable pageable);

// In service
Page orders = orderRepository.findByStatus(status, PageRequest.of(page, size, Sort.by("createdAt").descending()));

Bidirectional Relationships

// Parent side (Order)
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List items = new ArrayList<>();

// Child side (OrderItem) — owns the FK
@ManyToOne(fetch = FetchType.LAZY) // LAZY always on @ManyToOne
@JoinColumn(name = "order_id", nullable = false)
private Order order;

// Helper on parent to keep both sides in sync
public void addItem(OrderItem item) {
    items.add(item);
    item.setOrder(this);
}

Deep Pagination — Keyset over OFFSET

OFFSET pagination scans and discards every skipped row. On page 5,000 the DB reads 100,000 rows to return 20. For large or infinite-scroll datasets, paginate by the last seen key (the "seek" method):

// ❌ Slow on deep pages — OFFSET grows linearly
Page findByStatus(OrderStatus status, Pageable pageable);

// ✅ Keyset — constant time regardless of depth. Pass the last row's createdAt + id.
@Query("""
    SELECT o FROM Order o
    WHERE o.status = :status
      AND (o.createdAt  findNextPage(OrderStatus status, Instant lastCreatedAt, UUID lastId, Limit limit);

The (createdAt, id) tuple breaks ties so the cursor is stable when timestamps collide. Index (status, created_at DESC, id DESC).

Batch Inserts

Saving a list one row at a time is N round-trips. Enable JDBC batching so Hibernate groups them:

spring:
  jpa:
    properties:
      hibernate:
        jdbc.batch_size: 50
        order_inserts: true
        order_updates: true

Caveat: GenerationType.IDENTITY silently disables insert batching (Hibernate needs the generated key per row). GenerationType.UUID or a pooled sequence preserves it — another reason to prefer UUIDs.

Gotchas

  • Agent uses FetchType.EAGER — always use LAZY on @ManyToOne and @ManyToMany
  • Agent uses @Enumerated(EnumType.ORDINAL) — always use STRING
  • Agent uses Long IDs — use UUID
  • Agent calls findAll() for list endpoints — always use Pageable
  • Agent uses OFFSET pagination on huge tables — switch to keyset for deep pages
  • Agent adds setters to entities — use behavior methods instead
  • Agent forgets orphanRemoval = true on @OneToMany — child records become orphans
  • Agent writes N+1 without realizing — check for items access in loops
  • Agent batches inserts with GenerationType.IDENTITY — batching is silently off; use UUID/sequence

Examples And Templates

See \examples/\ for side-by-side good vs bad patterns agents commonly get wrong. See \ emplates/\ for copy-paste starters aligned with this skill.

Decision Framework

  • Prefer Spring Boot 3.x and Spring AI 1.0 GA artifact coordinates — reject pre-GA dead names.
  • Use constructor injection and immutable dependencies by default.
  • Keep domain content in services; controllers are HTTP adapters only.
  • Externalize prompts, API keys, and migration scripts — never hardcode secrets.

Common Rationalizations And Rebuttals

  • "@Autowired fields are fine for prototypes." -> Field injection hides dependencies and breaks testability; use constructor injection.
  • "The agent knows Spring Boot." -> Agents default to outdated patterns; follow this skill's gotchas and GA coordinates.
  • "We can skip Flyway for this column." -> Manual DDL drifts from environments; use versioned migrations.

Evidence Pack

  • Test output for changed endpoints, services, or migrations
  • Diff showing DTO boundaries and no entity leakage in API layer
  • Dependency or coordinate list confirming GA artifact names
  • Observability or security checklist for auth/AI changes

Exit Criteria

  • Generated code matches project layering and naming conventions
  • No pre-GA Spring AI or MCP artifact names in pom/build files
  • Tests pass for happy path and at least one failure/edge case

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.