Data Access — Cheat Sheet
Spring Boot · 7 topics. Download the PDF or the Instagram carousel and share it.
Spring Data JPA Basics
Spring Data JPA generates repository implementations at runtime, eliminating boilerplate DAO code for standard CRUD and derived-query operations.
- ✓Extend JpaRepository<Entity, ID> — Spring generates the implementation at runtime. No @Autowired implementation class needed.
- ✓Derived query methods: Spring parses findByFieldAnd/Or/Between/In/Like… and generates the JPQL — no @Query needed for simple queries.
- ✓@Query accepts JPQL (entity/field names) or native SQL (nativeQuery=true) for complex queries.
- ✓@Modifying + @Transactional is required for @Query UPDATE and DELETE — without them Spring throws an exception.
- ✓Return Page<T> by adding a Pageable parameter to any query method — Spring Data handles the COUNT query automatically.
- ✓Projection interfaces (with getX() methods matching JPQL aliases) avoid loading full entities for read-only summary queries.
// 1. Entity
@Entity
@Table(name = "orders")
public class Order {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String reference;
private String status;
@Column(name = "customer_id")
private Long customerId;
private BigDecimal total;
private LocalDateTime createdAt;
// getters / setters
}
// 2. Repository — just an interface, Spring provides the implementation
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// built-in: save, findById, findAll, delete, count, existsById, etc.
}
// 3. Service — inject and use
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepo;
public Order create(Order order) {
return orderRepo.save(order); // INSERT
}
public Optional<Order> find(Long id) {
return orderRepo.findById(id); // SELECT … WHERE id=?
}
public Page<Order> list(Pageable page) {
return orderRepo.findAll(page); // SELECT with LIMIT/OFFSET
}
}JpaRepository & CrudRepository
JpaRepository extends PagingAndSortingRepository adding flush, batch-delete, and JPA-specific bulk operations on top of basic CRUD.
- ✓Extend JpaRepository<Entity, Id> — Spring generates the implementation at runtime with no code required
- ✓save() performs INSERT for new entities (no id) and UPDATE (merge) for managed/detached entities
- ✓deleteAllInBatch() issues a single DELETE ... WHERE id IN (...) — much faster than N individual deleteById calls
- ✓Derived method names generate JPQL automatically; use @Query for complex conditions to keep method names short
- ✓Always use readOnly = true on @Transactional for query-only methods — reduces overhead and enables Hibernate optimisations
- ✓Interface projections SELECT only specified columns; DTO projections (constructor expression) work for multi-table queries
// Define repository — Spring generates the implementation
public interface OrderRepository extends JpaRepository<Order, Long> {
// All JpaRepository methods available immediately:
// save, findById, findAll, delete, count, existsById, ...
}
// Usage in service
@Service
@Transactional(readOnly = true)
public class OrderService {
private final OrderRepository orderRepository;
public Optional<Order> findById(Long id) {
return orderRepository.findById(id); // returns Optional — no NPE
}
public List<Order> findAll() {
return orderRepository.findAll();
}
@Transactional
public Order save(Order order) {
return orderRepository.save(order); // INSERT or UPDATE (merge)
}
@Transactional
public void delete(Long id) {
orderRepository.deleteById(id);
}
public long count() {
return orderRepository.count();
}
// Batch operations — much faster than N individual deletes
@Transactional
public void deleteAll(List<Order> orders) {
orderRepository.deleteAllInBatch(orders); // single DELETE ... WHERE id IN (...)
}
}Custom Queries with @Query
@Query accepts JPQL or native SQL strings and can return projections, DTOs, or pageable results; @Modifying enables UPDATE/DELETE statements.
- ✓@Query with JPQL references entity class and field names — independent of table/column names
- ✓JOIN FETCH in @Query eagerly loads associations in one query, preventing N+1 for that method
- ✓nativeQuery = true sends raw SQL — use for CTEs, window functions, or DB-specific functions
- ✓@Modifying + @Transactional enables bulk UPDATE/DELETE that bypass entity lifecycle hooks
- ✓clearAutomatically = true evicts stale first-level cache entries after a bulk update
- ✓Native @Query with Pageable requires an explicit countQuery — Spring cannot auto-derive count for native SQL
public interface OrderRepository extends JpaRepository<Order, Long> {
// Simple JPQL with named parameter
@Query("SELECT o FROM Order o WHERE o.status = :status AND o.total > :minAmount")
List<Order> findActiveOrdersAbove(@Param("status") OrderStatus status,
@Param("minAmount") BigDecimal minAmount);
// JOIN FETCH — load customer in same query (prevents N+1)
@Query("SELECT o FROM Order o JOIN FETCH o.customer c WHERE c.email = :email")
List<Order> findByCustomerEmail(@Param("email") String email);
// DTO projection via constructor expression
@Query("SELECT new com.example.dto.OrderSummaryDTO(o.id, o.status, o.total) " +
"FROM Order o WHERE o.customer.id = :customerId")
List<OrderSummaryDTO> findSummariesByCustomer(@Param("customerId") Long id);
// Pageable with @Query — Spring adds ORDER BY and LIMIT automatically
@Query("SELECT o FROM Order o WHERE o.status = :status")
Page<Order> findByStatusPaged(@Param("status") OrderStatus status, Pageable pageable);
// Count query for pagination (optional — Spring auto-derives it)
@Query(value = "SELECT o FROM Order o WHERE o.status = :status",
countQuery = "SELECT COUNT(o) FROM Order o WHERE o.status = :status")
Page<Order> findByStatusPagedWithCount(@Param("status") OrderStatus status,
Pageable pageable);
}Pagination & Sorting
Pass a Pageable object to repository methods; Spring Data returns a Page<T> containing the slice of results plus total-count metadata.
- ✓Spring MVC resolves Pageable automatically from ?page=0&size=20&sort=field,direction request parameters.
- ✓Page<T> issues a COUNT(*) query for totalElements; Slice<T> skips it — use Slice for infinite scroll.
- ✓Always sanitise sort field names — never pass raw user input to Sort to prevent field-name injection.
- ✓Use @PageableDefault to set default page size and sort direction when no query parameters are provided.
- ✓Map Page<Entity> to a custom DTO before returning from the controller — do not leak JPA proxy internals.
- ✓For large offsets (page=10000), consider cursor-based pagination — OFFSET scans all preceding rows.
// Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// Spring Data generates: SELECT * FROM orders WHERE status=? LIMIT ? OFFSET ?
// + COUNT(*) query for totalElements
Page<Order> findByStatus(String status, Pageable pageable);
// Custom JPQL with pagination
@Query("SELECT o FROM Order o WHERE o.createdAt > :since")
Page<Order> findRecentOrders(@Param("since") LocalDateTime since,
Pageable pageable);
// Slice<T>: no count query — use for infinite scroll
Slice<Order> findByCustomerId(Long customerId, Pageable pageable);
}
// Controller — Spring resolves Pageable from ?page=0&size=20&sort=createdAt,desc
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping
public Page<OrderDto> list(
@RequestParam(defaultValue = "PENDING") String status,
@PageableDefault(size = 20, sort = "createdAt",
direction = Sort.Direction.DESC) Pageable pageable) {
return orderRepository.findByStatus(status, pageable)
.map(orderMapper::toDto);
}
}Transaction Management
@Transactional wraps method execution in a DB transaction; propagation and isolation attributes control nested-transaction and concurrency behaviour.
- ✓@Transactional works through a CGLIB proxy — self-invocation (calling a @Transactional method from the same class) bypasses the proxy and starts no transaction.
- ✓Default rollback: only RuntimeException and Error. Checked exceptions do NOT roll back unless rollbackFor = Exception.class is configured.
- ✓readOnly = true is a performance hint — it skips Hibernate dirty-checking and may enable DB-level optimisations like read replicas.
- ✓REQUIRES_NEW suspends the outer transaction and opens a new independent one — useful for audit logs that must survive an outer rollback.
- ✓REQUIRED (default propagation) joins an existing transaction; if none exists, it creates one.
- ✓A @Transactional annotation on a private method has no effect — Spring proxies only intercept public method calls on injected beans.
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepo;
private final InventoryService inventoryService;
// ✅ Transactional — commits when method returns, rolls back on RuntimeException
@Transactional
public Order placeOrder(OrderRequest req) {
Order order = orderRepo.save(new Order(req));
inventoryService.reserve(req.getItems()); // same transaction — any RuntimeException
// here rolls back the whole unit of work
return order;
}
// ✅ Read-only hint — lets the DB / ORM skip dirty-checking & flush
@Transactional(readOnly = true)
public Order getOrder(Long id) {
return orderRepo.findById(id).orElseThrow();
}
// ✅ Also rollback on checked exceptions
@Transactional(rollbackFor = Exception.class)
public void importOrders(List<OrderRequest> orders) throws IOException {
for (OrderRequest req : orders) {
orderRepo.save(new Order(req));
}
}
}Connection Pool — HikariCP
Spring Boot auto-configures HikariCP as the default connection pool; tuning maximumPoolSize, connectionTimeout, and keepaliveTime is critical for production.
- ✓HikariCP is Spring Boot's default pool — ~50 μs acquire time, minimal overhead
- ✓maximumPoolSize is the most critical tuning knob — too low queues requests, too high overwhelms the DB
- ✓Set minimumIdle = maximumPoolSize for a fixed pool with no cold-start latency
- ✓maxLifetime must be less than the DB server's wait_timeout to prevent stale connections
- ✓keepaliveTime pings idle connections to prevent firewall/NAT dropping them
- ✓Monitor hikaricp_connections_pending — non-zero means pool exhaustion; tune size or fix slow queries
# application.properties — HikariCP tuning spring.datasource.hikari.maximum-pool-size=20 # max concurrent connections spring.datasource.hikari.minimum-idle=20 # keep pool warm (= max for fixed pool) spring.datasource.hikari.connection-timeout=30000 # 30s wait for connection from pool spring.datasource.hikari.idle-timeout=600000 # 10min: release idle connections spring.datasource.hikari.max-lifetime=1800000 # 30min: max connection age (rotate before DB server times out) spring.datasource.hikari.keepalive-time=60000 # 1min: ping idle connections to prevent firewall drops spring.datasource.hikari.pool-name=OrderDB-Pool # name shown in JMX/logs # Validate configuration at startup spring.datasource.hikari.initialization-fail-timeout=1 # fail fast if DB unreachable
Spring Data REST
Exports repository operations as hypermedia-driven REST endpoints automatically, following HATEOAS principles with HAL or HAL-FORMS media types.
- ✓Spring Data REST auto-generates CRUD + search endpoints from repository interfaces
- ✓Responses are HAL+JSON with _links for navigation (self, collection, next/prev pages)
- ✓@RepositoryRestResource(exported=false) hides a repository from the REST layer
- ✓@Projection defines named field subsets; clients opt-in with ?projection=name
- ✓@RepositoryEventHandler intercepts before/after save, create, delete events
- ✓Use spring.data.rest.base-path=/api to namespace all generated endpoints
@RepositoryRestResource(collectionResourceRel = "products", path = "products")
public interface ProductRepository extends PagingAndSortingRepository<Product, Long> {
// Custom finder exposed at: GET /products/search/findByCategory?category=ELECTRONICS
List<Product> findByCategory(@Param("category") String category);
}
// Generated endpoints:
// GET /products — paginated list with _links
// POST /products — create
// GET /products/{id} — single item
// PUT /products/{id} — full replace
// PATCH /products/{id} — partial update
// DELETE /products/{id} — delete