SpringBoot Kotlin으로 작성하기 Getting Started with SpringBoot and Kotlin
- Writing Springboot using Kotlin
- Let’s implement an order model using h2 database and JPA
Development Environment
- Kotlin
- Springboot
- Gradle
- MacOS
Project Structure
.
├── HELP.md
├── README.md
├── SpringKotlin-Simple-Example.iml
├── build
├── build.gradle.kts
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
├── main
│ ├── kotlin
│ │ └── com
│ │ └── example
│ │ └── springkotlinsimpleexample
│ │ ├── SpringKotlinSimpleExampleApplication.kt
│ │ ├── controller
│ │ │ ├── OrderController.kt
│ │ │ └── SampleController.kt
│ │ ├── domain
│ │ │ ├── OrderModel.kt
│ │ │ └── dto
│ │ │ ├── CreateOrderModelDTO.kt
│ │ │ └── ReadOrderModelDTO.kt
│ │ ├── repository
│ │ │ └── OrderRepository.kt
│ │ └── service
│ │ └── OrderService.kt
│ └── resources
│ ├── application.yml
│ ├── static
│ └── templates
Dependency - gradle
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
compileOnly("org.projectlombok:lombok")
runtimeOnly("com.h2database:h2")
annotationProcessor("org.projectlombok:lombok")
testImplementation("org.springframework.boot:spring-boot-starter-test")
Creating Project Internal Configuration - application.yml
spring:
jpa:
open-in-view: false
generate-ddl: true
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
show_sql: true
format_sql: true
h2:
console:
enabled: true
Writing Project Code
- Domain Design
- DTO Design
- Repository Creation
- Service Creation
- Controller Creation
We’ll work in this order.
Domain Model
-
Create an OrderModel Kotlin data class inside the domain package
@Entity data class OrderModel( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Int? = null, val orderCode: String, val orderUserName: String, val orderUserPhone: String, val orderUserAddress: String, val orderUserEmail: String, val orderUserComment: String, val orderCreatedDate: OffsetDateTime = OffsetDateTime.now(), )- When using the created Entity, I plan to wrap it in a DTO for delivery.
- Let’s write DTOs to extract only the desired parts of data going into OrderModel or coming from OrderModel
-
Create a dto package and write
CreateOrderModelDTOandReadOrderModelDTObelow it// CreateOrderModelDTO.kt data class CreateOrderModelDTO( val id: Int? = null, val orderCode: String, val orderUserName: String, val orderUserPhone: String, val orderUserAddress: String, val orderUserEmail: String, val orderUserComment: String, ) { fun toEntity(): OrderModel { return OrderModel( orderCode = orderCode, orderUserName = orderUserName, orderUserPhone = orderUserPhone, orderUserAddress = orderUserAddress, orderUserEmail = orderUserEmail, orderUserComment = orderUserComment ) } }// ReadOrderModelDTO.kt data class ReadOrderModelDTO( val id: Int? = null, val orderCode: String, val orderUserName: String, val orderUserPhone: String, val orderUserAddress: String, val orderUserEmail: String, val orderUserComment: String, val orderCreatedDate: OffsetDateTime, )
-
Let’s complete the OrderModel data class based on the created
CreateOrderModelDTOandReadOrderModelDTO@Entity data class OrderModel( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Int? = null, val orderCode: String, val orderUserName: String, val orderUserPhone: String, val orderUserAddress: String, val orderUserEmail: String, val orderUserComment: String, val orderCreatedDate: OffsetDateTime = OffsetDateTime.now(), ) { fun getReadOrderDTO(): ReadOrderModelDTO { return ReadOrderModelDTO( id = id, orderCode = orderCode, orderUserName = orderUserName, orderUserPhone = orderUserPhone, orderUserAddress = orderUserAddress, orderUserEmail = orderUserEmail, orderUserComment = orderUserComment, orderCreatedDate = orderCreatedDate ) } fun createOrderModelDTO(): CreateOrderModelDTO { return CreateOrderModelDTO( orderCode = orderCode, orderUserName = orderUserName, orderUserPhone = orderUserPhone, orderUserAddress = orderUserAddress, orderUserEmail = orderUserEmail, orderUserComment = orderUserComment ) } override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false other as OrderModel return id != null && id == other.id } override fun hashCode(): Int = javaClass.hashCode() @Override override fun toString(): String { return this::class.simpleName + "(id = $id , orderCode = $orderCode , orderUserName = $orderUserName , orderUserPhone = $orderUserPhone , orderUserAddress = $orderUserAddress , orderUserEmail = $orderUserEmail , orderUserComment = $orderUserComment , orderCreatedDate = $orderCreatedDate )" } }
Creating Repository
-
Repository is created by extending JPARepository
interface OrderRepository : CrudRepository<OrderModel, String> { fun findAllByOrderUserName(orderCode: String): List<OrderModel> fun findByOrderCode(orderCode: String): OrderModel? }- The created Repository extends
OrderRepository OrderRepositoryprovides functions for reading, creating, updating, and deletingOrderModel- Write and use additional custom queries and functions
- Keep internal logic implementation flexible through interface work
- The created Repository extends
Creating Service
-
Write the service logic. Since this is still a basic example, it only handles saving data
@Component class OrderService { @Autowired lateinit var orderRepository: OrderRepository fun getAllOrders(): List<ReadOrderModelDTO> { val orders = orderRepository.findAll() return orders.map { it.getReadOrderDTO() } } fun getOrderByOrderUserName(orderUserName: String): List<ReadOrderModelDTO> { val order = orderRepository.findAllByOrderUserName(orderUserName) return order.map { it.getReadOrderDTO() } } @Transactional fun createOrder(order: CreateOrderModelDTO): CreateOrderModelDTO { val requestOrderCode: String = order.orderCode val oldOrderCode: String = orderRepository.findByOrderCode(requestOrderCode)?.orderCode ?: "" orderRepository.findByOrderCode(requestOrderCode).let { if (it != null) { throw IllegalArgumentException("Order Code is already exist") } } return orderRepository.save(order.toEntity()).createOrderModelDTO() } }- In the createOrder function, I wrote it so that duplicate
OrderCodecannot be saved
- In the createOrder function, I wrote it so that duplicate
Creating Controller
-
Let’s write code to save/read data using the created Service
@RestController @RequestMapping("/api/v1/orders") class OrderController { @Autowired private lateinit var orderService: OrderService @GetMapping(produces = ["application/json"]) fun getOrders(): ResponseEntity<Any> { return ResponseEntity.ok(orderService.getAllOrders()) } @GetMapping(value = ["/{orderUserName}"], produces = ["application/json"]) fun getOrderByUserName(orderUserName: String): ResponseEntity<Any> { return ResponseEntity.ok(orderService.getOrderByOrderUserName(orderUserName)) } @PostMapping() fun createOrder(@RequestBody createOrderDTO: CreateOrderModelDTO): ResponseEntity<Any> { orderService.createOrder(createOrderDTO) return ResponseEntity.ok().body(true) } }
댓글남기기