agentic-team-templates 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/package.json +1 -1
- package/src/index.js +22 -2
- package/src/index.test.js +5 -0
- package/templates/cpp-expert/.cursorrules/concurrency.md +211 -0
- package/templates/cpp-expert/.cursorrules/error-handling.md +170 -0
- package/templates/cpp-expert/.cursorrules/memory-and-ownership.md +220 -0
- package/templates/cpp-expert/.cursorrules/modern-cpp.md +211 -0
- package/templates/cpp-expert/.cursorrules/overview.md +87 -0
- package/templates/cpp-expert/.cursorrules/performance.md +223 -0
- package/templates/cpp-expert/.cursorrules/testing.md +230 -0
- package/templates/cpp-expert/.cursorrules/tooling.md +312 -0
- package/templates/cpp-expert/CLAUDE.md +242 -0
- package/templates/csharp-expert/.cursorrules/aspnet-core.md +311 -0
- package/templates/csharp-expert/.cursorrules/async-patterns.md +206 -0
- package/templates/csharp-expert/.cursorrules/dependency-injection.md +206 -0
- package/templates/csharp-expert/.cursorrules/error-handling.md +235 -0
- package/templates/csharp-expert/.cursorrules/language-features.md +204 -0
- package/templates/csharp-expert/.cursorrules/overview.md +92 -0
- package/templates/csharp-expert/.cursorrules/performance.md +251 -0
- package/templates/csharp-expert/.cursorrules/testing.md +282 -0
- package/templates/csharp-expert/.cursorrules/tooling.md +254 -0
- package/templates/csharp-expert/CLAUDE.md +360 -0
- package/templates/java-expert/.cursorrules/concurrency.md +209 -0
- package/templates/java-expert/.cursorrules/error-handling.md +205 -0
- package/templates/java-expert/.cursorrules/modern-java.md +216 -0
- package/templates/java-expert/.cursorrules/overview.md +81 -0
- package/templates/java-expert/.cursorrules/performance.md +239 -0
- package/templates/java-expert/.cursorrules/persistence.md +262 -0
- package/templates/java-expert/.cursorrules/spring-boot.md +262 -0
- package/templates/java-expert/.cursorrules/testing.md +272 -0
- package/templates/java-expert/.cursorrules/tooling.md +301 -0
- package/templates/java-expert/CLAUDE.md +325 -0
- package/templates/javascript-expert/.cursorrules/overview.md +5 -3
- package/templates/javascript-expert/.cursorrules/typescript-deep-dive.md +348 -0
- package/templates/javascript-expert/CLAUDE.md +34 -3
- package/templates/kotlin-expert/.cursorrules/coroutines.md +237 -0
- package/templates/kotlin-expert/.cursorrules/error-handling.md +149 -0
- package/templates/kotlin-expert/.cursorrules/frameworks.md +227 -0
- package/templates/kotlin-expert/.cursorrules/language-features.md +231 -0
- package/templates/kotlin-expert/.cursorrules/overview.md +77 -0
- package/templates/kotlin-expert/.cursorrules/performance.md +185 -0
- package/templates/kotlin-expert/.cursorrules/testing.md +213 -0
- package/templates/kotlin-expert/.cursorrules/tooling.md +258 -0
- package/templates/kotlin-expert/CLAUDE.md +276 -0
- package/templates/swift-expert/.cursorrules/concurrency.md +230 -0
- package/templates/swift-expert/.cursorrules/error-handling.md +213 -0
- package/templates/swift-expert/.cursorrules/language-features.md +246 -0
- package/templates/swift-expert/.cursorrules/overview.md +88 -0
- package/templates/swift-expert/.cursorrules/performance.md +260 -0
- package/templates/swift-expert/.cursorrules/swiftui.md +260 -0
- package/templates/swift-expert/.cursorrules/testing.md +286 -0
- package/templates/swift-expert/.cursorrules/tooling.md +285 -0
- package/templates/swift-expert/CLAUDE.md +275 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Java Expert Overview
|
|
2
|
+
|
|
3
|
+
Principal-level Java engineering. Deep JVM knowledge, modern language features, and production-grade patterns.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
This guide applies to:
|
|
8
|
+
- Web services and APIs (Spring Boot, Quarkus, Micronaut)
|
|
9
|
+
- Microservices and distributed systems
|
|
10
|
+
- Event-driven architectures (Kafka, RabbitMQ)
|
|
11
|
+
- Batch processing and data pipelines
|
|
12
|
+
- Libraries and Maven/Gradle artifacts
|
|
13
|
+
- Cloud-native applications (Kubernetes, GraalVM native images)
|
|
14
|
+
|
|
15
|
+
## Core Philosophy
|
|
16
|
+
|
|
17
|
+
Java's strength is its ecosystem maturity and runtime reliability. The best Java code is clear, testable, and boring.
|
|
18
|
+
|
|
19
|
+
- **Readability over cleverness.** A junior engineer should understand your code without a tutorial.
|
|
20
|
+
- **Immutability by default.** Records, `final` fields, unmodifiable collections. Mutation is the exception.
|
|
21
|
+
- **Composition over inheritance.** Interfaces, delegation, and dependency injection — not deep class hierarchies.
|
|
22
|
+
- **The JVM is your ally.** Understand garbage collection, JIT compilation, and memory model — don't fight them.
|
|
23
|
+
- **Fail fast, fail loud.** Validate at boundaries, throw meaningful exceptions, never swallow errors.
|
|
24
|
+
- **If you don't know, say so.** Admitting uncertainty is professional. Guessing at JVM behavior you haven't verified is not.
|
|
25
|
+
|
|
26
|
+
## Key Principles
|
|
27
|
+
|
|
28
|
+
1. **Modern Java Is Required** — Java 21+ features: records, sealed classes, pattern matching, virtual threads
|
|
29
|
+
2. **Null Is a Bug** — Use `Optional` for return types, `@Nullable`/`@NonNull` annotations, and validation at boundaries
|
|
30
|
+
3. **Dependency Injection Is the Architecture** — Constructor injection, interface segregation, Spring's application context
|
|
31
|
+
4. **Tests Are Documentation** — Descriptive names, Arrange-Act-Assert, behavior over implementation
|
|
32
|
+
5. **Observability Is Not Optional** — Structured logging, metrics, distributed tracing from day one
|
|
33
|
+
|
|
34
|
+
## Project Structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
project/
|
|
38
|
+
├── src/main/java/com/example/myapp/
|
|
39
|
+
│ ├── Application.java # Entry point
|
|
40
|
+
│ ├── config/ # Configuration classes
|
|
41
|
+
│ ├── domain/ # Core domain (no framework deps)
|
|
42
|
+
│ │ ├── model/ # Entities, value objects, records
|
|
43
|
+
│ │ ├── service/ # Domain services
|
|
44
|
+
│ │ └── event/ # Domain events
|
|
45
|
+
│ ├── application/ # Use cases, orchestration
|
|
46
|
+
│ │ ├── command/ # Write operations
|
|
47
|
+
│ │ ├── query/ # Read operations
|
|
48
|
+
│ │ └── port/ # Interfaces (driven/driving)
|
|
49
|
+
│ ├── infrastructure/ # External concerns
|
|
50
|
+
│ │ ├── persistence/ # JPA repositories, entity mappings
|
|
51
|
+
│ │ ├── messaging/ # Kafka/RabbitMQ producers/consumers
|
|
52
|
+
│ │ └── client/ # HTTP clients, external APIs
|
|
53
|
+
│ └── api/ # REST controllers, DTOs
|
|
54
|
+
│ ├── controller/
|
|
55
|
+
│ ├── dto/
|
|
56
|
+
│ └── exception/ # Exception handlers
|
|
57
|
+
├── src/main/resources/
|
|
58
|
+
│ ├── application.yml
|
|
59
|
+
│ └── db/migration/ # Flyway/Liquibase migrations
|
|
60
|
+
├── src/test/java/com/example/myapp/
|
|
61
|
+
│ ├── unit/
|
|
62
|
+
│ ├── integration/
|
|
63
|
+
│ └── architecture/
|
|
64
|
+
├── pom.xml or build.gradle.kts
|
|
65
|
+
└── Dockerfile
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Definition of Done
|
|
69
|
+
|
|
70
|
+
A Java feature is complete when:
|
|
71
|
+
|
|
72
|
+
- [ ] Code compiles with zero warnings (`-Xlint:all -Werror`)
|
|
73
|
+
- [ ] All tests pass (`mvn verify` or `gradle check`)
|
|
74
|
+
- [ ] No SpotBugs/ErrorProne findings
|
|
75
|
+
- [ ] No SonarQube code smells or security hotspots
|
|
76
|
+
- [ ] Null safety enforced (no raw `null` returns from public APIs)
|
|
77
|
+
- [ ] Javadoc on all public classes and methods
|
|
78
|
+
- [ ] Error paths are tested
|
|
79
|
+
- [ ] Thread safety verified for shared mutable state
|
|
80
|
+
- [ ] No `TODO` without an associated issue
|
|
81
|
+
- [ ] Code reviewed and approved
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Java Performance
|
|
2
|
+
|
|
3
|
+
The JVM is a world-class runtime. Understand it, don't fight it. Measure first.
|
|
4
|
+
|
|
5
|
+
## Profile Before Optimizing
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# JFR (Java Flight Recorder) — production-safe profiling
|
|
9
|
+
java -XX:StartFlightRecording=filename=recording.jfr,duration=60s -jar app.jar
|
|
10
|
+
|
|
11
|
+
# Async-profiler — low-overhead sampling
|
|
12
|
+
./asprof -d 30 -f profile.html <pid>
|
|
13
|
+
|
|
14
|
+
# JVM flags for diagnostics
|
|
15
|
+
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xlog:gc*:file=gc.log
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Diagnostic Tools
|
|
19
|
+
|
|
20
|
+
| Tool | Purpose |
|
|
21
|
+
|------|---------|
|
|
22
|
+
| JFR + JMC | Production profiling (CPU, memory, I/O, locks) |
|
|
23
|
+
| async-profiler | Low-overhead CPU/allocation profiling |
|
|
24
|
+
| JMH | Micro-benchmarks with statistical rigor |
|
|
25
|
+
| jcmd | Runtime diagnostics (heap dump, thread dump) |
|
|
26
|
+
| VisualVM | Real-time monitoring |
|
|
27
|
+
|
|
28
|
+
## JMH Benchmarks
|
|
29
|
+
|
|
30
|
+
```java
|
|
31
|
+
@BenchmarkMode(Mode.AverageTime)
|
|
32
|
+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
|
33
|
+
@State(Scope.Benchmark)
|
|
34
|
+
@Warmup(iterations = 5, time = 1)
|
|
35
|
+
@Measurement(iterations = 5, time = 1)
|
|
36
|
+
public class StringConcatBenchmark {
|
|
37
|
+
|
|
38
|
+
private List<String> items;
|
|
39
|
+
|
|
40
|
+
@Setup
|
|
41
|
+
public void setup() {
|
|
42
|
+
items = IntStream.range(0, 1000)
|
|
43
|
+
.mapToObj(Integer::toString)
|
|
44
|
+
.toList();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Benchmark
|
|
48
|
+
public String concatenation() {
|
|
49
|
+
var result = "";
|
|
50
|
+
for (var item : items) result += item;
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@Benchmark
|
|
55
|
+
public String stringBuilder() {
|
|
56
|
+
var sb = new StringBuilder();
|
|
57
|
+
for (var item : items) sb.append(item);
|
|
58
|
+
return sb.toString();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Benchmark
|
|
62
|
+
public String stringJoin() {
|
|
63
|
+
return String.join("", items);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## GC Tuning
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# G1GC (default, good for most workloads)
|
|
72
|
+
-XX:+UseG1GC
|
|
73
|
+
-XX:MaxGCPauseMillis=200
|
|
74
|
+
|
|
75
|
+
# ZGC (ultra-low latency, Java 21+ production-ready)
|
|
76
|
+
-XX:+UseZGC
|
|
77
|
+
-XX:+ZGenerational # Generational ZGC (Java 21+)
|
|
78
|
+
|
|
79
|
+
# Heap sizing
|
|
80
|
+
-Xms2g -Xmx2g # Fixed heap size (no resize pauses)
|
|
81
|
+
-XX:+AlwaysPreTouch # Touch memory pages at startup
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### GC Rules
|
|
85
|
+
|
|
86
|
+
- Start with G1GC defaults — they're good
|
|
87
|
+
- Use ZGC if you need < 1ms pause times
|
|
88
|
+
- Set `-Xms` = `-Xmx` for predictable behavior
|
|
89
|
+
- Monitor with JFR, not gut feeling
|
|
90
|
+
|
|
91
|
+
## Memory Patterns
|
|
92
|
+
|
|
93
|
+
### Avoid Unnecessary Allocations
|
|
94
|
+
|
|
95
|
+
```java
|
|
96
|
+
// Bad: autoboxing in hot loops
|
|
97
|
+
long sum = 0;
|
|
98
|
+
for (Integer value : integerList) {
|
|
99
|
+
sum += value; // Unboxing on every iteration
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Good: use primitive streams
|
|
103
|
+
long sum = integerList.stream().mapToLong(Integer::longValue).sum();
|
|
104
|
+
|
|
105
|
+
// Bad: intermediate collections
|
|
106
|
+
var result = users.stream()
|
|
107
|
+
.map(User::name)
|
|
108
|
+
.collect(Collectors.toList()) // Intermediate list
|
|
109
|
+
.stream()
|
|
110
|
+
.filter(n -> n.startsWith("A"))
|
|
111
|
+
.toList();
|
|
112
|
+
|
|
113
|
+
// Good: single pipeline
|
|
114
|
+
var result = users.stream()
|
|
115
|
+
.map(User::name)
|
|
116
|
+
.filter(n -> n.startsWith("A"))
|
|
117
|
+
.toList();
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### String Optimization
|
|
121
|
+
|
|
122
|
+
```java
|
|
123
|
+
// String.intern() for repeated strings (use carefully — fills PermGen/Metaspace)
|
|
124
|
+
// Better: use enum or constants for known string sets
|
|
125
|
+
|
|
126
|
+
// StringBuilder for complex concatenation
|
|
127
|
+
var sb = new StringBuilder(256); // Pre-size if length is known
|
|
128
|
+
for (var item : items) {
|
|
129
|
+
sb.append(item.name()).append(": ").append(item.value()).append('\n');
|
|
130
|
+
}
|
|
131
|
+
return sb.toString();
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Collection Sizing
|
|
135
|
+
|
|
136
|
+
```java
|
|
137
|
+
// Pre-size collections when capacity is known
|
|
138
|
+
var map = new HashMap<String, User>(expectedSize * 4 / 3 + 1); // Account for load factor
|
|
139
|
+
var list = new ArrayList<User>(expectedSize);
|
|
140
|
+
|
|
141
|
+
// Use specialized collections
|
|
142
|
+
EnumMap<Status, List<Order>> byStatus = new EnumMap<>(Status.class);
|
|
143
|
+
EnumSet<Permission> permissions = EnumSet.of(READ, WRITE);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Connection Pooling (HikariCP)
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
spring:
|
|
150
|
+
datasource:
|
|
151
|
+
hikari:
|
|
152
|
+
maximum-pool-size: 10 # Default is fine for most apps
|
|
153
|
+
minimum-idle: 5
|
|
154
|
+
idle-timeout: 300000 # 5 minutes
|
|
155
|
+
connection-timeout: 30000 # 30 seconds
|
|
156
|
+
max-lifetime: 1800000 # 30 minutes
|
|
157
|
+
leak-detection-threshold: 60000 # 1 minute — detect leaked connections
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Pool Sizing Rule
|
|
161
|
+
|
|
162
|
+
Formula: `connections = (core_count * 2) + effective_spindle_count`
|
|
163
|
+
For SSDs: `connections ≈ core_count * 2`
|
|
164
|
+
|
|
165
|
+
Most apps need 10-20 connections. More is rarely better.
|
|
166
|
+
|
|
167
|
+
## HTTP Client Performance
|
|
168
|
+
|
|
169
|
+
```java
|
|
170
|
+
// Shared HttpClient instance with connection pooling
|
|
171
|
+
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
|
|
172
|
+
.connectTimeout(Duration.ofSeconds(5))
|
|
173
|
+
.executor(Executors.newVirtualThreadPerTaskExecutor())
|
|
174
|
+
.build();
|
|
175
|
+
|
|
176
|
+
// Or with Spring: RestClient / WebClient (reuse instances)
|
|
177
|
+
@Bean
|
|
178
|
+
public RestClient restClient(RestClient.Builder builder) {
|
|
179
|
+
return builder
|
|
180
|
+
.baseUrl("https://api.example.com")
|
|
181
|
+
.requestFactory(new JdkClientHttpRequestFactory(HTTP_CLIENT))
|
|
182
|
+
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
|
183
|
+
.build();
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Caching
|
|
188
|
+
|
|
189
|
+
```java
|
|
190
|
+
// Spring Cache with Caffeine (in-process)
|
|
191
|
+
@Configuration
|
|
192
|
+
@EnableCaching
|
|
193
|
+
public class CacheConfig {
|
|
194
|
+
@Bean
|
|
195
|
+
public CacheManager cacheManager() {
|
|
196
|
+
var caffeine = Caffeine.newBuilder()
|
|
197
|
+
.maximumSize(10_000)
|
|
198
|
+
.expireAfterWrite(Duration.ofMinutes(10))
|
|
199
|
+
.recordStats();
|
|
200
|
+
|
|
201
|
+
var manager = new CaffeineCacheManager();
|
|
202
|
+
manager.setCaffeine(caffeine);
|
|
203
|
+
return manager;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@Cacheable(value = "users", key = "#id")
|
|
208
|
+
public Optional<User> findById(UUID id) {
|
|
209
|
+
return userRepository.findById(id);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@CacheEvict(value = "users", key = "#user.id")
|
|
213
|
+
public void update(User user) {
|
|
214
|
+
userRepository.save(user);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Anti-Patterns
|
|
219
|
+
|
|
220
|
+
```java
|
|
221
|
+
// Never: premature optimization without profiling
|
|
222
|
+
// "I think this is slow" — prove it with JMH or JFR
|
|
223
|
+
|
|
224
|
+
// Never: creating threads manually
|
|
225
|
+
new Thread(() -> process(item)).start(); // No lifecycle management, no error handling
|
|
226
|
+
// Use ExecutorService or virtual threads
|
|
227
|
+
|
|
228
|
+
// Never: synchronizing on string literals
|
|
229
|
+
synchronized ("lock") { } // String interning means unexpected sharing
|
|
230
|
+
// Use: private final Object lock = new Object();
|
|
231
|
+
|
|
232
|
+
// Never: reflection in hot paths
|
|
233
|
+
method.invoke(target, args); // Orders of magnitude slower than direct calls
|
|
234
|
+
// Use interfaces and polymorphism
|
|
235
|
+
|
|
236
|
+
// Never: unbounded caches
|
|
237
|
+
private final Map<String, Object> cache = new HashMap<>(); // Grows forever → OOM
|
|
238
|
+
// Use Caffeine with eviction policies
|
|
239
|
+
```
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Java Persistence
|
|
2
|
+
|
|
3
|
+
JPA/Hibernate done right, JDBC when you need control, and database patterns that scale.
|
|
4
|
+
|
|
5
|
+
## JPA Entity Design
|
|
6
|
+
|
|
7
|
+
```java
|
|
8
|
+
@Entity
|
|
9
|
+
@Table(name = "orders")
|
|
10
|
+
public class Order {
|
|
11
|
+
|
|
12
|
+
@Id
|
|
13
|
+
@GeneratedValue(strategy = GenerationType.UUID)
|
|
14
|
+
private UUID id;
|
|
15
|
+
|
|
16
|
+
@Column(nullable = false)
|
|
17
|
+
private String customerId;
|
|
18
|
+
|
|
19
|
+
@Enumerated(EnumType.STRING)
|
|
20
|
+
@Column(nullable = false)
|
|
21
|
+
private OrderStatus status;
|
|
22
|
+
|
|
23
|
+
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
|
|
24
|
+
private List<OrderItem> items = new ArrayList<>();
|
|
25
|
+
|
|
26
|
+
@Version
|
|
27
|
+
private Long version; // Optimistic locking
|
|
28
|
+
|
|
29
|
+
@CreationTimestamp
|
|
30
|
+
private Instant createdAt;
|
|
31
|
+
|
|
32
|
+
@UpdateTimestamp
|
|
33
|
+
private Instant updatedAt;
|
|
34
|
+
|
|
35
|
+
// Protected no-arg constructor for JPA
|
|
36
|
+
protected Order() {}
|
|
37
|
+
|
|
38
|
+
// Factory method with validation
|
|
39
|
+
public static Order create(String customerId, List<OrderItem> items) {
|
|
40
|
+
Objects.requireNonNull(customerId, "customerId is required");
|
|
41
|
+
if (items.isEmpty()) throw new IllegalArgumentException("Order must have items");
|
|
42
|
+
|
|
43
|
+
var order = new Order();
|
|
44
|
+
order.customerId = customerId;
|
|
45
|
+
order.status = OrderStatus.PENDING;
|
|
46
|
+
items.forEach(order::addItem);
|
|
47
|
+
return order;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public void addItem(OrderItem item) {
|
|
51
|
+
items.add(item);
|
|
52
|
+
item.setOrder(this);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Getters only — no public setters for domain-relevant fields
|
|
56
|
+
public UUID getId() { return id; }
|
|
57
|
+
public OrderStatus getStatus() { return status; }
|
|
58
|
+
public List<OrderItem> getItems() { return Collections.unmodifiableList(items); }
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Repository Pattern
|
|
63
|
+
|
|
64
|
+
```java
|
|
65
|
+
// Spring Data JPA — let the framework generate implementations
|
|
66
|
+
public interface OrderRepository extends JpaRepository<Order, UUID> {
|
|
67
|
+
|
|
68
|
+
// Derived query
|
|
69
|
+
List<Order> findByCustomerIdAndStatus(String customerId, OrderStatus status);
|
|
70
|
+
|
|
71
|
+
// JPQL for complex queries
|
|
72
|
+
@Query("""
|
|
73
|
+
SELECT o FROM Order o
|
|
74
|
+
JOIN FETCH o.items
|
|
75
|
+
WHERE o.customerId = :customerId
|
|
76
|
+
ORDER BY o.createdAt DESC
|
|
77
|
+
""")
|
|
78
|
+
List<Order> findWithItemsByCustomerId(@Param("customerId") String customerId);
|
|
79
|
+
|
|
80
|
+
// Native query when JPQL isn't enough
|
|
81
|
+
@Query(value = """
|
|
82
|
+
SELECT o.* FROM orders o
|
|
83
|
+
WHERE o.created_at > :since
|
|
84
|
+
AND o.status = 'PENDING'
|
|
85
|
+
FOR UPDATE SKIP LOCKED
|
|
86
|
+
LIMIT :limit
|
|
87
|
+
""", nativeQuery = true)
|
|
88
|
+
List<Order> findPendingForProcessing(
|
|
89
|
+
@Param("since") Instant since,
|
|
90
|
+
@Param("limit") int limit);
|
|
91
|
+
|
|
92
|
+
// Projections for read-only queries
|
|
93
|
+
@Query("""
|
|
94
|
+
SELECT new com.example.dto.OrderSummary(o.id, o.status, o.createdAt, SIZE(o.items))
|
|
95
|
+
FROM Order o
|
|
96
|
+
WHERE o.customerId = :customerId
|
|
97
|
+
""")
|
|
98
|
+
Page<OrderSummary> findSummariesByCustomerId(
|
|
99
|
+
@Param("customerId") String customerId, Pageable pageable);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Transaction Management
|
|
104
|
+
|
|
105
|
+
```java
|
|
106
|
+
@Service
|
|
107
|
+
@RequiredArgsConstructor
|
|
108
|
+
public class OrderService {
|
|
109
|
+
|
|
110
|
+
private final OrderRepository orderRepository;
|
|
111
|
+
private final PaymentService paymentService;
|
|
112
|
+
private final TransactionTemplate txTemplate;
|
|
113
|
+
|
|
114
|
+
// Declarative — for simple cases
|
|
115
|
+
@Transactional
|
|
116
|
+
public Order create(CreateOrderRequest request) {
|
|
117
|
+
var order = Order.create(request.customerId(), request.items());
|
|
118
|
+
return orderRepository.save(order);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Read-only transactions — enables optimizations
|
|
122
|
+
@Transactional(readOnly = true)
|
|
123
|
+
public Optional<Order> findById(UUID id) {
|
|
124
|
+
return orderRepository.findById(id);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Programmatic — for fine-grained control
|
|
128
|
+
public OrderResult processOrder(UUID orderId) {
|
|
129
|
+
// Step 1: Update order status (transactional)
|
|
130
|
+
var order = txTemplate.execute(status -> {
|
|
131
|
+
var o = orderRepository.findById(orderId)
|
|
132
|
+
.orElseThrow(() -> new NotFoundException("Order: " + orderId));
|
|
133
|
+
o.markProcessing();
|
|
134
|
+
return orderRepository.save(o);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Step 2: Call external payment (non-transactional)
|
|
138
|
+
var paymentResult = paymentService.charge(order);
|
|
139
|
+
|
|
140
|
+
// Step 3: Update with result (new transaction)
|
|
141
|
+
return txTemplate.execute(status -> {
|
|
142
|
+
if (paymentResult.isSuccess()) {
|
|
143
|
+
order.markPaid(paymentResult.transactionId());
|
|
144
|
+
} else {
|
|
145
|
+
order.markFailed(paymentResult.errorMessage());
|
|
146
|
+
}
|
|
147
|
+
orderRepository.save(order);
|
|
148
|
+
return OrderResult.from(order);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## N+1 Query Prevention
|
|
155
|
+
|
|
156
|
+
```java
|
|
157
|
+
// Bad: lazy loading in a loop
|
|
158
|
+
var orders = orderRepository.findAll(); // 1 query
|
|
159
|
+
for (var order : orders) {
|
|
160
|
+
order.getItems().size(); // N queries!
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Good: JOIN FETCH
|
|
164
|
+
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.status = :status")
|
|
165
|
+
List<Order> findWithItemsByStatus(@Param("status") OrderStatus status);
|
|
166
|
+
|
|
167
|
+
// Good: @EntityGraph
|
|
168
|
+
@EntityGraph(attributePaths = {"items", "customer"})
|
|
169
|
+
List<Order> findByStatus(OrderStatus status);
|
|
170
|
+
|
|
171
|
+
// Good: DTO projection (best performance)
|
|
172
|
+
@Query("""
|
|
173
|
+
SELECT new com.example.dto.OrderSummary(o.id, o.status, SIZE(o.items))
|
|
174
|
+
FROM Order o WHERE o.status = :status
|
|
175
|
+
""")
|
|
176
|
+
List<OrderSummary> findSummariesByStatus(@Param("status") OrderStatus status);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Database Migrations (Flyway)
|
|
180
|
+
|
|
181
|
+
```sql
|
|
182
|
+
-- V1__create_orders_table.sql
|
|
183
|
+
CREATE TABLE orders (
|
|
184
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
185
|
+
customer_id VARCHAR(255) NOT NULL,
|
|
186
|
+
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
|
187
|
+
version BIGINT NOT NULL DEFAULT 0,
|
|
188
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
189
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
|
|
193
|
+
CREATE INDEX idx_orders_status ON orders(status);
|
|
194
|
+
|
|
195
|
+
-- V2__add_order_items_table.sql
|
|
196
|
+
CREATE TABLE order_items (
|
|
197
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
198
|
+
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
|
|
199
|
+
sku VARCHAR(100) NOT NULL,
|
|
200
|
+
quantity INT NOT NULL CHECK (quantity > 0),
|
|
201
|
+
price NUMERIC(10, 2) NOT NULL CHECK (price >= 0)
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## JDBC Template (When JPA Is Overkill)
|
|
208
|
+
|
|
209
|
+
```java
|
|
210
|
+
// For bulk operations, complex queries, or maximum performance
|
|
211
|
+
@Repository
|
|
212
|
+
@RequiredArgsConstructor
|
|
213
|
+
public class OrderReportRepository {
|
|
214
|
+
|
|
215
|
+
private final JdbcTemplate jdbc;
|
|
216
|
+
|
|
217
|
+
public List<DailyRevenue> getDailyRevenue(LocalDate from, LocalDate to) {
|
|
218
|
+
return jdbc.query("""
|
|
219
|
+
SELECT DATE(created_at) as day, SUM(total) as revenue, COUNT(*) as order_count
|
|
220
|
+
FROM orders
|
|
221
|
+
WHERE created_at BETWEEN ? AND ?
|
|
222
|
+
AND status = 'COMPLETED'
|
|
223
|
+
GROUP BY DATE(created_at)
|
|
224
|
+
ORDER BY day
|
|
225
|
+
""",
|
|
226
|
+
(rs, rowNum) -> new DailyRevenue(
|
|
227
|
+
rs.getDate("day").toLocalDate(),
|
|
228
|
+
rs.getBigDecimal("revenue"),
|
|
229
|
+
rs.getInt("order_count")),
|
|
230
|
+
from, to);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Anti-Patterns
|
|
236
|
+
|
|
237
|
+
```java
|
|
238
|
+
// Never: open-in-view (lazy loading in presentation layer)
|
|
239
|
+
spring.jpa.open-in-view=true // Masks N+1 problems, unclear data boundaries
|
|
240
|
+
|
|
241
|
+
// Never: entities as API responses
|
|
242
|
+
@GetMapping("/{id}")
|
|
243
|
+
public Order getById(@PathVariable UUID id) {
|
|
244
|
+
return orderRepository.findById(id).orElseThrow();
|
|
245
|
+
// Exposes internal structure, lazy loading exceptions, circular references
|
|
246
|
+
}
|
|
247
|
+
// Map to DTOs/records
|
|
248
|
+
|
|
249
|
+
// Never: manual ID generation with UUID.randomUUID() in application code
|
|
250
|
+
// Let the database or JPA strategy handle it
|
|
251
|
+
|
|
252
|
+
// Never: ignoring @Version for concurrent writes
|
|
253
|
+
// Optimistic locking prevents lost updates — use it
|
|
254
|
+
|
|
255
|
+
// Never: long-running transactions
|
|
256
|
+
@Transactional
|
|
257
|
+
public void processAllOrders() {
|
|
258
|
+
var orders = orderRepository.findAll(); // Locks for entire method
|
|
259
|
+
orders.forEach(this::processExpensiveOperation);
|
|
260
|
+
}
|
|
261
|
+
// Process in batches with separate transactions
|
|
262
|
+
```
|