반응형

데이터의 Total 개수와 해당 페이지에 해당하는 요소를 데이터베이스로부터 추출해 응답해야 하는 API를 개발해야 했다.

보통은 이런 것을 JPA의 페이징을 이용해서 만든다.

근데, 이것을 JDBC로 만들어보고 JPA보다 빠른지 궁금해져서 만들며 비교를 해보려고 한다.

각각 파이썬으로 데이터를 1000개 정도 넣고 테스트 해보았다.

 

  • JDBC

우선 JDBC이다.

사용한 코드는 아래와 같다.

총 개수와 해당 요소들을 가져오는 쿼리문을 2번 날리게 된다.

 

Service

override fun professorGetWritingList(
        assignmentId: Int,
        page: Int,
        pageSize: Int
    ): ResponseEntity<CoBoResponseDto<ProfessorGetWritingListRes>> {
        val startTime = System.currentTimeMillis()
        val totalElements = writingRepository.countByAssignmentIdWithJDBC(assignmentId)
        val writings =
            writingRepository.findByAssignmentIdOrderByStatePagingWithJDBC(
                assignmentId = assignmentId,
                page = page,
                pageSize = pageSize
            ).map{
                ProfessorGetWritingLisElementRes(
                    studentId = it.studentId,
                    createdAt = it.createdAt!!,
                    updatedAt = it.updatedAt!!,
                    writingState = it.state.value
                )
            }

        println("TIME: ${System.currentTimeMillis() - startTime}ms")

        return CoBoResponse(
            data = ProfessorGetWritingListRes(
                totalElements = totalElements,
                writings = writings
            ),
            coBoResponseStatus = CoBoResponseStatus.SUCCESS
        ).getResponseEntity()
    }

 

Repository

    override fun findByAssignmentIdOrderByStatePagingWithJDBC(assignmentId: Int, page: Int, pageSize: Int): List<Writing> {
        val sql = "SELECT writing.id, writing.student_id, writing.assignment_id, writing.content, writing.state, writing.created_at, writing.updated_at, writing.submitted_at " +
                "FROM writing WHERE writing.assignment_id = ? ORDER BY writing.state LIMIT ?, ?"
        return jdbcTemplate.query(
            sql, {rs, _ -> writingRowMapper(rs)}, assignmentId, page, pageSize
        )
    }

    override fun countByAssignmentIdWithJDBC(assignmentId: Int): Long {
        return jdbcTemplate.queryForObject("SELECT count(*) FROM writing WHERE assignment_id = ?", Long::class.java, assignmentId)!!

    }

    private fun writingRowMapper(resultSet: ResultSet): Writing {
        return Writing(
            id = resultSet.getInt("id"),
            studentId = resultSet.getString("student_id"),
            assignment = Assignment(resultSet.getInt("assignment_id")),
            content = resultSet.getString("content"),
            state = WritingStateEnum.from(resultSet.getShort("state"))!!,
            createdAt = resultSet.getTimestamp("created_at").toLocalDateTime(),
            updatedAt = resultSet.getTimestamp("updated_at").toLocalDateTime(),
            submittedAt = resultSet.getTimestamp("submitted_at").toLocalDateTime()
        )
    }

 

0페이지에 요소를 20개로 속도를 측정해보면

요소 20개 요소 100개
39MS 47MS
41MS 46MS
44MS 48MS
38MS 40MS

 

이 정도의 속도가 나왔다, 역시 요소가 많아질수록 시간이 오래 걸리기는 한다.

 

  • JPA

이번에는 간편한 JPA이다.

간단하게 PageRequest로 페이징해서 거기에서 totalElements와 요소들을 가져왔다.

override fun professorGetWritingList(
        assignmentId: Int,
        page: Int,
        pageSize: Int
    ): ResponseEntity<CoBoResponseDto<ProfessorGetWritingListRes>> {
        val startTime = System.currentTimeMillis()
        val jpaList = writingRepository.findByAssignment(Assignment(assignmentId), PageRequest.of(page, pageSize))

        val totalElements = jpaList.totalElements
        val writings = jpaList.content.map {
            ProfessorGetWritingListElementRes(
                it.studentId, it.createdAt!!, it.updatedAt!!, it.state.value
            )
        }

        println("TIME: ${System.currentTimeMillis() - startTime}ms")

        return CoBoResponse(
            data = ProfessorGetWritingListRes(
                totalElements = totalElements,
                writings = writings
            ),
            coBoResponseStatus = CoBoResponseStatus.SUCCESS
        ).getResponseEntity()
    }

 

전과 동일하게 측정해보자

요소 20개 요소 100개
128MS 81MS
63MS 76MS
61MS 68MS
61MS 69MS

 

확실히 JDBC로 호출하는 것이 빠른 것을 볼 수 있다.

 

사용자가 얼마 없으면 JPA로 개발하고, 사용자가 많아지면 JDBC로 개발할 것 같다.

이번은 이미 JDBC 코드로 만들어놔서 JPA를 사용하지는 않을 것 같다.

 

  • JDBC 비동기

마지막으로 JDBC에서 호출하던 2개의 쿼리문을 비동기로 동시에 호출해보자.

 

JDBC 서비스 코드에서 아래같이 수정했다.

override fun professorGetWritingList(
        assignmentId: Int,
        page: Int,
        pageSize: Int
    ): ResponseEntity<CoBoResponseDto<ProfessorGetWritingListRes>> {

        val startTime = System.currentTimeMillis()

        val totalElementsCompletableFuture = CompletableFuture.supplyAsync {
            writingRepository.countByAssignmentIdWithJDBC(assignmentId)
        }

        val writingsCompletableFuture = CompletableFuture.supplyAsync {
            writingRepository.findByAssignmentIdOrderByStatePagingWithJDBC(
                assignmentId = assignmentId,
                page = page,
                pageSize = pageSize
            ).map{
                ProfessorGetWritingListElementRes(
                    studentId = it.studentId,
                    createdAt = it.createdAt!!,
                    updatedAt = it.updatedAt!!,
                    writingState = it.state.value
                )
            }
        }

        val coboResponse = CoBoResponse(
            data = ProfessorGetWritingListRes(
                totalElements = totalElementsCompletableFuture.get(),
                writings = writingsCompletableFuture.get()
            ),
            coBoResponseStatus = CoBoResponseStatus.SUCCESS
        )

        println("TIME: ${System.currentTimeMillis() - startTime}ms")

        return coboResponse.getResponseEntity()
    }

 

실행해보면

요소 20개 요소 100개
35MS 46MS
37MS 31MS
29MS 21MS
30MS 26MS

 

이렇게 말도 안되는 속도가 나온다.

물론 가장 빠르기는 하지만, 그냥 JDBC를 사용했을 때와 비슷한 거 같기도....?

 

확실히 JPA가 편하고 비동기가 강력한 것 같다.

+ Recent posts