Install
$ agentstack add skill-vaquarkhan-fullstack-development-agent-skills-spring-data-jpa ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
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
- Confirm the change matches this skill's domain triggers before coding.
- Follow the domain guide conventions and gotchas below — not generic Spring Boot defaults.
- Apply project-specific response envelopes, DTO boundaries, and dependency injection rules.
- Validate with targeted tests (slice, integration, or contract as appropriate).
- 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 —ORDINALbreaks on enum reorderingGenerationType.UUIDfor IDs — never expose auto-increment integers@NoArgsConstructor(access = PROTECTED)— required by JPA, hidden from app code@Getterfrom Lombok — no@Setteron 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 useLAZYon@ManyToOneand@ManyToMany - Agent uses
@Enumerated(EnumType.ORDINAL)— always useSTRING - Agent uses
LongIDs — useUUID - Agent calls
findAll()for list endpoints — always usePageable - Agent uses
OFFSETpagination on huge tables — switch to keyset for deep pages - Agent adds setters to entities — use behavior methods instead
- Agent forgets
orphanRemoval = trueon@OneToMany— child records become orphans - Agent writes N+1 without realizing — check for
itemsaccess in loops - Agent batches inserts with
GenerationType.IDENTITY— batching is silently off; useUUID/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.
- Author: vaquarkhan
- Source: vaquarkhan/Fullstack-development-agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.