Spring Boot + Spring Data: JSONデータベースCouchbaseを使ってJavaオブジェクトをそのまま保存する

# はじめに

### 概要
本記事では、[Spring Boot](https://spring.io/projects/spring-boot)と[Spring Data Couchbase](https://spring.io/projects/spring-data-couchbase)を使ったアプリケーション開発について紹介します。

イントロダクションとして、できるだけ前提知識を置かず、また徒らに複雑にならない様、動作を確認できるアプリケーションの開発までの流れを整理します。

### 想定対象読者

対象読者として、例えば以下の様な人を想定しています。

- 開発言語としてJavaを使っており、SpringフレームワークやSpring Bootに関心がある（または使っている）
- リレーショナルデータベース以外のデータベースを開発に使えないかと考えている
- そもそもデータベースに対して選り好みはないが（使ったことがないが）、JAVAのオブジェクトをそのまま保存・再利用できれば良いと思っている
- オブジェクトとリレーショナルデータとのマッピングに嫌気がさしている
- データベースにミリ秒以下の応答性能を求めている

# アプリケーション構築手順

## 初期準備

### データベースの準備

以下の記事を参考にしてください（Dockerだけでなく、Mac、Windows、Ubuntuへのインストーラーを使ったインストールについても触れています）。

[Dockerでお手軽JSONデータベース体験 ~ Couchbase Server最新版を試す](https://qiita.com/yoshiyuki_kono/items/19359432f46afc2c105b)

また、（上記記事を参考に）今回作成するアプリケーション用のバケット(Couchbaseにおけるデータベース)を作成しておきます。
ここでは`spring_user_mgmt`という名前を用います（簡単なユーザ管理のアプリケーションを作ります）。

### 開発用インデックス定義

CouchbaseのWEBコンソールでバケットを作成したら、同じWEBコンソールのQueryワークスペースで、開発用のインデックスを作成しておきます。

```sql
CREATE PRIMARY INDEX `idx_spring_user_mgmt` ON `spring_user_mgmt`
```

**注意：** 本番リリースの際には、インデックスの適切な設計が重要です。ここで利用するインデックスは開発用の簡易な定義であることにご留意ください。

### Springプロジェクト生成

[Spring Initializr](https://start.spring.io/)を使って、プロジェクトの雛形を作ります。

右の「Dependencies」に、Spring Data Couchbaseを追加します（他は全てデフォルトのままにしています）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/d175608c-ed5f-eac1-d373-ad45c6172505.png)

「Generate」押下によりダウンロードされたファイルを適当な場所に展開します。

### 依存関係の追加

pom.xmlの`<dependencies>`タグの間に以下の要素を追加します。

```xml

   <dependency>
    　<groupId>org.springframework.data</groupId>
    　<artifactId>spring-data-couchbase</artifactId>
    　<version>4.0.0.RELEASE</version>
   </dependency>
   <dependency>
　　　　<groupId>org.springframework.boot</groupId>
　　　　<artifactId>spring-boot-starter-web</artifactId>
　　</dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter</artifactId>
   </dependency>
```

## データベースとの連携

### データベース接続設定

抽象クラス`AbstractCouchbaseConfiguration`を継承したクラス中で下記の様にCouchbaseへの接続設定を行います。

```java
package com.example.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;

@Configuration
public class Config extends AbstractCouchbaseConfiguration {

    @Override
    public String getConnectionString() {
        return "couchbase://127.0.0.1";
    }

    @Override
    public String getUserName() {
        return "Administrator";
    }

    @Override
    public String getPassword() {
        return "password";
    }

    @Override
    public String getBucketName() {
        return "spring_user_mgmt";
    }
}
```

### データベースに保存するクラスの定義

データベースに保存するクラスには`@Document`アノテーションを付けます(`id`と`@Id`にも注目）。

```java
package com.example.demo;

import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;

@Document
public class User {

    @Id
    private String id;
    private String firstName;
    private String lastName;
    private String email;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

}
```

### Repositoryインターフェイスの定義

Springフレームワークの作法に従い、下記の様に`Repository`インターフェイスを定義します。実装は自動的に生成されます。

```java
package com.example.demo;

import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface UserRepository extends CouchbaseRepository<User, String> {

    Optional<User> findById(String id);
    
    List<User> findByFirstName(String firstName);
    
    List<User> findByEmailLike(String email);
}
```
Springフレームワークで、Couchbaseデータベースを使うために最低限必要な内容は、基本的に以上です。

## 動作確認

### CommandLineRunner実装クラスの作成

上記作成した内容の動作確認のために`CommandLineRunner`実装クラスを作成します。

`init()`で、`User`オブジェクトを作成しデータベースへ挿入（Upsert）し、`dryrun()`で、データベースから`User`オブジェクトを取得・検索した結果を出力しています。

```java
package com.example.demo;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class DemoCommandLineRunner implements CommandLineRunner {

	@Override
	public void run(String... args) throws Exception {
		init();
		dryrun();
	}

	@Autowired
    private UserRepository userRepository;
	
	private void init() {
        User u1 = createUser("user::0001", "太郎", "佐藤", "taro.sato@demo.com");
        userRepository.save(u1);

        User u2 = createUser("user::0002", "次郎", "田中", "ziro.tanaka@demo.com");
        userRepository.save(u2);

        User u3 = createUser("user::0003", "花子", "鈴木", "hanako.suzuki@demo.com");
        userRepository.save(u3);
	}
	
    private void dryrun() {
        Optional<User> userById = userRepository.findById("user::0001");
        System.out.println("User get = "+userById.get().getFirstName());

        List<User> usersByEmail = userRepository.findByEmailLike("%@demo.com");
        System.out.println( "Total # of users by email = "+usersByEmail.size()  );
        
        List<User> usersByFirstName = userRepository.findByFirstName("太郎");
        System.out.println( "Total # of users by first name = "+usersByFirstName.size()  );
	}
	
    public static User createUser(String id, String firstName, String lastName, String email) {
		User user = new User();
		user.setId(id);
		user.setFirstName(firstName);
		user.setLastName(lastName);
		user.setEmail(email);
		return user;
	}
}

```

### コマンドライン実行

プロジェクトディレクトリで下記の様に実行します。

```
$ mvn spring-boot:run
```

起動時の出力の後、下記の様に結果が出力されるはずです。

```
User get = 太郎
Total # of users by email = 3
Total # of users by first name = 1
```

### Couchbase上のデータ確認
WEBコンソールの「Documents」メニューからバケットの中身を確認した結果は以下の様な画面になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/1bd9e901-d8a5-360e-de01-830341c8d0b5.png)

ここで注目したいのは、全てのデータ（ドキュメント）が`"_class":"com.example.demo.User"`という要素を持っていることです。この`_class`要素が暗黙に追加されることにより、`User`以外のクラスを定義・保存した場合も、クラス毎のマッピングに煩わされることなく、同じバケットに異なるクラスのデータを保存することができます。

# REST APIの公開

CouchbaseとSpringとの連携という意味では本質的ではありませんが、以上、作成・確認したデータ取得・検索操作をREST APIとして公開し、利用するまでを紹介します（詳細な解説は、本旨ではないためコードと実行結果のみの紹介に留めます）。

## ソースコード

### Controlerクラス定義

```java
package com.example.demo;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
	
	  @Autowired
	  UserService userService;


	  @GetMapping("/get/{id:.+}")
	  public ResponseEntity<Optional<User>> findById(@PathVariable String id) {
	        return ResponseEntity.ok(userService.findById(id));
	  }

	  @GetMapping("/name/{name:.+}")
	  public ResponseEntity<List<User>> findByFirstName(@PathVariable String name) {
	        return ResponseEntity.ok(userService.findByFirstName(name));
	  }
	  
	  @GetMapping("/email/{email:.+}")
	  public ResponseEntity<List<User>> findByEmail(@PathVariable String email) {
	        return ResponseEntity.ok(userService.findByEmailLike("%"+email+"%"));
	  }
	  
}
```

### Serviceインターフェイス定義

```java
package com.example.demo;

import java.util.List;
import java.util.Optional;

public interface UserService {

    Optional<User> findById(String id);
    
    List<User> findByFirstName(String fistName);
    
    List<User> findByEmailLike(String email);
}

```
### Service実装定義

```java
package com.example.demo;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
	
    private UserRepository repo;

    @Autowired
    public void setUserRepository(UserRepository repo) {
        this.repo = repo;
    }

	@Override
	public List<User> findByFirstName(String fistName) {
		System.out.println(fistName);
		return repo.findByFirstName(fistName);
	}

	@Override
	public Optional<User> findById(String id) {
		return repo.findById(id);
	}


	@Override
	public List<User> findByEmailLike(String email) {
		return repo.findByEmailLike(email);
	}

}
```

## 実行結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/fe1c9471-298b-eb6e-9ee8-6a246eaf2554.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/e768873e-b5d7-868d-5764-e612e3c37039.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/b3501f4b-acaf-50c3-9dc6-04af6fc9c882.png)

# 最後に

Javaで開発する場合、またSpringフレームワークを用いる場合、必ずしもSpring Data Couchbaseを使わなければならないわけではなく、CouchbaseのJava SDKを用いることも可能です。

また、Spring Data Couchbaseを使ってできることは、今回紹介した範囲に尽きるものではありません。

JSONデータベースCouchbaseを使ったJAVAアプリケーション開発のための導入として、参考にしていただける様でしたら幸いです。