[Spring Boot] DynamoDB

(Modify : 2023-08-24)

Amazon DynamoDB

완전 관리형 NoSQL Database(비관계형 데이터베이스) Service

Amazon Web Services에서만 사용 가능한 Serverless Instance로 서버를 직접 관리할 필요가 없다.

데이터를 Key-Value 구조로 저장하며 JSON을 지원한다.

스키마가 없기 때문에 테이블을 생성할 때 기본 키 외에 추가 속성에 대해서는 정의하지 않는다.

데이터 유형이 String, Number, Binary로 제한되어 있다.

가변 요금제로 데이터 읽기, 쓰기, 저장 등 사용량에 따라 요금이 부과된다.

백업, 배포, 확장이 편리하다.

Spring Boot와 DynamoDB

Spring Data에서 공식 지원하지 않는다. (링크)

@Transaction을 사용할 수 없다.

Git Library

  • boostchicken: 3번째 분기된 라이브러리. 2020년 이후 업데이트되지 않고 있다.

  • derjust: 2.2부터 지원되지 않는다.

  • 최신 라이브러리?: boostchicken Issue(링크)에 따르면 awspring이 언급되지만 확실하지 않음

Docker localstack

local에서 테스트할 수 있는 AWS 클라우드 환경을 제공한다.

AWS 서비스를 대부분 지원한다. (AWS Service Feature Coverage)

docker-compose.yml (example link)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

version: "3"
services:
  aws:
    container_name: exam-aws
    image: localstack/localstack
    platform: linux/amd64
    ports:
      - "4566:4566"            # LocalStack Gateway
      - "4510-4559:4510-4559"  # external services port range
    environment:
      - SERVICE=dynamodb       # 사용할 서비스
      - DEBUG=1
      - DOCKER_HOST=unix:///var/run/docker.sock
      # Container 내부에서 aws-cli를 사용하기 위해 설정. awslocal 사용 시 미지정
      - AWS_ACCESS_KEY_ID=dev     
      - AWS_SECRET_ACCESS_KEY=dev
      - AWS_DEFAULT_REGION=ap-northeast-2
    volumes:
      - "./localstack/data:/var/lib/localstack"
      - "./localstack/init/ready.d:/etc/localstack/init/ready.d"
    restart: unless-stopped

create_dynamodb.sh (./localstack/init/ready.d)

Plain Text

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

#!/bin/bash

echo $(aws dynamodb create-table \
--endpoint-url http://localhost:4566 \
--table-name ad_event \
--attribute-definitions \
 AttributeName=id,AttributeType=S \
--key-schema \
 AttributeName=id,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5)

echo $(aws dynamodb create-table \
--endpoint-url http://localhost:4566 \
--table-name button_event \
--attribute-definitions \
 AttributeName=id,AttributeType=N \
 AttributeName=username,AttributeType=S \
--key-schema \
 AttributeName=id,KeyType=HASH \
 AttributeName=username,KeyType=RANGE \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5)

echo $(aws dynamodb create-table \
--endpoint-url http://localhost:4566 \
--table-name click_event \
--attribute-definitions \
 AttributeName=id,AttributeType=N \
 AttributeName=reg_dtm,AttributeType=S \
--key-schema \
 AttributeName=id,KeyType=HASH \
 AttributeName=reg_dtm,KeyType=RANGE \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5)

echo $(aws dynamodb create-table \
--endpoint-url http://localhost:4566 \
--table-name scroll_event \
--attribute-definitions \
 AttributeName=id,AttributeType=S \
--key-schema \
 AttributeName=id,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5)

Spring Boot 방법 1. GIT Library boostchicken

build.gradle.kts

Kotlin

1

2

implementation("com.amazonaws:aws-java-sdk-dynamodb:1.12.533")
implementation("io.github.boostchicken:spring-data-dynamodb:5.2.5")

Config

Kotlin

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

44

45

46

47

import com.amazonaws.auth.AWSCredentialsProvider
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain
import com.amazonaws.client.builder.AwsClientBuilder
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig
import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary

@Configuration
@EnableDynamoDBRepositories(basePackages = ["com.example.tracker"])
class DynamoDBConfig(
    @Value("\NULL") private val endpoint: String,
    @Value("\NULL") private val region: String
) {

    @Bean
    @Primary
    fun dynamoDBMapper(amazonDynamoDB: AmazonDynamoDB, dynamoDBMapperConfig: DynamoDBMapperConfig): DynamoDBMapper {
        return DynamoDBMapper(amazonDynamoDB, dynamoDBMapperConfig)
    }

    @Bean
    @Primary
    fun awsCredentialsProvider(): AWSCredentialsProvider {
        return DefaultAWSCredentialsProviderChain.getInstance() // 자격 증명을 환경변수, 프로필 파일(~/.aws/credentials) 순으로 탐색
    }

    @Bean
    @Primary
    fun dynamoDBMapperConfig(): DynamoDBMapperConfig {
        return DynamoDBMapperConfig.DEFAULT
    }

    @Bean
    fun amazonDynamoDB(): AmazonDynamoDB {
        return AmazonDynamoDBClientBuilder
            .standard()
            .withEndpointConfiguration(AwsClientBuilder.EndpointConfiguration(endpoint, region))
            .withCredentials(awsCredentialsProvider())
            .build()
    }
}

Domain

Kotlin

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

import com.amazonaws.services.dynamodbv2.datamodeling.*
import com.example.tracker.common.converter.DynamoDbLocalDateTimeConverter
import java.time.LocalDateTime
import java.util.*

/**
 * required: var, default value
 */
@DynamoDBTable(tableName = "ad_event")
data class AdEvent(
    @DynamoDBHashKey
    @DynamoDBAutoGeneratedKey
    var id: String = UUID.randomUUID().toString(),

    @DynamoDBAttribute(attributeName = "session_key")
    var sessionKey: String = "",

    @DynamoDBAttribute(attributeName = "reg_dtm")
    @DynamoDBTypeConverted(converter = DynamoDbLocalDateTimeConverter::class)
    var regDtm: LocalDateTime = LocalDateTime.now()
)

데이터 타입 변환 설정

DynamoDB 속성 타입은 String, Number, Binary만 존재하기 때문에 날짜로 변환하기 위한 Converter가 필요하다

Kotlin

1

2

3

4

5

6

7

8

9

10

11

12

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter
import java.time.LocalDateTime

class DynamoDbLocalDateTimeConverter : DynamoDBTypeConverter<String, LocalDateTime> {
    override fun convert(date: LocalDateTime?): String? {
        return date?.toString()
    }

    override fun unconvert(value: String?): LocalDateTime? {
        return value?.let { LocalDateTime.parse(it) }
    }
}

Repository

Kotlin

1

2

3

4

5

6

7

8

9

10

11

import com.example.tracker.ad.domain.AdEvent
import org.socialsignin.spring.data.dynamodb.repository.EnableScan
import org.springframework.data.repository.CrudRepository
import java.time.LocalDateTime

@EnableScan
interface AdEventRepository : CrudRepository<AdEvent, String> {

    fun findByRegDtmGreaterThan(regDtm: LocalDateTime): List<AdEvent>

}

Service

Kotlin

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

@Service
class AdEventService(
    private val adEventRepository: AdEventRepository
) {

    fun save(sessionKey: String) {
        adEventRepository.save(
            AdEvent(
                sessionKey = sessionKey
            )
        )
    }

    fun findAll(): List<AdEvent> {
        return adEventRepository.findAll().toList()
    }

    fun findAdEvent(regDtm: LocalDateTime): List<AdEvent> {
        return adEventRepository.findByRegDtmGreaterThan(regDtm)
    }

}

Spring Boot 방법 2. GIT Library, Primary Key

build.gradle.kts (위 예시와 동일)

Kotlin

1

2

implementation("com.amazonaws:aws-java-sdk-dynamodb:1.12.533")
implementation("io.github.boostchicken:spring-data-dynamodb:5.2.5")

Config

Plain Text

1

방법 1과 동일

Domain

Kotlin

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

/**
 * DynamoDB는 Schema가 없기 때문에 기본값을 NULL로 하는게 안전
 */
@DynamoDBTable(tableName = "click_event")
data class ClickEvent(
    @get:DynamoDBHashKey
    var id: Long? = null,

    @get:DynamoDBRangeKey(attributeName = "reg_dtm")
    @DynamoDBTypeConverted(converter = DynamoDbLocalDateTimeConverter::class)
    var regDtm: LocalDateTime? = null,

    @DynamoDBAttribute(attributeName = "json_data")
    var jsonData: String? = null
) {

    // Spring Boot 3 (jakarta) 대응이 아직 안되어 있다.
    @org.springframework.data.annotation.Id
    private var key: PrimaryKey? = null
        get() {
            return PrimaryKey(id, regDtm)
        }

}

Domain Key

Kotlin

1

2

3

4

5

6

7

8

data class PrimaryKey(
    @DynamoDBHashKey
    var id: Long? = null,

    @DynamoDBRangeKey(attributeName = "reg_dtm")
    @DynamoDBTypeConverted(converter = DynamoDbLocalDateTimeConverter::class)
    var regDtm: LocalDateTime? = null
)

Repository

Kotlin

1

2

3

4

5

6

7

8

9

10

11

12

13

package com.example.tracker.click.repository

import com.example.tracker.click.domain.ClickEvent
import com.example.tracker.common.domain.PrimaryKey
import org.socialsignin.spring.data.dynamodb.repository.EnableScan
import org.springframework.data.repository.CrudRepository

@EnableScan
interface ClickEventRepository : CrudRepository<ClickEvent, PrimaryKey> {

    fun findByKey(key: PrimaryKey): List<ClickEvent>

}

Service

Kotlin

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

@Service
class ClickEventService(
    private val clickEventRepository: ClickEventRepository
) {

    fun save() {
        clickEventRepository.save(
            ClickEvent(
                id = 8L,
                regDtm = LocalDateTime.now(),
                jsonData = Gson().toJson(JsonData("abcd"))
            )
        )
    }

    fun findAll(): List<ClickEvent> {
        return clickEventRepository.findAll().toList()
    }

    fun findByKey(key: PrimaryKey): List<ClickEvent> {
        return clickEventRepository.findByKey(key)
    }

}

Spring Boot 방법 3. AWS SDK Kotlin

build.gradle.kts

Kotlin

1

2

implementation("aws.sdk.kotlin:dynamodb-jvm:0.21.4-beta")
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.11")

Client Provider

Kotlin

1

2

3

4

5

6

7

8

9

10

11

@Component
class DynamoDbClientProvider(
    @Value("\NULL") private val endpoint: String,
    @Value("\NULL") private val regions: String
) {
    fun client() = DynamoDbClient {
        region = regions
        endpointUrl = Url.parse(endpoint)
        credentialsProvider = EnvironmentCredentialsProvider()
    }
}

Domain

Kotlin

1

2

3

4

5

6

7

import java.time.LocalDateTime

data class ClickEvent(
    var id: Long? = null,
    val regDtm: LocalDateTime? = null,
    val jsonData: String? = null
)

Repository

Kotlin

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

44

45

46

47

48

49

50

51

52

53

54

55

56

import aws.sdk.kotlin.services.dynamodb.model.AttributeValue
import aws.sdk.kotlin.services.dynamodb.model.QueryRequest
import com.example.tracker.click.domain.ClickEvent
import com.example.tracker.common.provider.DynamoDbClientProvider
import kotlinx.coroutines.runBlocking
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

@Repository
class ClickEventRepository(
    private val clientProvider: DynamoDbClientProvider
) {

    companion object {
        const val TABLE_NAME = "click_event"
    }

    fun findByRegDtmBetween(
        partitionVal: Long,
        startDtm: LocalDateTime,
        endDtm: LocalDateTime
    ): List<ClickEvent> {
        val list = mutableListOf<ClickEvent>()

        runBlocking {
            val attr = mutableMapOf<String, AttributeValue>()
            attr[":a"] = AttributeValue.N(partitionVal.toString())
            attr[":t1"] = AttributeValue.S(startDtm.toString())
            attr[":t2"] = AttributeValue.S(endDtm.toString())

            val request = QueryRequest {
                tableName = TABLE_NAME
                keyConditionExpression = "id = :a AND reg_dtm BETWEEN :t1 AND :t2"
                expressionAttributeValues = attr
            }
            clientProvider.client().use { ddb ->
                ddb.query(request).items?.forEach {
                    val id = it.getValue("id").asN()
                    val regDtm = it.getValue("reg_dtm").asS()
                    val jsonData = it.getValue("json_data").asS()
                    list.add(
                        ClickEvent(
                            id = id.toLong(),
                            regDtm = LocalDateTime.parse(regDtm, DateTimeFormatter.ISO_DATE_TIME),
                            jsonData = jsonData
                        )
                    )
                }
            }
        }

        return list
    }

}

Service

Kotlin

1

2

3

4

5

6

7

8

9

10

11

12

13

@Service
class ClickEventService(
    private val clickEventRepository: ClickEventRepository
) {
    fun findByRegDtmBetween(
        partitionVal: Long,
        startDtm: LocalDateTime,
        endDtm: LocalDateTime
    ): List<ClickEvent> {
        return clickEventRepository.findByRegDtmBetween(partitionVal, startDtm, endDtm)
    }

}

Spring Boot 방법 4. AWS SDK Kotlin 2

build.gradle.kts

Kotlin

1

2

implementation("aws.sdk.kotlin:dynamodb-jvm:0.21.4-beta")
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.11")

service

Kotlin

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

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

import aws.sdk.kotlin.runtime.auth.credentials.EnvironmentCredentialsProvider
import aws.sdk.kotlin.services.dynamodb.DynamoDbClient
import aws.sdk.kotlin.services.dynamodb.model.*
import aws.smithy.kotlin.runtime.net.Url
import kotlinx.coroutines.runBlocking
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service

@Service
class ScrollEventService(
    @Value("\NULL") private val endpoint: String,
    @Value("\NULL") private val regions: String
) {
    companion object {
        const val TABLE_NAME = "scroll_event"
    }

    fun getAll() {
        runBlocking {
            val request = ScanRequest {
                tableName = "scroll_event"
            }

            getClient().use { ddb ->
                val response = ddb.scan(request)
                response.items?.forEach { item ->
                    item.keys.forEach { key ->
                        println("key name: $key / value: ${item[key]}")
                    }
                }
            }
        }
    }

    fun putItem(
        key: String,
        keyVal: String,
        columnKey1: String,
        columnKey1Val: String
    ) {
        runBlocking {
            val itemValues = mutableMapOf<String, AttributeValue>()
            itemValues[key] = AttributeValue.S(keyVal)
            itemValues[columnKey1] = AttributeValue.S(columnKey1Val)

            val request = PutItemRequest {
                tableName = TABLE_NAME
                item = itemValues
            }

            getClient().use { ddb ->
                ddb.putItem(request)
                println("put item: $TABLE_NAME / key val: $keyVal")
            }
        }
    }

    /**
     * @param keyName = Required key
     */
    fun getItem(keyName: String, keyVal: String) {
        runBlocking {
            val keyToGet = mutableMapOf<String, AttributeValue>()
            keyToGet[keyName] = AttributeValue.S(keyVal)

            val request = GetItemRequest {
                key = keyToGet
                tableName = TABLE_NAME
            }

            getClient().use { ddb ->
                val returnedItem = ddb.getItem(request)
                val numberMap = returnedItem.item
                numberMap?.forEach { key ->
                    println("key name: NULL / value: NULL")
                }
            }
        }
    }

    fun keyConditionExpressionsForQuery(
        partitionAlias: String,
        partitionKeyName: String,
        partitionKeyVal: String
    ) {
        runBlocking {
            val attrValues = mutableMapOf<String, AttributeValue>()
            attrValues[":$partitionAlias"] = AttributeValue.S(partitionKeyVal)

            val request = QueryRequest {
                tableName = TABLE_NAME
                keyConditionExpression = "$partitionKeyName = :$partitionAlias"
                expressionAttributeValues = attrValues
            }

            getClient().use { ddb ->
                val response = ddb.query(request)
                println("count: NULL")
                response.items?.forEach { item ->
                    item.forEach { map ->
                        println("key: NULL / value: NULL")
                    }
                    println("--------------------")
                }
            }
        }
    }

    fun filterForQuery(
        filterKeyAlias: String,
        filterKey: String,
        filterKeyValAlias: String,
        filterKeyVal: String,
        partitionAlias: String,
        partitionKeyName: String,
        partitionKeyVal: String,
    ) {
        runBlocking {
            val attrNameAlias = mutableMapOf<String, String>()
            attrNameAlias["#NULL"] = filterKey

            val attrValues = mutableMapOf<String, AttributeValue>()
            attrValues[":$partitionAlias"] = AttributeValue.S(partitionKeyVal)
            attrValues[":$filterKeyValAlias"] = AttributeValue.S(filterKeyVal)

            val request = QueryRequest {
                tableName = TABLE_NAME
                keyConditionExpression = "$partitionKeyName = :$partitionAlias"
                filterExpression = "#NULL = :$filterKeyValAlias"
                expressionAttributeNames = attrNameAlias
                expressionAttributeValues = attrValues
            }

            getClient().use { ddb ->
                val response = ddb.query(request)
                println("count: NULL")
                response.items?.forEach { item ->
                    item.forEach { map ->
                        println("key: NULL / value: NULL")
                    }
                    println("--------------------")
                }
            }
        }
    }

    private fun getClient() = DynamoDbClient {
        region = regions
        credentialsProvider = EnvironmentCredentialsProvider()
        endpointUrl = Url.parse(endpoint)
    }

}

Spring Boot 방법 5. AWS SDK JAVA

build.gradle.kts

Kotlin

1

2

implementation("software.amazon.awssdk:dynamodb-enhanced")
implementation("io.reactivex.rxjava2:rxjava:2.2.21")

Config

Kotlin

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

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient
import java.net.URI

@Configuration
class DynamoDBConfig(
    @Value("\NULL") private val endpoint: String,
    @Value("\NULL") private val region: String,
    @Value("\${cloud.aws.access-key-id}") private val accessKeyId: String,
    @Value("\${cloud.aws.secret-access-key}") private val secretAccessKeyId: String,
) {

    @Bean
    fun dynamoDbAsyncClient(): DynamoDbAsyncClient {
        return DynamoDbAsyncClient.builder()
            .region(Region.of(region))
            .endpointOverride(URI.create(endpoint))
            .credentialsProvider(
                StaticCredentialsProvider.create(
                    AwsBasicCredentials.create(
                        accessKeyId,
                        secretAccessKeyId
                    )
                )
            )
            .build()
    }

    @Bean
    fun dynamoDbEnhancedAsyncClient(): DynamoDbEnhancedAsyncClient {
        return DynamoDbEnhancedAsyncClient.builder()
            .dynamoDbClient(dynamoDbAsyncClient())
            .build()
    }

}

Domain

Kotlin

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.LocalDateTimeAttributeConverter
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*
import java.time.LocalDateTime

@DynamoDbBean
data class ButtonEvent(
    @get:DynamoDbPartitionKey
    var id: Long? = null,

    @get:DynamoDbSortKey
    var username: String? = null,

    @get:DynamoDbAttribute("button_id")
    var buttonId: Long? = null,

    @get:DynamoDbAttribute("reg_dtm")
    @get:DynamoDbConvertedBy(LocalDateTimeAttributeConverter::class)
    var regDtm: LocalDateTime? = null
)

Repository

Kotlin

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

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

import com.example.tracker.btn.domain.ButtonEvent
import io.reactivex.Flowable
import org.springframework.stereotype.Repository
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient
import software.amazon.awssdk.enhanced.dynamodb.Expression
import software.amazon.awssdk.enhanced.dynamodb.Key
import software.amazon.awssdk.enhanced.dynamodb.TableSchema
import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
import java.time.LocalDateTime
import java.util.concurrent.CompletableFuture


@Repository
class ButtonEventRepository(
    private val client: DynamoDbEnhancedAsyncClient
) {

    companion object {
        private const val TABLE_NAME = "button_event"
        private val tableSchema = TableSchema.fromBean(ButtonEvent::class.java)
    }

    private val table = client.table(TABLE_NAME, tableSchema)

    fun save(buttonEvent: ButtonEvent) {
        table.putItem(buttonEvent).get()
    }

    fun findByKey(partitionVal: Long, sortVal: String): ButtonEvent? {
        val key = Key.builder().partitionValue(partitionVal).sortValue(sortVal).build()
        val result: CompletableFuture<ButtonEvent>? = table.getItem { it.key(key) }
        return result?.get()
    }

    fun findByKeyAndRegDtm(
        partitionVal: Long,
        sortVal: String,
        startDtm: LocalDateTime,
        endDtm: LocalDateTime
    ): List<ButtonEvent> {
        val values = mutableMapOf<String, AttributeValue>()
        values[":start_dtm"] = AttributeValue.builder().s(startDtm.toString()).build()
        values[":end_dtm"] = AttributeValue.builder().s(endDtm.toString()).build()

        val expression = Expression.builder()
            .expression("reg_dtm >= :start_dtm AND reg_dtm <= :end_dtm")
            .expressionValues(values)
            .build()

        val queryConditional = QueryConditional
            .keyEqualTo(Key.builder().partitionValue(partitionVal).sortValue(sortVal).build())

        val result = table.query { r ->
            r.queryConditional(queryConditional)
                .filterExpression(expression)
        }.items()

        return Flowable.fromPublisher(result).toList().blockingGet()
    }

}

Service

Kotlin

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

@Service
class ButtonEventService(
    private val buttonEventRepository: ButtonEventRepository
) {

    fun save(buttonEvent: ButtonEvent) {
        buttonEventRepository.save(buttonEvent)
    }

    fun findByKey(partitionVal: Long, sortVal: String): ButtonEvent? {
        return buttonEventRepository.findByKey(partitionVal, sortVal)
    }

    fun findByKeyAndRegDtm(
        partitionVal: Long,
        sortVal: String,
        startDtm: LocalDateTime,
        endDtm: LocalDateTime
    ): List<ButtonEvent> {
        return buttonEventRepository.findByKeyAndRegDtm(partitionVal, sortVal, startDtm, endDtm)
    }

}

Reference

AWS 공식 예제

AWS Example Kotlin

AWS Example JAVA

AWS Doc SDK Example

https://howtodoinjava.com/spring-boot/access-dynamodb-with-spring

https://betterprogramming.pub/aws-java-sdk-v2-dynamodb-enhanced-client-with-kotlin-spring-boot-application-f880c74193a2

https://kouzie.github.io/aws/aws-dynamodb/#%EA%B8%B0%EB%B3%B8-%ED%82%A4

https://jsonobject.tistory.com/599

https://wedul.site/708

https://dev.to/aws-builders/using-dynamodb-on-spring-boot-featkotlin-3674

https://refactorfirst.com/using-dynamodb-with-spring-boot

https://techfox.tistory.com/45

https://velog.io/@godol811/Spring Boot에서-DynamoDB-aws-java-sdk-v2-Test-code를-이용해서-적용하기

전체 예제 Git 링크

민갤

Back-End Developer

백엔드 개발자입니다.

[Spring Boot] Event Tracking

spring boot Event Tracking

Spring Boot

AWS CLI - DynamoDB

aws cli dynamodb

Server

v5.0