728x90
UserService
@Service
class UserService(
private val userRepository: UserRepository,
) {
//Transactional 기능을 사용하기 위해서는 오버라이드 될 수 있어야 하는데, 코틀린은 함수를 기본적으로 상속이 불가능하기 떄문에 오류가 발생한다.
//open 키워드를 붙여서 상속이 가능하도록 해도 되지만, 플러그인을 추가해도 된다(spring)
@Transactional
fun saveUser(request: UserCreateRequest) {
val newUser = User(request.name, request.age)
userRepository.save(newUser)
}
@Transactional(readOnly = true)
fun getUsers(): List<UserResponse> {
//return userRepository.findAll().map { user -> UserResponse(user) }
//return userRepository.findAll().map { UserResponse(it) }
return userRepository.findAll().map(::UserResponse)
}
@Transactional
fun updateUserName(request: UserUpdateRequest){
val user = userRepository.findById(request.id).orElseThrow(::IllegalArgumentException)
user.updateName(request.name)
}
@Transactional
fun deleteUser(name: String){
val user = userRepository.findByName(name).orElseThrow(::IllegalArgumentException)
userRepository.delete(user)
}
}
- private val 키워드로 변경이 불가능하고 내부에서만 사용 가능하도록 빈을 주입받는다.
- @Transactional 같은 경우 기본적으로 상속이 가능해야지만 사용할 수 있다(트랜잭션은 Proxy 기술이 사용되기 때문)
- 코틀린에서는 기본적으로 모든 클래스는 상속이 막혀 있고, 상속이 가능한 클래스/메서드 에는 open 키워드가 붙는다.
- 매번 @Transactional 애노테이션이 있는 메서드마다, open 키워드가 붙이지 말고 Spring 플러그인을 설치하면 문제가 해결된다.(id 'org.jetbrains.kotlin.plugin.spring' version '1.6.21')
- 클래스(it)나 ::클래스를 통해 생성자를 호출할 수 있다.
BookService
@Service
class BookService(
private val bookRepository: BookRepository,
private val userRepository: UserRepository,
private val userLoanHistoryRepository: UserLoanHistoryRepository,
) {
@Transactional
fun saveBook(request: BookRequest) {
val book = Book(request.name)
bookRepository.save(book)
}
@Transactional
fun loanBook(request: BookLoanRequest) {
val book =
bookRepository.findByName(request.bookName).orElseThrow(::IllegalArgumentException)
if (userLoanHistoryRepository.findByBookNameAndIsReturn(request.bookName, false) != null) {
throw IllegalArgumentException("진작 대출되어 있는 책입니다")
}
val user =
userRepository.findByName(request.userName).orElseThrow(::IllegalArgumentException)
user.loanBook(book)
}
@Transactional
fun returnBook(request: BookReturnRequest) {
val user =
userRepository.findByName(request.userName).orElseThrow(::IllegalArgumentException)
user.returnBook(request.bookName)
}
}
- 이전 코드와 비슷하게 작성하면 된다.
- 생성자를 통해 주입받아야 되는 빈을 private val 키워드를 사용한다.
Optional 제거하기
JDK8에서 등장한 Optional은 어떤 값이 null이 될 수 있는지를 표시했는데, 코틀린은 언어 자체에서 null여부를 판단할 수 있기 때문에 필요 없다.
1) UserRepository, BookRepository Optional 제거
interface UserRepository : JpaRepository<User, Long> {
fun findByName(name: String): User?
}
interface BookRepository : JpaRepository<Book, Long> {
fun findByName(bookName: String): Book? //코틀린에서는 Optional이 필요 없다
}
2) BookService, UserService에서 더 이상 Optional을 사용하지 않으니까, 엘비스 연산자 사용
- throw IllegalArgumentException이 반복되므로 util에 공통 함수로 만들어서 분리
@Service
class BookService(
private val bookRepository: BookRepository,
private val userRepository: UserRepository,
private val userLoanHistoryRepository: UserLoanHistoryRepository,
) {
@Transactional
fun saveBook(request: BookRequest) {
val book = Book(request.name)
bookRepository.save(book)
}
@Transactional
fun loanBook(request: BookLoanRequest) {
val book =
bookRepository.findByName(request.bookName) ?: fail() //항상 반복되므로 리펙토링 가능
if (userLoanHistoryRepository.findByBookNameAndIsReturn(request.bookName, false) != null) {
throw IllegalArgumentException("진작 대출되어 있는 책입니다")
}
val user =
userRepository.findByName(request.userName) ?: fail()
user.loanBook(book)
}
@Transactional
fun returnBook(request: BookReturnRequest) {
val user =
userRepository.findByName(request.userName)?: fail()
user.returnBook(request.bookName)
}
}
@Service
class UserService(
private val userRepository: UserRepository,
) {
@Transactional
fun saveUser(request: UserCreateRequest) {
val newUser = User(request.name, request.age)
userRepository.save(newUser)
}
@Transactional(readOnly = true)
fun getUsers(): List<UserResponse> {
return userRepository.findAll().map(::UserResponse)
}
@Transactional
fun updateUserName(request: UserUpdateRequest){
val user = userRepository.findById(request.id).orElseThrow(::IllegalArgumentException)
user.updateName(request.name)
}
@Transactional
fun deleteUser(name: String){
//val user = userRepository.findByName(name) ?: throw IllegalArgumentException() //엘비스 연산자 사용
val user = userRepository.findByName(name) ?: fail()
userRepository.delete(user)
}
}
fun fail(): Nothing {
throw IllegalArgumentException()
}
3) findById와 같이 JPA에서 제공되는 메서드는 Optional로 감싸서 주게 되는데, JPA에서는 이를 코틀린에서 확장해서 사용할 수 있도록 findByIdOrNull() 함수를 제공한다.
- 코틀린에서는 확장 함수 기능을 사용해 기능을 간편하게 추가할 수 있다.
import org.springframework.data.repository.CrudRepository
import org.springframework.data.repository.findByIdOrNull
fun fail(): Nothing {
throw IllegalArgumentException()
}
fun <T, ID> CrudRepository<T, ID>.findByIdOrThrow(id: ID): T { //확장함수 기능을 사용해서 더욱확장이 가능하다
return this.findByIdOrNull(id) ?: fail()
}
- CrudRepository에 확장 함수를 만들어서 커스텀 함수를 간편하게 만들 수 있다.
728x90
'실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기' 카테고리의 다른 글
| Ch03. 요구사항 추가(type, 대출현황) - Java 서버를 Kotlin 서버로 리팩토링하자 - 책의 분야 추가하기 (0) | 2022.11.05 |
|---|---|
| Ch02. Java 서버를 Kotlin 서버로 리팩토링하자 - DTO,Controller를 Kotlin으로 변경하기 (0) | 2022.11.01 |
| Ch02. Java 서버를 Kotlin 서버로 리팩토링하자 - Repository를 Kotlin으로 변경하기 (0) | 2022.11.01 |
| Ch02. Java 서버를 Kotlin 서버로 리팩토링하자 - Kotlin과 JPA를 함께 사용할 때 주의할 점 (0) | 2022.11.01 |
| Ch02. Java 서버를 Kotlin 서버로 리팩토링하자 - Domain 계층 리펙토링하기 (0) | 2022.11.01 |