execute mysql server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // docker-compose for execute mysql server in docker
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql-container
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: blog_db
MYSQL_USER: user
MYSQL_PASSWORD: user_password
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
|
install dependency
1
2
3
4
5
| dependencies {
...
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("mysql:mysql-connector-java:8.0.32")
}
|
set configuration to access mysql server
1
2
3
4
5
6
7
8
9
| // application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/blog_db
spring.datasource.username=user
spring.datasource.password=user_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
|
blog application을 실행시켰을 때 정상적으로 서버가 띄어지면, db connection에 성공한 것으로 생각할 수 있다.
define entity
PrimaryKeyEntity
모든 entity에 대해 UUID를 이용해 PK를 관리하고, Persistable
interface를 구현해서 isNew 메서드를 커스텀화하고 isNew property에 대한 상태관리를 좀 더 구체화하기 위해 PrimaryKeyEntity를 정의하고자 한다.
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
32
33
34
35
36
37
38
39
40
41
42
|
@MappedSuperclass
abstract class PrimaryKeyEntity : Persistable<UUID> {
@Id
@Column(columnDefinition = "uuid")
private val id : UUID = UUID.nameUUIDFromBytes(ULID().nextValue().toString().toByteArray())
@Transient
private var _isNew : Boolean = true
override fun getId() = id
override fun isNew() = _isNew
override fun equals(other: Any?): Boolean {
if(other == null){
return false
}
if(other !is HibernateProxy && this::class != other::class){
return false
}
return id == getIdentifier(other)
}
private fun getIdentifier(obj: Any) : Serializable {
return if(obj is HibernateProxy){
obj.hibernateLazyInitializer.identifier as Serializable
}else{
(obj as PrimaryKeyEntity).id
}
}
override fun hashCode(): Int = Objects.hashCode(id)
@PostPersist
@PostLoad
protected fun load(){
_isNew = false
}
}
|
@MappedSuperclass
JPA 에서 사용하는 annotation으로, 이 annotation이 정의된 클래스를 상속받는 모든 클래스들이 PrimaryKeyEntity에 정의된 매핑 정보를 상속받을 수 있도록 해주는 annotation이다.
@Transient
@Transient annotation은 객체레벨에서만 필드를 쓰도록 허용하는 것이며, 실제 엔티티에 해당 필드를 생성하지 않게 하는 annotation이다.
@PostPersist
entity가 persist 된 후에 해당 annotation이 할당된 함수를 실행하라는 의미이다.
@PostLoad
entity가 데이터베이스에서 로드되었을 때(조회) 해당 annotation이 할당된 함수를 실행하라는 의미이다.
equals()/hashCode()를 overriding
위 2개 함수를 오버라이딩하여, 동등성 비교에 대한 로직을 커스텀화하였다.
BlogEntity
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
32
33
34
35
36
37
38
39
40
41
42
43
| @Entity
class PostEntity(
title: String,
content: String,
information: PostInformation
) : PrimaryKeyEntity() {
@Column(nullable = false)
var createdAt : LocalDateTime = LocalDateTime.now()
protected set
@Column(nullable = false)
var title: String = title
protected set
@Column(nullable = false)
var content: String = content
protected set
@Embedded
var information : PostInformation = information
protected set
fun update(data: PostUpdateData){
this.title=data.title
this.information=data.information
this.content=data.content
}
}
@Embeddable
data class PostInformation(
@Column
val link: String?,
@Column(nullable = false)
val rank: Int
)
data class PostUpdateData(
val title:String,
val content: String,
val information: PostInformation
)
|
여기서 information 필드를 보면, @Embeddable
로 선언된 PostInformation data class 를 @Embedded
한 것을 볼 수 있다. 이렇게 함으로써 엔티티 설계를 좀 더 객체지향적으로 설계할 수 있게 된다. 물리적으로는 PostInformation 에 있는 필드들은 Post entity의 필드로써 들어가게 된다.
create simple api to create/get/update/delete blog
repository
1
| interface PostRepository : JpaRepository<Post, String>{}
|
service
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
|
@Service
class PostService(
private val postRepository: PostRepository
){
fun getAllPosts() : List<Post> = this.postRepository.findAll()
fun getById(id: String) : Post? = this.postRepository.findById(id).orElseThrow()
@Transactional
fun create(data : PostCreationPayload) {
this.postRepository.save(data.toEntity())
}
@Transactional
fun update(id:String, data : PostUpdatePayload){
getById(id)?.apply { update(data.toData()) }
}
@Transactional
fun delete(id: String){
postRepository.deleteById(id)
}
}
|
controller
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
32
33
34
|
@RestController
@RequestMapping("/api/posts")
class PostController (
private val postService: PostService
){
@GetMapping
fun getAllPosts() : List<Post> = postService.getAllPosts()
@GetMapping("/{id}")
fun getPostById(@PathVariable id : String) : ResponseEntity<Post> {
val post = postService.getById(id)
return if(post!=null){
ResponseEntity.ok(post)
}else{
ResponseEntity.notFound().build()
}
}
@PostMapping
fun createPost(@RequestBody data : PostCreationPayload){
postService.create(data)
}
@PatchMapping("/{id}")
fun updatePost(@PathVariable id: String,@RequestBody data : PostUpdatePayload){
postService.update(id, data)
}
@DeleteMapping("/{id}")
fun deletePost(@PathVariable id : String){
postService.delete(id)
}
}
|