엘비스 연산자
코틀린에서는 NullPointerException을 방지하기 위해 기본적인 자료형은 null이 될 수 없도록 되어있습니다.
fun plus(n: Int?): Int { .. }
null을 사용하기 위해서는 자료형 뒤에 ? 를 붙여야 합니다.
이는 null을 허용한 파라미터임을 명시합니다.
추가적으로 null인 경우에 예외 처리를 작성해주어야 합니다.
?.
엘비스 연산자는 코틀린에서 null 일 수도 있는 값을 간편하게 처리하는데 유용합니다.
fun plus(n:Int?):Int{
return n?.toInt()?:-1
}
?. 뒤에 있는 메서드는 n이 null이 아닐 때만 실행이 됩니다.
?:
fun plus(n:Int?):Int{
return n?.toInt()?:-1
}
?:은 n이 null일 때 실행할 코드를 작성합니다.
두 연산자를 통해 nullable한 값을 간편하게 처리할 수 있습니다.
(코틀린의 엘비스 연산자를 통해 optional 객체로 감싸지 않아도 되는 장점이 있습니다.)
!!
코틀린에서 !! 연산자는 직접 null이 될 수 없다고 명시할 때 사용합니다.
변수의 null 가능성 여부를 확인하라는 에러가 뜰 때가 있는데 이때 !! 연산자를 통해 해당 값은 null일 수 없음을 명시할 수 있습니다.
fun plusTen(n:Int?):Int{
return n!!.toInt()
}
firstOrNull
first 함수는 컬렉션 인자 중에서 첫 번째 인자를 리턴합니다.
first { … } 는 특정 조건을 만족하는 첫 번째 인자를 리턴할 수 있습니다.
firstOrNull은 first 함수와 동일하면서, 추가적으로 만족하는 인자가 없다면 null을 리턴해줍니다.
Before: 그룹핑 1차
@Transactional(readOnly = true)
fun getBookStatistics(): List<BookStatResponse> {
val results = mutableListOf<BookStatResponse>() // 문제 1 : 불변한 객체 아님
val books = bookRepository.findAll() // 문제 2: 모든 Book 데이터 조회
for (book in books) {
// 문제 3: bookType별로 그룹핑하는 코드 복잡함
val targetDto = results.firstOrNull { dto -> book.type == dto.type }
if (targetDto == null) {
results.add(BookStatResponse(book.type, 1))
} else {
targetDto.plus() // 문제 1: 불변한 객체가 아님
}
}
return results
}
After 1 : 엘비스 연산자를 통한 코드 개선
@Transactional(readOnly = true)
fun getBookStatistics(): List<BookStatResponse> {
val results = mutableListOf<BookStatResponse>()
val books = bookRepository.findAll()
// 문제 3 개선: 엘비스 연산자를 통해 가독성 개선
// But, 길어지는 Method Chaining은 가독성을 저하시킬 수 있음
for (book in books) {
results.firstOrNull { dto -> book.type == dto.type }?.plus()
?: results.add(BookStatResponse(book.type, 1))
}
return results
}
- ?., ?:, firstOrNull 과 같이 nullable 연산자를 사용하는 경우 다른 개발자가 이해하는데 불편함이 있을 수 있다.
- 길어지는 method chain이 코드를 이해하는데 어려움이 있을 수 있다.
- 또한, BookStatResponse에 count 필드의 값이 var 타입으로 불변하지 않아 불변성 객체를 가질 수 없는 문제가 있음
After 2 : group by를 통한 불변성 객체 유지
@Transactional(readOnly = true)
fun getBookStatistics(): List<BookStatResponse> {
// 문제 3 개선: group by 함수를 통해 가독성 개선
return bookRepository.findAll()
.groupBy { book -> book.type } // Map<BookType, List<Book>>
.map{ (type, books) -> BookStatResponse(type, books.size) } // (key-value)
}
- groupBy를 이용하여 처리
- var → var로 변경하고 plus() 메서드가 필요 없어집니다.
- 불변 객체를 유지할 수 있게 됨
서버 코드 vs sql
서버에서 모든 데이터를 가져오는 것과 SQL 쿼리에서 필요한 데이터를 호출하는 경우 서버 메모리 성능에 큰 차이가 있습니다.
- findAllByStatus() : 데이터베이스에 있는 모든 UserLoanHistory를 가지고 와 서버에서 가지고 있는다.
- 조회된 모든 UserLoanHistory 객체를 가지고 있는다.
- 그리고 size 함수를 통해 개수를 구한다.
- 데이터 크기를 구하려고 모든 데이터를 조회하면, 불필요하게 메모리를 차지한다.
- countByStatus() : count 쿼리를 통해 개수만 서버에서 가지고 있는다.
- Query로 필요한 데이터만 추출하므로 메모리 측면에서 효율적이다.
최적화된 JPQL 쿼리
@Query("select new com.group.libraryapp.dto.book.response.BookStatResponse(b.type, count(b.id))" +
" from Book b group by b.type")
fun getStat(): List<BookStatResponse>
@Query를 통해 직접 조회할 SQL문을 작성한다.
new package...DTO를 통해 DTO 클래스에 값을 바로 매핑한다.
@Transactional(readOnly = true)
fun getBookStatistics(): List<BookStatResponse> {
// return bookRepository.findAll()
// .groupBy { book -> book.type } // Map<BookType, List<Book>>
// .map{ (type, books) -> BookStatResponse(type, books.size) } // (key-value)
return bookRepository.getStat()
}
- JPQL을 통해 필요한 데이터만 가져옴으로써 메모리 측면에 있어 최적화되었습니다.
- findAll() 하는 경우 모든 데이터를 가지고 온 다음에 그룹핑하므로, 데이터 개수가 많은 경우에 성능이 저하될 수 있는 문제 개선
'Kotlin > 코프링' 카테고리의 다른 글
코틀린 스코프 함수 총정리 - let, also, run, apply, with 제대로 사용하기 (0) | 2024.12.12 |
---|---|
[Kotlin+Spring] 코프링 Kotlin으로 Spring DTO 클래스 생성하기 - data class (0) | 2024.06.18 |