# Kotlin #### Kotlin 프로젝트를 시작하며 생긴 질문 - Gradle vs Maven: https://pearlluck.tistory.com/704 - Lombok을 사용할까 말까?: https://d2.naver.com/helloworld/6685007 #### Run Configuration - JRE: azul-17 Active profiles: local ![](https://i.imgur.com/SGbUBfC.png) #### JPA - build.gradle.kts jpa관련 plugin과 dependency를 추가하고 allOpen, noArg 설정을 추가함 - allOpen: 자동으로 모든 클래스(프로퍼티, 함수까지)를 open 시킴 open되지 않은 class는 Java에서 final과 동일 (final: class 상속 불가, method override 불가) - noArg: 설정한 어노테이션이 붙은 클래스에 자동으로 기본 생성자를 생성 ```kotlin plugins { ... kotlin("plugin.jpa") version "1.7.20" id("org.jetbrains.kotlin.plugin.allopen") version "1.7.20" id("org.jetbrains.kotlin.plugin.noarg") version "1.7.20" } dependencies { ... implementation("org.springframework.boot:spring-boot-starter-data-jpa") } noArg { annotation("javax.persistence.Entity") } allOpen { annotation("javax.persistence.Entity") annotation("javax.persistence.MappedSuperclass") annotation("javax.persistence.Embeddable") } ``` - entity ```kotlin @Entity @Table(name = "users") class UserEntity ( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null, // val은 getter만 var은 setter도 할당 @Column(name = "name") var name: String? = null, @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "department") var department: Department, @Column(name = "inserted_at") var insertedAt: ZonedDateTime? = ZonedDateTime.now(), @Convert(converter = UserStatusConverter::class) @Column(name = "status") var status: UserStatus? = UserStatus.ACTIVATED, ) enum class UserStatus(val status: String) { ACTIVATED("activated"), DEACTIVATED("deactivated"), INACTIVATED("inactivated"), INOPERATIVE("inoperative"); } @Converter class UserStatusConverter : AttributeConverter<UserStatus, String> { override fun convertToDatabaseColumn(UserStatus): String { return UserStatus.status } override fun convertToEntityAttribute(value: String): UserStatus { return UserStatus.valueOf(value.toUpperCase()) } } ``` - repository ```kotlin @Repository interface UserRepository : JpaRepository<UserEntity, Long> { fun findAllByVacationStartAtAndVacationEndAtAndPublicKey(userId: Long): MutableList<Users> // bad @Query(SELECT u FROM USER u WHERE u.vacationStartAt AND u.vacationEndAt AND u.publicKey) fun findAllByVacationTimeAndPKey(userId: Long): MutableList<Users> // good fun findByOrderById(pageable: Pageable): Page<User> // count 쿼리가 중복적으로 발생하는걸 방지하기 위해 전체 사이즈를 미리 구해놓고 함께 전달 fun findByOrderById(pageable: Pageable, Integer totalEleme): Page<User> } ``` - service ```kotlin @Service class UserService( private val userRepository: UserRepository, ) { fun findAll(): List<UserEntity> { return userRepository.findAll() } } ``` #### JPA + Auditoring jpa를 사용하다 보면 @CreatedBy @CreatedDate @UpdatedBy @LastModifiedDate등 annotation을 통해 Entity의 생성과정에서 셋팅되어야할 프로퍼티를 설정하고 싶을 수 있다. - @CreatedDate and @LastModifiedDate ```kotlin @SpringBootApplication @EnableJpaAuditing // JpaAuditing을 설정해주고 class MainApplication @Entity @EntityListeners(AuditingEntityListener::class) class SomeEntity { val id: Long? = 0, val name: String, ... @CreatedDate var createdDateTime: LocalDateTime = LocalDateTime.now() // 위 처럼 사용해주면 되는데 한가지 유의할 점은 val 대신 var을 사용해줘야 setter가 할당된다 } ``` - @CreatedBy and @LastModifiedBy - JpaAuditorConfig ```kotlin @Configuration @EnableJpaAuditing(modifyOnCreate = false) class JpaAuditingConfig { @Component class CustomAuditorAware : AuditorAware<String> { override fun getCurrentAuditor(): Optional<String> { val authentication = SecurityContextHolder.getContext().authentication val roles = authorities if (roles has user) return authentication.name else "Unknown User" } } } ``` - Entity ```kotlin @Entity @EntityListeners(AuditingEntityListener::class) class SomeEntity { val id: Long? = 0, val name: String, ... @CreatedBy var createdBy: String? = null // 위 처럼 사용해주면 되는데 한가지 유의할 점은 val 대신 var을 사용해줘야 setter가 할당된다 } ``` #### MySQL - build.gradle.kts ```kotlin dependencies { ... implementation("org.springframework.boot:spring-boot-starter-jdbc") implementation("mysql:mysql-connector-java") } ``` - application.yml ```yaml datasource: url: jdbc:mysql://ip:port/database_name username: username password: password ``` #### Spring Security - build.gradle.kts ```kotlin dependencies { ... implementation("org.springframework.boot:spring-boot-starter-security") implementation("io.jsonwebtoken:jjwt-api:0.11.2") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.2") runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.2") } ``` - WebSecurityConfig ```kotlin @Configuration @EnableWebSecurity class WebSecurityConfig( private val jwtFilter: JwtFilter ) : WebSecurityConfigurerAdapter() { override fun configure(http: HttpSecurity) { http.httpBasic().disable() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .requestMatchers(RequestMatcher { request: HttpServletRequest? -> CorsUtils.isPreFlightRequest(request!!) }).permitAll() .anyRequest().hasAuthority("user") // role이 아니라 authority .and() .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter::class.java) } } ``` - JwtUtil RSA256기반 public key를 사용한 token parsing 기법 kotlin 사용에 중점을 둔 프로젝트라 토큰 생성은 이곳에서 다루지 않음 ```kotlin class JwtUtils() { val publicKey: String = "publicKey" // 토큰발취 fun resolveToken(req: HttpServletRequest): String? { val authHeader: String? = req.getHeader("Authorization") var tokenString: String? = null if (authHeader != null && authHeader.startsWith("Bearer ")) { tokenString = authHeader.replace("Bearer ".toRegex(), "").trim() } return tokenString } // 토큰검증 fun validateToken(token: String?): Boolean { return try { val claims: Claims = getAllClaims(token) return claims.expiration.after(Date()) } catch (e: Exception) { false } } // username으로 Authentcation객체 생성 fun getAuthentication(token: String): Authentication { val claims: Claims = getAllClaims(token) return UsernamePasswordAuthenticationToken(claims.subject, null, listOf(SimpleGrantedAuthority("user"))) } // 모든 Claims 조회 private fun getAllClaims(token: String?): Claims { val kf: KeyFactory = KeyFactory.getInstance("RSA") val pubKeySpecX509EncodedKeySpec = X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)) val key = kf.generatePublic(pubKeySpecX509EncodedKeySpec) return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .body } } ``` - JwtFilter ```kotlin @Component class JwtFilter(private val jwtUtils: JwtUtils) : GenericFilterBean() { @Throws(IOException::class, ServletException::class) override fun doFilter(request: ServletRequest?, response: ServletResponse?, filterChain: FilterChain) { val token: String? = (request as HttpServletRequest?)?.let { jwtUtils.resolveToken(it) } if (token != null && jwtUtils.validateToken(token)) { val auth: Authentication = jwtUtils.getAuthentication(token) SecurityContextHolder.getContext().authentication = auth } filterChain.doFilter(request, response) } } ``` #### Swagger - build.gradle.kts ```kotlin dependencies { ... implementation("io.springfox:springfox-boot-starter:3.0.0") implementation("io.springfox:springfox-swagger-ui:3.0.0") } ``` - SwaggerConfig ```kotlin @Configuration @EnableSwagger2 class SwaggerConfig { @Bean fun api(): Docket { return Docket(DocumentationType.SWAGGER_2) .useDefaultResponseMessages(false) .select() .apis(RequestHandlerSelectors.basePackage("path.to.api.package")) .paths(PathSelectors.any()) .build() } ``` - WebSecurityConfig ```kotlin override fun configure(http: HttpSecurity) { http ... .antMatchers("/swagger-resources/**").permitAll() // swagger resource api filter 허용 } // 정적 요소 필터 제외 override fun configure(web: WebSecurity) { web.ignoring().antMatchers( "/v2/api-docs", "/swagger-resources/**", "/swagger-ui/**", "/webjars/**", "/swagger/**" ) } ``` #### Logging - build.gradle.kts ```kotlin dependencies { ... runtimeOnly("io.github.microutils:kotlin-logging-jvm:3.0.2") } ``` - logback.xml ```xml <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} MDC=%X{user} - %msg%n</pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="STDOUT" /> </root> </configuration> ``` - service.kts ```kotlin @Service class Service( private val someRepository: SomeRepository, ) { private val logger = KotlinLogging.logger {} fun logTest(arg1: String, arg2: String) { logger.debug { "Hello World arg1=$arg1 arg2=$arg2" } } } ```