엘비스 연산자
코틀린에서는 NullPointerException을 방지하기 위해 기본적인 자료형은 null이 될 수 없도록 되어있습니다.
fun plus(n: Int?): Int { .. }
null을 사용하기 위해서는 자료형 뒤에 ? 를 붙여야 합니다. 이는 null을 허용한 파라미터임을 명시합니다.
그리고 null인 경우에 추가로 예외 처리를 작성해주어야 합니다.
?.
Elvis 연산자는 코틀린에서 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 가능성 여부를 확인하라는 에러가 뜰 때가 있는데 이때 !! 연산자를 통해 해당 값은 null일 수 없음을 명시할 수 있습니다.
fun plusTen(n:Int?):Int{
return n!!.toInt()
}
추가로 코틀린의 first, firstOrNull 메서드에 대해 설명하겠습니다.
firstOrNull
first 함수는 컬렉션 인자 중에서 첫 번째 인자를 리턴합니다. first { … } 는 특정 조건을 만족하는 첫 번째 인자를 리턴할 수 있습니다.
firstOrNull은 first 함수와 동일하면서, 추가적으로 만족하는 인자가 없다면 null을 리턴해줍니다.
Before
@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 chaining이 길어지게 되면 코드의 가독성이 저하될 우려가 있다.
- 또한, BookStatResponse에 count 변수 값이 var 타입으로 불변하지 않아 불변성 객체를 가질 수 없음
- plus()로 각 책 종류의 개수를 세고 있음
After 2
@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 → val로 변경하고, plus() 메서드 필요 없이 각 종류의 책 개수를 셀 수 있음
- 불변 객체를 유지할 수 있게 됨
SQL 및 메모리 최적화
- findAllByStatus() : 데이터베이스에 있는 모든 UserLoanHistory를 가지고 와 서버에서 가지고 있습니다.
- 조회된 모든 UserLoanHistory 객체를 가지고 있어야 하는 문제가 있음
- 그리고 size 함수를 통해 개수를 구해야 함
- countByStatus() : count 쿼리를 통해 개수만 서버에서 가지고 있을 수 있음
- 메모리적으로 효율적임
@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()
}
@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>
- JPQL을 통해 필요한 데이터만 가져옴으로써 최적화할 수 있다.
- findAll() 하는 경우 모든 데이터를 가지고 온 다음에 그룹핑하므로, 데이터 개수가 많은 경우에 성능이 저하될 수 있음
연관 관계 설정할 때 지연로딩(Lazy Loading) 또는 즉시로딩(Eager Loading)으로 처리하면 다음과 같은 문제가 발생할 수 있습니다.
1. 데이터 커넥션을 길게 유지해야 하는 문제
지연로딩의 경우, 필요한 데이터를 불러오려는 순간 데이터베이스를 추가 호출하게 됩니다. 이는 데이터베이스 커넥션을 길게 유지하게 만들어 빠르게 처리되어야 하는 서비스에 성능 문제가 될 수 있습니다.
2. 불필요한 데이터를 함께 로드하는 문제
즉시 로딩을 사용하면, 실제로 필요한 데이터보다 더 많은 데이터를 한 번에 로드하게 될 수 있습니다. 이는 메모리 사용량을 증가시키고, 애플리케이션에 성능에 부정적인 영향을 미칠 수 있습니다.
'Kotlin' 카테고리의 다른 글
Kotlin init(생성자), constructor(보조 생성자), get, set 이해하기 (0) | 2024.12.15 |
---|