Part 03 · Spring Framework & Spring Boot · 3.1.07A

Các cách viết query với Spring Data JPA và PostgreSQL

JPA là specification, Hibernate thường là provider, Spring Data JPA tạo repository abstraction, còn PostgreSQL thực thi SQL. QueryDSL là thư viện bổ sung; TypeScript không liên quan đến cách query JPA.


1. Các layer thường bị nhầm

LayerVai tròVí dụ
Jakarta Persistence/JPASpecification cho entity, persistence context, JPQL và lifecycle.@Entity, EntityManager, JPQL.
HibernateJPA provider phổ biến.Dirty checking, SQL generation, dialect.
Spring Data JPARepository abstraction xây trên JPA.JpaRepository, derived query, @Query, @Modifying.
PostgreSQLDatabase thực thi SQL, lock, MVCC và index.JSONB, RETURNING, partial index, FOR UPDATE.
QueryDSLThư viện type-safe query builder.QOrder.order.status.eq(...).

SQL là ngôn ngữ chuẩn với dialect riêng; PostgreSQL là database; psql là CLI client; T-SQL là dialect Microsoft SQL Server; JPQL query theo entity/field rồi provider dịch thành SQL.

JPQL: select t from Transfer t where t.status = :status. PostgreSQL SQL: select * from transfers where status = $1. Cùng ý định nhưng khác model và nơi thực thi.

2. Derived query methods

interface TransferRepository extends JpaRepository<Transfer, UUID> {
  Optional<Transfer> findByIdempotencyKey(String key);
  Page<Transfer> findByAccountIdAndStatusOrderByCreatedAtDesc(
      UUID accountId, TransferStatus status, Pageable pageable);
}

Spring Data parse tên method và tạo query. Cách này tốt cho điều kiện ngắn, ổn định; tên quá dài, nhiều optional filters hoặc joins phức tạp nên chuyển sang @Query, Specification hoặc QueryDSL.

3. JPQL với @Query

@Query("""
  select t from Transfer t
  join fetch t.entries
  where t.id = :id
  """)
Optional<Transfer> findDetailById(@Param("id") UUID id);

JPQL query theo entity và Java fields, portable hơn native SQL nhưng không expose mọi PostgreSQL feature. Collection fetch join với pagination cần kiểm tra SQL, duplicate rows và query count.

4. Native PostgreSQL SQL

@Query(value = """
  select * from transfers
  where metadata @> cast(:filter as jsonb)
  order by created_at desc
  limit :limit
  """, nativeQuery = true)
List<Transfer> searchMetadata(String filter, int limit);

Dùng native query khi cần JSONB, CTE, window function, RETURNING, database-specific locking/index behavior hoặc query đã tune kỹ. Đổi lại, portability thấp hơn và mapping, pagination, count query cùng migration phải được test trên PostgreSQL thật.

5. @Modifying thực sự làm gì?

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
  update Transfer t
     set t.status = :status, t.updatedAt = :now
   where t.id = :id and t.status = :expected
  """)
int transition(UUID id, TransferStatus expected,
               TransferStatus status, Instant now);

6. Transaction đặt ở application service

@Transactional
public void approve(UUID id) {
  int changed = repository.transition(id, PENDING, APPROVED, clock.instant());
  if (changed != 1) throw new ConcurrentTransferUpdateException(id);
  outbox.save(TransferApproved.of(id));
}

Boundary service đảm bảo state transition và Outbox cùng commit hoặc rollback. Nếu chỉ transaction repository update, Outbox phía sau có thể chạy ngoài transaction và phá invariant.

7. Dirty checking hay bulk update?

CáchPhù hợpCaveat
Load entity và đổi stateDomain rule, callback, optimistic lock.Phải load row; nhiều row tốn memory/query.
Bulk @ModifyingUpdate/delete nhiều row hoặc atomic conditional update.Bypass context, callback và version nếu query không tự xử lý.
Native DMLPostgreSQL feature hoặc performance đặc thù.Mapping, portability và stale context.

8. Specification và Criteria

Specification<Transfer> spec = (root, query, cb) ->
    cb.and(
        cb.equal(root.get("accountId"), accountId),
        cb.greaterThanOrEqualTo(root.get("createdAt"), from));

repository.findAll(spec, pageable);

Specification compose predicates cho nhiều optional filters. Criteria type-oriented nhưng verbose; string field names vẫn có refactor risk nếu không dùng static metamodel.

9. Query by Example

QBE tiện cho matching theo fields của probe, nhưng yếu với ranges, OR phức tạp, joins và aggregates. Không dùng nó cho reporting query phức tạp.

10. QueryDSL

QTransfer t = QTransfer.transfer;
List<Transfer> result = queryFactory.selectFrom(t)
    .where(t.accountId.eq(accountId),
           t.status.eq(PENDING),
           t.createdAt.goe(from))
    .orderBy(t.createdAt.desc())
    .fetch();

QueryDSL sinh Q-types lúc build và cung cấp fluent type-safe query. Nó hữu ích khi dynamic query làm Specification khó đọc, nhưng cần annotation-processing setup và không tự giải quyết N+1 hoặc query performance.

11. Projection

interface TransferSummary {
  UUID getId();
  BigDecimal getAmount();
  TransferStatus getStatus();
}

Interface hoặc DTO projection chỉ lấy fields cần cho read use case và tránh load entity graph. DTO constructor hoặc record projection làm contract rõ hơn. Không trả managed entity trực tiếp ra API.

12. Locking query

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select a from Account a where a.id = :id")
Optional<Account> findByIdForUpdate(UUID id);

Lock chỉ có ý nghĩa trong transaction thật và thường giữ tới commit/rollback. PostgreSQL FOR UPDATE block, timeout hoặc deadlock theo access order. Bulk update phải tự đưa version vào predicate/increment nếu muốn giữ optimistic-lock protection.

13. Stored procedure và JdbcTemplate

Spring Data JPA hỗ trợ stored procedure ở mức nhất định, nhưng procedure hoặc result-set phức tạp có thể rõ hơn qua JdbcTemplate hay Spring Data JDBC. Không bắt buộc mọi query dùng JPA; chọn tool theo access pattern, correctness, mapping và vận hành.

14. Decision guide

Nhu cầuƯu tiên
CRUD/query ngắnDerived method.
Entity query rõ, tương đối portableJPQL @Query.
Nhiều optional filtersSpecification hoặc QueryDSL.
Read model nhỏProjection hoặc DTO query.
Bulk update/delete@Modifying + transaction + context strategy.
JSONB, CTE, window, RETURNINGNative PostgreSQL SQL hoặc JdbcTemplate.
Business state transitionDirty checking hoặc atomic conditional update có affected-row check.

15. Checklist

Nguồn tham khảo