بنقرة واحدة
jpa-patterns
JPA/Hibernate optimization patterns, entity design, and query performance for Spring Data
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
JPA/Hibernate optimization patterns, entity design, and query performance for Spring Data
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Use when implementing Spring Boot 3.x applications. Provides hands-on implementation patterns for web, data, security, testing, and cloud-native development.
Review REST API contracts for HTTP semantics, versioning, backward compatibility, and response consistency. Use when user asks "review API", "check endpoints", "REST review", or before releasing API changes.
Use when designing Spring Boot 3.x application architecture, making technology decisions, or planning project structure. Provides architectural patterns and references for project setup, JPA, security, testing, and reactive programming.
Use when designing, reviewing, or modifying REST API endpoints to enforce proper HTTP verbs, status codes, versioning, and API contract best practices.
Use when reviewing or writing code to enforce DRY, KISS, and YAGNI principles with Java-specific guidance.
Use when implementing or reviewing code that benefits from established design patterns like Builder, Factory, Strategy, Observer, Decorator, or Adapter.
| name | jpa-patterns |
| description | JPA/Hibernate optimization patterns, entity design, and query performance for Spring Data |
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@CreatedDate
@Column(updatable = false)
private Instant createdAt;
@LastModifiedDate
private Instant updatedAt;
@Version
private Long version;
// getters only -- immutable fields
}
@Entity
@Table(name = "orders")
public class Order extends BaseEntity {
@Column(nullable = false)
private String customerId;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private OrderStatus status = OrderStatus.DRAFT;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal totalAmount = BigDecimal.ZERO;
// Proper bidirectional relationship management
public void addItem(OrderItem item) {
items.add(item);
item.setOrder(this);
recalculateTotal();
}
public void removeItem(OrderItem item) {
items.remove(item);
item.setOrder(null);
recalculateTotal();
}
private void recalculateTotal() {
this.totalAmount = items.stream()
.map(OrderItem::getSubtotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
// equals/hashCode using business key or ID
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Order other)) return false;
return getId() != null && getId().equals(other.getId());
}
@Override
public int hashCode() {
return getClass().hashCode(); // constant for JPA entities
}
}
@Entity
@Table(name = "order_items")
public class OrderItem extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "order_id")
private Order order;
@Column(nullable = false)
private String productId;
@Column(nullable = false)
private int quantity;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal unitPrice;
public BigDecimal getSubtotal() {
return unitPrice.multiply(BigDecimal.valueOf(quantity));
}
}
// BAD: N+1 -- loads orders, then items for each order
List<Order> orders = orderRepository.findAll();
orders.forEach(o -> o.getItems().size()); // triggers N queries
// GOOD: Fetch join in repository
public interface OrderRepository extends JpaRepository<Order, String> {
@Query("SELECT o FROM Order o LEFT JOIN FETCH o.items WHERE o.status = :status")
List<Order> findByStatusWithItems(@Param("status") OrderStatus status);
// EntityGraph alternative
@EntityGraph(attributePaths = {"items"})
List<Order> findByStatus(OrderStatus status);
}
// Interface projection -- fetches only needed columns
public interface OrderSummary {
String getId();
String getCustomerId();
OrderStatus getStatus();
BigDecimal getTotalAmount();
Instant getCreatedAt();
}
public interface OrderRepository extends JpaRepository<Order, String> {
List<OrderSummary> findSummaryByCustomerId(String customerId);
// DTO projection with constructor expression
@Query("""
SELECT new com.app.dto.OrderStatsDto(
o.status,
COUNT(o),
SUM(o.totalAmount)
)
FROM Order o
WHERE o.createdAt >= :since
GROUP BY o.status
""")
List<OrderStatsDto> getOrderStats(@Param("since") Instant since);
}
// Always paginate list endpoints
@GetMapping
public ResponseEntity<Page<OrderDto>> listOrders(
@RequestParam(required = false) OrderStatus status,
@ParameterObject Pageable pageable) {
var orders = orderRepository.findByStatus(status, pageable);
return ResponseEntity.ok(orders.map(orderMapper::toDto));
}
// Custom pageable defaults
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
var resolver = new PageableHandlerMethodArgumentResolver();
resolver.setMaxPageSize(100);
resolver.setFallbackPageable(PageRequest.of(0, 20, Sort.by("createdAt").descending()));
resolvers.add(resolver);
}
}
// Batch inserts with proper configuration
// application.yml:
// spring.jpa.properties.hibernate.jdbc.batch_size: 50
// spring.jpa.properties.hibernate.order_inserts: true
// spring.jpa.properties.hibernate.order_updates: true
@Transactional
public void importProducts(List<CreateProductRequest> requests) {
var batchSize = 50;
var products = new ArrayList<Product>(batchSize);
for (int i = 0; i < requests.size(); i++) {
products.add(productMapper.toEntity(requests.get(i)));
if (products.size() >= batchSize) {
productRepository.saveAll(products);
entityManager.flush();
entityManager.clear();
products.clear();
}
}
if (!products.isEmpty()) {
productRepository.saveAll(products);
}
}
public class OrderSpecifications {
public static Specification<Order> byCustomer(String customerId) {
return (root, query, cb) ->
customerId == null ? null : cb.equal(root.get("customerId"), customerId);
}
public static Specification<Order> byStatus(OrderStatus status) {
return (root, query, cb) ->
status == null ? null : cb.equal(root.get("status"), status);
}
public static Specification<Order> createdAfter(Instant after) {
return (root, query, cb) ->
after == null ? null : cb.greaterThanOrEqualTo(root.get("createdAt"), after);
}
public static Specification<Order> amountBetween(BigDecimal min, BigDecimal max) {
return (root, query, cb) -> {
if (min == null && max == null) return null;
if (min != null && max != null) return cb.between(root.get("totalAmount"), min, max);
if (min != null) return cb.greaterThanOrEqualTo(root.get("totalAmount"), min);
return cb.lessThanOrEqualTo(root.get("totalAmount"), max);
};
}
}
// Usage in service
public Page<OrderDto> searchOrders(OrderSearchRequest request, Pageable pageable) {
var spec = Specification.where(byCustomer(request.customerId()))
.and(byStatus(request.status()))
.and(createdAfter(request.fromDate()))
.and(amountBetween(request.minAmount(), request.maxAmount()));
return orderRepository.findAll(spec, pageable).map(orderMapper::toDto);
}
FetchType.LAZY for all @ManyToOne and @OneToManyJOIN FETCH or @EntityGraph to load related data in one query@Index annotations for frequently queried columns@Transactional(readOnly = true) for queries