일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- kotlin querydsl
- 티스토리챌린지
- 공부
- kotlin coroutine
- minikube
- Kubernetes
- CloudWatch
- IntelliJ
- 정보처리기사 실기 기출문제
- mysql 튜닝
- Linux
- Java
- 코틀린 코루틴의 정석
- kotlin
- CKA
- Pinpoint
- PETERICA
- 정보처리기사 실기
- 기록으로 실력을 쌓자
- AI
- APM
- AWS EKS
- kotlin spring
- CKA 기출문제
- aws
- 정보처리기사실기 기출문제
- 오블완
- MySQL
- Elasticsearch
- Spring
- Today
- Total
피터의 개발이야기
[kotlin] Springboot - JPA 의존성 주입 본문
ㅁ 들어가며
ㅇ spring boot tutorial를 참조하여 나만의 확장 프로그램 만들기를 구현해 보았습니다.
ㅁ Gradle 수정
plugins {
...
kotlin("plugin.allopen") version "1.9.22"
}
allOpen {
annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.Embeddable")
annotation("jakarta.persistence.MappedSuperclass")
}
ㅇ 코틀린에서 JPA를 사용하기 위해서는 allopen 플러그인을 사용해야한다.
ㅇ allopen 플로그인과 JPA를 사용하기 위한 추가 annotation을 설정하였다.
ㅇ 수정파일: build.gradle.kts
ㅁEntity 생성
@Entity
class Article(
var title: String,
var headline: String,
var content: String,
@ManyToOne var author: User,
var slug: String = title.toSlug(),
var addedAt: LocalDateTime = LocalDateTime.now(),
@Id @GeneratedValue var id: Long? = null)
@Entity
class User(
var login: String,
var firstname: String,
var lastname: String,
var description: String? = null,
@Id @GeneratedValue var id: Long? = null)
ㅇ 속성과 생성자 매개변수를 동시에 선언할 수 있는 Kotlin 기본 생성자 간결 구문을 사용하여 모델을 생성한다.
ㅇ 파일명: Entities.kt
ㅇ 생성자의 매개변수 String.toSlug()에 기본 인수를 제공하기 위해 확장을 사용하고 있다 .
ㅇ 기본값이 있는 선택적 매개변수는 위치 인수 사용 시 생략이 가능하도록 마지막 위치에 정의된다.
( Kotlin은 명명된 인수도 지원합니다 ).
ㅇ Kotlin에서는 동일한 파일에 간결한 클래스 선언을 그룹화하는 것이 일반적이다.
ㅇ 여기서는 JPA가 불변 클래스 또는 클래스에 의해 자동으로 생성된 메소드와 작동하도록 설계되지 않았기 때문에 속성이 있는 data클래스를 사용하지 않는다.
ㅇ 다른 Spring Data 플레이버를 사용하는 경우 대부분은 이러한 구성을 지원하도록 설계되었으므로 Spring Data MongoDB, Spring Data JDBC 등을 사용할 때와 같은 클래스를 사용해야 한다.
ㅁ Spring Data JPA 저장소
interface ArticleRepository : CrudRepository<Article, Long> {
fun findBySlug(slug: String): Article?
fun findAllByOrderByAddedAtDesc(): Iterable<Article>
}
interface UserRepository : CrudRepository<User, Long> {
fun findByLogin(login: String): User?
}
ㅇ Repositories.kt
ㅁ JPA 테스트를 작성
@DataJpaTest
class RepositoriesTests @Autowired constructor(
val entityManager: TestEntityManager,
val userRepository: UserRepository,
val articleRepository: ArticleRepository) {
@Test
fun `When findByIdOrNull then return Article`() {
val johnDoe = User("johnDoe", "John", "Doe")
entityManager.persist(johnDoe)
val article = Article("Lorem", "Lorem", "dolor sit amet", johnDoe)
entityManager.persist(article)
entityManager.flush()
val found = articleRepository.findByIdOrNull(article.id!!)
assertThat(found).isEqualTo(article)
}
@Test
fun `When findByLogin then return User`() {
val johnDoe = User("johnDoe", "John", "Doe")
entityManager.persist(johnDoe)
entityManager.flush()
val user = userRepository.findByLogin(johnDoe.login)
assertThat(user).isEqualTo(johnDoe)
}
}
ㅇ RepositoriesTests.kt
ㅁ 블러그 템플릿 수정
ㅇ "블로그" Mustache 템플릿을 업데이트한다.
{{> header}}
<h1>{{title}}</h1>
<div class="articles">
{{#articles}}
<section>
<header class="article-header">
<h2 class="article-title"><a href="/article/{{slug}}">{{title}}</a></h2>
<div class="article-meta">By <strong>{{author.firstname}}</strong>, on <strong>{{addedAt}}</strong></div>
</header>
<div class="article-description">
{{headline}}
</div>
</section>
{{/articles}}
</div>
{{> footer}}
ㅇ blog.mustache
{{> header}}
<section class="article">
<header class="article-header">
<h1 class="article-title">{{article.title}}</h1>
<p class="article-meta">By <strong>{{article.author.firstname}}</strong>, on <strong>{{article.addedAt}}</strong></p>
</header>
<div class="article-description">
{{article.headline}}
{{article.content}}
</div>
</section>
{{> footer}}
ㅇ 기사 템플릿 수정
ㅇ article.mustache
ㅁ HtmlController
@Controller
class HtmlController(private val repository: ArticleRepository) {
@GetMapping("/")
fun blog(model: Model): String {
model["title"] = "Blog"
model["articles"] = repository.findAllByOrderByAddedAtDesc().map { it.render() }
return "blog"
}
@GetMapping("/article/{slug}")
fun article(@PathVariable slug: String, model: Model): String {
val article = repository
.findBySlug(slug)
?.render()
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "This article does not exist")
model["title"] = article.title
model["article"] = article
return "article"
}
fun Article.render() = RenderedArticle(
slug,
title,
headline,
content,
author,
addedAt.format()
)
data class RenderedArticle(
val slug: String,
val title: String,
val headline: String,
val content: String,
val author: User,
val addedAt: String)
}
ㅇ 형식화된 날짜로 블로그 및 기사 페이지를 렌더링하기 위해 HtmlControlle를 업데이트한다.
ㅁ 초기 데이터 생성
@Configuration
class BlogConfiguration {
@Bean
fun databaseInitializer(userRepository: UserRepository,
articleRepository: ArticleRepository) = ApplicationRunner {
val johnDoe = userRepository.save(User("johnDoe", "John", "Doe"))
articleRepository.save(Article(
title = "Lorem",
headline = "Lorem",
content = "dolor sit amet",
author = johnDoe
))
articleRepository.save(Article(
title = "Ipsum",
headline = "Ipsum",
content = "dolor sit amet",
author = johnDoe
))
}
}
ㅇ 새 클래스에 데이터를 주입하기 위해 BlogConfiguration.kt 생성한다.
ㅇ Configuration은 빈 생성 주기 시 한번만 실행된다.
ㅁ TestCase 수정
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTests(@Autowired val restTemplate: TestRestTemplate) {
@BeforeAll
fun setup() {
println(">> Setup")
}
@Test
fun `Assert blog page title, content and status code`() {
println(">> Assert blog page title, content and status code")
val entity = restTemplate.getForEntity<String>("/")
assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)
assertThat(entity.body).contains("<h1>Blog</h1>", "Lorem")
}
@Test
fun `Assert article page title, content and status code`() {
println(">> Assert article page title, content and status code")
val title = "Lorem"
val entity = restTemplate.getForEntity<String>("/article/${title.toSlug()}")
assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)
assertThat(entity.body).contains(title, "Lorem", "dolor sit amet")
}
@AfterAll
fun teardown() {
println(">> Tear down")
}
}
ㅇ API의 변경에 따라 새로운 테스트 케이스를 IntegrationTests에 작성한다.
ㅁ Test
ㅇ localhost:8080 초기화면에서 blog 리스트가 출력되고, 아티클 클릭 시 상세 화면이 출력된다.
'Programming > Kotlin' 카테고리의 다른 글
[kotlin] Springboot - Configuration properties (0) | 2024.05.25 |
---|---|
[kotlin] Springboot - RestController 생성 및 WebMvcTest (0) | 2024.05.24 |
[kotlin] Kotlin에서 Java로, Java에서 Kotlin으로 코드 변환 (0) | 2024.05.22 |
[kotlin] Springboot - 나만의 확장 프로그램 만들기 (0) | 2024.05.21 |
[kotlin] Springboot - JUnit5 테스트 작성, 테스트 클래스 인스턴스화 (0) | 2024.05.17 |