SpringBootでMongoDBを使用する

自社でMongoDB,SpringBootを扱う機会があり、勉強のために簡単なデモコードを作成しました。
自社のメインサーバーはフレームワークを用いておらず、社内でフレームワークを用いたコードとの比較を行うための資料として作成しています。
SpringBootでMongoDBを扱う雰囲気が伝わればと思います。

まず、アプリケーションの起動時に呼ばれるApplicationクラスです。

```kotlin
@SpringBootApplication
@EnableMongoRepositories
class MyApplication @Autowired constructor(val groceryItemsDao: GroceryItemsDao, val usersDao: UsersDao): CommandLineRunner {

	companion object {
		@JvmStatic
		fun main(args: Array<String>) {
			runApplication<MyApplication>(*args)
		}
	}
	override fun run(vararg args: String?) {
		groceryItemsDao.insertItem(GroceryItem("Whole Wheat Biscuit", "Whole Wheat Biscuit", 5, "snacks"))
        usersDao.insertUser(User("Suzuki"))
	}
}

```

SpringBootにはDI機能が備わっており、以下のようなinterfaceを宣言し、そのinterfaceを実装したクラスを自動的にinjectすることができます。
- interfaceで使いたい関数を宣言する

```kotlin
interface GroceryItemsDao {

    fun findItemByName(name: String): GroceryItem?

    fun insertItem(groceryItem: GroceryItem)
}
```

```kotlin
interface UsersDao {
    fun insertUser(user: User)
}
```



- 上のinterfaceを実装したクラスがフレームワーク側で自動的にinsertされる
```kotlin
@Component
class GroceryItemsDaoImpl @Autowired constructor(
    @Qualifier("myGroceryListTemplate")
    val template: MongoTemplate,
): GroceryItemsDao {

    private val model = GroceryItem::class.java

    override fun findItemByName(name: String): GroceryItem? {
        val query = Query(Criteria.where(GroceryItem.KEY.NAME).`is`(name))
        return template.find(query, model).first()
    }

    override fun insertItem(groceryItem: GroceryItem) {
        try {
            template.insert(groceryItem)
        } catch (me: MongoException) {
            println("Unable to insert due to an error: $me")
        }
    }
}
```

```kotlin
@Component
class UsersDaoImpl @Autowired constructor(@Qualifier("userTemplate") val template: MongoTemplate): UsersDao {
    private val model = User::class.java
    override fun insertUser(user: User) {
        template.insert(user)
    }
}
```

今回はGroceryItemsDaoとUsersDaoは異なるデータベースに属するコレクションのDaoにしました。
複数のDatabaseへの接続は以下のようなConfigクラスを作成します。

```kotlin
@Configuration
class MongoConfig( @Value("\${spring.data.mongodb.uri}") private val connectionString: String) {

    @Bean
    fun mongoClient(): MongoClient {
        val connectionString = ConnectionString(connectionString)
        val settings = MongoClientSettings.builder().applyConnectionString(connectionString)
                .serverApi(ServerApi.builder().version(ServerApiVersion.V1).build()).build()
        return MongoClients.create(settings)
    }

    @Bean
    fun userTemplate(): MongoTemplate {
        return MongoTemplate(mongoClient(), "user")
    }

    @Bean
    fun myGroceryListTemplate(): MongoTemplate {
        return MongoTemplate(mongoClient(), "grocery")
    }
}
```

```:application.properties
spring.data.mongodb.uri=mongodb+srv://admin:☓☓☓☓☓☓☓☓@cluster0.☓☓☓☓☓☓.mongodb.net/?retryWrites=true&w=majority
spring.autoconfigure.exclude= org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
```

これでuserTemplate()は「user」データベースの操作を行うTemplateを提供し、myGroceryListTemplate()は「grocery」データベースの操作を行うTemplateを提供します。
どのコレクションに対してのDaoなのかはModelクラスに記述します。

```kotlin
@Document("groceryItem")
data class GroceryItem(
    val title: String,
    val name: String,
    val quantity: Int,
    val category: String?,
    @Id
    val id: ObjectId = ObjectId()
) {
    object KEY {
        val ID = GroceryItem::id.name
        val TITLE = GroceryItem::title.name
        val NAME = GroceryItem::name.name
        val QUANTITY = GroceryItem::quantity.name
        val CATEGORY = GroceryItem::category.name
    }
}
```

```kotlin
@Document("user")
data class User(
    val name: String,
    @Id
    val id: ObjectId = ObjectId(),
) {
    object KEY {
        val ID = User::id.name
        val NAME = User::name.name
    }
}
```


RestAPIを作成するときは以下のように記述して作成します
```kotlin
@RestController
@RequestMapping("/user")
class UserController @Autowired constructor(val usersDao: UsersDao) {
    @PostMapping("/insert")
    fun insertUser(@RequestBody user: User) {
        usersDao.insertUser(user)
    }
}
```

アノテーションやDI機能を用いて簡潔にサーバーを作成できることがわかりました。