Spring boot学習 (TypeScript  + DynamoDB)

はじめに
---
spring boot 初学者向け
spring bootでdynamoDBとのやりとりを学習するために作成しましたので参考に投稿させていただきます。
データベースはDynamoDB localを利用
（RDSの場合は[こちら](https://qiita.com/kazucb/items/8e2fc14e551b21b19724)を参考。フロントエンドのコードは全く同じ）


簡単なフロントとバックとデータベースを作成しましたので記事として残します

結果イメージ
フロントで「user」「email」を入力して、「DBに保存」ボタンを押すと、DBに保存される
フロントで検索したいuserを文字列として入力して、「検索」ボタンを押すと、DBに保存されているuserと対となるemailが表示される

バックエンドの作成手順は
https://qiita.com/kazucb/items/8e2fc14e551b21b19724
を参考

以下のフォルダ構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574492/52925d6b-6c0d-50fd-fa78-0cd7641d71c0.png)

DynamoDbConfig.kt
```
package com.example.backend

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient
import software.amazon.awssdk.services.dynamodb.DynamoDbClient
import java.net.URI

@Configuration
class DynamoDbConfig {

    @Bean
    fun dynamoDbClient(): DynamoDbClient {
        return DynamoDbClient.builder()
            .endpointOverride(URI.create("http://localhost:8000")) // 適切なエンドポイントに設定
            .build()
    }

    @Bean
    fun dynamoDbEnhancedClient(dynamoDbClient: DynamoDbClient): DynamoDbEnhancedClient {
        return DynamoDbEnhancedClient.builder()
            .dynamoDbClient(dynamoDbClient)
            .build()
    }
}
```

Userクラス
```
package com.example.backend

import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey

@DynamoDbBean
data class User(
    @get:DynamoDbPartitionKey
    var username: String? = null,
    var email: String? = null
)
```

UseContoroller.kt
```
package com.example.backend

import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RequestMapping("/users")

@RestController
@CrossOrigin(origins = ["http://localhost:5173"])//CORS対応
class UserController(private val userService: UserService) {

    @GetMapping("/{username}")
    fun getUserByUsername(@PathVariable username: String): User? {
        return userService.getUserByUsername(username)
    }

    @PostMapping
    fun createUser(@RequestBody user: User): ResponseEntity<Map<String,String>> {
        println("controller user:$user")
        userService.saveUser(user)
        return ResponseEntity.ok(mapOf("message" to "User saved successfully!"))
    }
}
```

UserService.kt
```
package com.example.backend

import org.springframework.stereotype.Service

@Service
class UserService(private val userRepository: UserRepository) {
    fun getUserByUsername(username: String): User? {
        val getUser = userRepository.findByUsername(username)
        println("Service getUser:$getUser")
        return getUser
    }

    fun saveUser(user: User) {
        println("service user:$user")
        userRepository.save(user)
    }
}
```

UserRepository.kt
```
package com.example.backend

import org.springframework.stereotype.Repository
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable
import software.amazon.awssdk.enhanced.dynamodb.Key
import software.amazon.awssdk.enhanced.dynamodb.TableSchema

@Repository
class UserRepository(private val dynamoDbEnhancedClient: DynamoDbEnhancedClient) {
    private val tableSchema: TableSchema<User> = TableSchema.fromBean(User::class.java)
    private val table: DynamoDbTable<User> = dynamoDbEnhancedClient.table("SampleTable", tableSchema)

    fun findByUsername(username: String): User? {
        println("Repo repository username: $username")
        val key = Key.builder().partitionValue(username).build()
        println("Repo key: $key")

        val answer = table.getItem { it.key(key) }
        println("Repo answer: $answer")
        return answer
    }


    fun save(user: User){
        println("repository user:$user")
        table.putItem(user)
    }
}
```

application.properties
```
spring.application.name=backend
aws.dynamodb.endpoint=http://localhost:8000
server.port=8080
```
build.gradle.kts
```
plugins {
	kotlin("jvm") version "1.9.25"
	kotlin("plugin.spring") version "1.9.25"
	id("org.springframework.boot") version "3.4.1"
	id("io.spring.dependency-management") version "1.1.7"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

repositories {
	mavenCentral()
}

dependencies {
	dependencies {
		implementation("org.springframework.boot:spring-boot-starter-web") // Spring Web Starter
		implementation("com.fasterxml.jackson.module:jackson-module-kotlin") // Kotlin用Jacksonモジュール
		implementation("org.jetbrains.kotlin:kotlin-reflect") // Kotlinリフレクション
		implementation("org.jetbrains.kotlin:kotlin-stdlib") // Kotlin標準ライブラリ
		implementation(platform("software.amazon.awssdk:bom:2.20.81"))

		implementation("software.amazon.awssdk:dynamodb") // DynamoDB SDK
		implementation("software.amazon.awssdk:dynamodb-enhanced") // DynamoDB Enhanced Client

		testImplementation("org.springframework.boot:spring-boot-starter-test") // Spring Boot Test
		testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
		testImplementation("org.mockito:mockito-core:5.14.2")
		testImplementation("org.mockito:mockito-inline:5.2.0")
		testImplementation("org.mockito.kotlin:mockito-kotlin:3.2.0")

		testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
		testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.3")
		testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.3")

	}

}

kotlin {
	compilerOptions {
		freeCompilerArgs.addAll("-Xjsr305=strict")
	}
}

//tasks.withType(JavaCompile) {
//	options.encoding = 'UTF-8'
//}

tasks.withType<Test> {
	useJUnitPlatform()
}

```

---
dynamoDB localの準備
---
dockerをローカルで起動
```
docker run -d -p 8000:8000 amazon/dynamodb-local
```

（DynamoDB ローカルがホストマシンのポート 8000 にマッピングされ、ホストマシンから http://localhost:8000 で DynamoDB ローカルにアクセスできます。）

テーブル作成
```
aws dynamodb create-table \
--table-name SampleTable \
--attribute-definitions AttributeName=username,AttributeType=S \
--key-schema AttributeName=username,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
--endpoint-url http://localhost:8000
```

---
動作確認
---
①
https://qiita.com/kazucb/items/8e2fc14e551b21b19724
で準備したフロント起動
②バックエンドを起動
③![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2574492/9f0bf178-f7d5-15c8-5464-fdec12b5ec9d.png)
名前とメールを入力して「DBに保存」を押下
dynamodbに保存される
次に、検索する名前に入力すると一致するメールが下に表示される
以上。


---
参考
---
dynamoDB localをコマンドで操作する方法

dockerをローカルで起動
```
docker run -d -p 8000:8000 amazon/dynamodb-local
```

テーブル確認
```
aws dynamodb list-tables --endpoint-url http://localhost:8000
```

テーブル作成 (テーブル名はSampleTable)
```
aws dynamodb create-table \
--table-name SampleTable \
--attribute-definitions AttributeName=username,AttributeType=S \
--key-schema AttributeName=username,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
--endpoint-url http://localhost:8000
```

データ挿入
```
aws dynamodb put-item --table-name SampleTable --item '{
"username": {"S": "testuser1"},
"email": {"S": "testmail1"}
}' --endpoint-url http://localhost:8000
```


テータを検索
```
aws dynamodb get-item --table-name SampleTable --key '{
"username": {"S": "testuser1"}
}' --endpoint-url http://localhost:8000
```

テーブル全体のスキャン
```
aws dynamodb scan --table-name SampleTable --endpoint-url http://localhost:8000
```

テーブルの詳細情報（メタデータ）を取得
```
aws dynamodb describe-table --table-name SampleTable --endpoint-url http://localhost:8000
```

テーブル削除
```
aws dynamodb delete-table --table-name SampleTable --endpoint-url http://localhost:8000
```

以上、もし何か指摘があればコメントいただければ幸いです