콘텐츠로 이동
Bluetape4k 문서1.11

Query와 repository 구성

최신 안정판 Bluetape4k 1.11.0 릴리스 기준

Spring Data Query를 그대로 사용한다

섹션 제목: “Spring Data Query를 그대로 사용한다”

이 모듈은 자체 query DSL을 만들지 않습니다. 조건, 정렬, limit는 Spring Data Relational의 CriteriaQuery로 표현합니다.

fun findAllByPostId(postId: Long): Flow<Comment> {
val query = Query.query(
Criteria.where(Comment::postId.name).isEqual(postId)
)
return operations.selectSuspending(query)
}
suspend fun countByPostId(postId: Long): Long {
val query = Query.query(
Criteria.where(Comment::postId.name).isEqual(postId)
)
return operations.countSuspending(query)
}

같은 조건을 select와 count에 사용하면 pagination이나 endpoint metadata를 만들기 쉽습니다. 조건 생성이 복잡해지면 작은 private 함수로 분리하되, repository 밖에 Spring Data query type을 불필요하게 노출하지 않습니다.

private fun publishedBy(authorId: Long, keyword: String?): Query {
var criteria = Criteria.where(Post::authorId.name).isEqual(authorId)
.and(Post::published.name).isEqual(true)
if (!keyword.isNullOrBlank()) {
criteria = criteria.and(Post::title.name).like("%$keyword%")
}
return Query.query(criteria)
.sort(Sort.by(Sort.Direction.DESC, Post::createdAt.name))
.limit(50)
}

사용자 입력을 그대로 wildcard pattern이나 property 이름으로 쓰지 않습니다. 값은 Spring Data가 parameter로 binding할 수 있게 넘기고, 정렬 property는 허용 목록에서 선택합니다.

repository는 다음 세 가지를 결정하면 충분합니다.

  1. 어떤 entity type과 Query를 사용할지
  2. 결과 cardinality를 Flow, one, first 중 무엇으로 표현할지
  3. write 결과 행 수를 domain 결과로 어떻게 해석할지

HTTP status, retry, 여러 repository를 묶는 transaction은 service나 controller advice처럼 더 넓은 경계가 소유합니다. 반대로 raw database exception을 repository에서 무조건 null로 바꾸면 데이터 부재와 장애를 구분할 수 없습니다.

@RestController
@RequestMapping("/posts")
class PostController(
private val posts: PostRepository,
) {
@GetMapping
fun findAll(): Flow<Post> = posts.findAll()
@GetMapping("/{id}")
suspend fun findOne(@PathVariable id: Long): Post =
posts.findOneByIdOrNull(id) ?: throw PostNotFoundException(id)
@PostMapping
suspend fun insert(@RequestBody post: Post): Post =
posts.insert(post)
}

Flow와 suspend 함수는 WebFlux가 직접 처리할 수 있습니다. database 호출 뒤 blocking JSON 변환이나 runBlocking을 끼워 넣지 않습니다.

복잡한 join, vendor-specific SQL, generated key 세부 제어, DTO projection을 직접 다뤄야 한다면 bluetape4k-r2dbcR2dbcClientDatabaseClient helper가 더 알맞습니다. 이 모듈의 entity extension에 raw SQL API를 억지로 섞지 않습니다.

table과 column을 Kotlin DSL로 다루고 repository 기반 구조를 원하면 bluetape4k-exposed R2DBC 계층을 비교합니다. 선택 기준은 생태계 학습 경로에 정리했습니다.

Transaction, 실패, 테스트에서 여러 repository 호출과 검증 환경을 다룹니다.