クラウドの普及に伴い、ビッグデータやキーバリュー型データの格納など、ますます活用の機会が広がりつつあるNoSQLデータベース。 第3回は代表的なNoSQLプロダクトであるAmazon DynamoDBやApache Cassandra、Amazon ElastiCacheへアクセスするSpringアプリケーションを開発する方法について、わかりやすく解説します。
本連載では、以下の様なステップで進めています。
前回、Spring Data DynamoDBを使ったアプリケーション実装(1) に引き続き、 Spring Data DynamoDBを使ってデータベースアクセスするアプリケーションを実装していきます。
アプリケーションコンポーネントの実装に移ります。データ追加・更新処理で、画面から受け取るリクエストパラメータのクラスを以下の様に作成しています。
リクエストパラメータクラス
package org.debugroom.mynavi.sample.spring.data.dynamodb.app.model;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class SampleModel implements Serializable {
private String samplePartitionKey;
private String sampleSortKey;
private String sampleText;
}
Controllerでは、以下5種類のリクエストを受け取り、ロジックを実行して結果をテンプレートに渡す処理を実装します。
package org.debugroom.mynavi.sample.spring.data.dynamodb.app.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class SampleController {
@Autowired
SampleService sampleService;
@RequestMapping(method = RequestMethod.GET, value="findAll")
public String findAll(Model model){
model.addAttribute("mynaviSampleTables", sampleService.getMynaviSampleTables());
return "findAll";
}
@RequestMapping(method = RequestMethod.GET, value="findOne")
public String findOne(SampleModel sampleModel, Model model){
model.addAttribute("mynaviSampleTable", sampleService.getMynaviSampleTable(
SampleModelMapper.mapToMynaviSampleTableKey(sampleModel)));
return "findOne";
}
@RequestMapping(method = RequestMethod.POST, value="add")
public String add(SampleModel sampleModel, Model model){
model.addAttribute("mynaviSampleTable",
sampleService.addMynaviSampleTable(SampleModelMapper.map(sampleModel)));
return "add";
}
@RequestMapping(method = RequestMethod.POST, value = "update")
public String update(SampleModel sampleModel, Model model){
model.addAttribute("mynaviSampleTable",
sampleService.updateMynaviSampleTable(SampleModelMapper.map(sampleModel)));
return "update";
}
@RequestMapping(method = RequestMethod.POST, value = "delete")
public String delete(SampleModel sampleModel, Model model){
model.addAttribute("mynaviSampleTable",
sampleService.deleteMynaviSampleTable(
SampleModelMapper.mapToMynaviSampleTableKey(sampleModel)));
return "delete";
}
}
また、Serviceを呼び出す際にリクエストパラメータオブジェクトを、ServiceのインプットオブジェクトであるDynamoDBのテーブルクラスやプライマリキークラスへ変換するマッパークラスをインターフェースで実装します。
package org.debugroom.mynavi.sample.spring.data.dynamodb.app.model;
import org.debugroom.mynavi.sample.spring.data.dynamodb.domain.model.MynaviSampleTable;
public interface SampleModelMapper {
public static MynaviSampleTable map(SampleModel sampleModel){
return MynaviSampleTable.builder()
.samplePartitionKey(sampleModel.getSamplePartitionKey())
.sampleSortKey(sampleModel.getSampleSortKey())
.sampleText(sampleModel.getSampleText())
.build();
}
public static MynaviSampleTableKey mapToMynaviSampleTableKey(SampleModel sampleModel){
return MynaviSampleTableKey.builder()
.samplePartitionKey(sampleModel.getSamplePartitionKey())
.sampleSortKey(sampleModel.getSampleSortKey())
.build();
}
}
注釈
Java8からstaticメソッドであれば、インターフェースでも実装できる様になっています。
Serviceクラスでは、以下の通り、CRUD処理をRepositoryを通して実行する処理を実装します。
package org.debugroom.mynavi.sample.spring.data.dynamodb.domain.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SampleServiceImpl implements SampleService{
@Autowired
SampleRepository sampleRepository;
@Override
public MynaviSampleTable getMynaviSampleTable(MynaviSampleTableKey mynaviSampleTableKey) {
return sampleRepository.findById(mynaviSampleTableKey).get();
}
@Override
public List<MynaviSampleTable> getMynaviSampleTables() {
List<MynaviSampleTable> sampleTables = new ArrayList<>();
sampleRepository.findAll().iterator().forEachRemaining(sampleTables::add);
return sampleTables;
}
@Override
public MynaviSampleTable addMynaviSampleTable(MynaviSampleTable mynaviSampleTable) {
mynaviSampleTable.setSamplePartitionKey(UUID.randomUUID().toString());
mynaviSampleTable.setSampleSortKey("1");
return sampleRepository.save(mynaviSampleTable);
}
@Override
public MynaviSampleTable updateMynaviSampleTable(MynaviSampleTable mynaviSampleTable) {
return sampleRepository.save(mynaviSampleTable);
}
@Override
public MynaviSampleTable deleteMynaviSampleTable(MynaviSampleTableKey mynaviSampleTableKey) {
MynaviSampleTable mynaviSampleTable = sampleRepository.findById(mynaviSampleTableKey).get();
sampleRepository.deleteById(mynaviSampleTableKey);
return mynaviSampleTable;
}
}
ここで、DynamoDBへアクセスするコンポーネントであるSampleRepositoryインターフェースは、以下の様な要領で実装しておく必要があります。
package org.debugroom.mynavi.sample.spring.data.dynamodb.domain.repository;
import org.debugroom.mynavi.sample.spring.data.dynamodb.domain.model.MynaviSampleTableKey;
import org.socialsignin.spring.data.dynamodb.repository.EnableScan;
import org.springframework.data.repository.CrudRepository;
import org.debugroom.mynavi.sample.spring.data.dynamodb.domain.model.MynaviSampleTable;
@EnableScan
public interface SampleRepository extends CrudRepository<MynaviSampleTable, MynaviSampleTableKey> {
}
注釈
特に実装クラスも作成せずインターフェースだけで、findAllやsaveメソッドが実行できる理由は、Spring Data DynamoDBが、GenericDAOパターンに基づく実装クラスを提供しているためです。 GenericDAOパターンとはJavaの型パラメータを利用した実装で、CRUD処理をJavaのジェネリクス機構を使って実装しておき、テーブルの種類に応じた、 型パラメータクラスを設定することで、汎用的なCRUD共通処理を実装したDAO(DatabaseAccessObject)を作成しておくパターンです。 Spring Data Dynamoに限らず、Spring Data JPAなど類似一連のプロダクトでは、同様にインターフェースを作成するだけで、 基本的なCRUDはほぼ実装せずにデータベースアクセス処理を行うことが可能です。
型パラメータとして指定する、DynamoDBのテーブルクラスは以下の要領に則って作成します。
また、プライマリキーがパーティションキーとソートキーで構成される場合、プライマリキークラスを作成し、テーブルクラスに定義する必要があります。 プライマリキークラスには、org.springframework.data.annotation.Idアノテーションを付与し、Getter・Setterメソッドを付与してはいけません。 パーティションキーのみで構成される場合は、テーブルクラスに@DynamoDBHashKeyのみを設定し、Repositoryの型パラメータにはキーのプリミティブな型を設定してください。
package org.debugroom.mynavi.sample.spring.data.dynamodb.domain.model;
import java.io.Serializable;
import lombok.*;
import org.springframework.data.annotation.Id;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
@DynamoDBTable(tableName = "mynavi-sample-table")
public class MynaviSampleTable implements Serializable {
@Id
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private MynaviSampleTableKey mynaviSampleTableKey;
@DynamoDBHashKey
private String samplePartitionKey;
@DynamoDBRangeKey
private String sampleSortKey;
@DynamoDBAttribute
private String sampleText;
}
package org.debugroom.mynavi.sample.spring.data.dynamodb.domain.model;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
import java.io.Serializable;
import lombok.*;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class MynaviSampleTableKey implements Serializable {
@DynamoDBHashKey
private String samplePartitionKey;
@DynamoDBRangeKey
private String sampleSortKey;
}
注釈
上記のテーブルクラスでは一律@DataアノテーションによりSetter・Getterメソッドを付与していますが、プライマリキークラスにはAccessLevel.NONEを設定して除外しています。 Spring Data DynamoDBの公式Wiki にもありますが、 プライマリキークラスにGetter・Setterメソッドをつけていると、「DynamoDBMappingException: not supported; requires @DynamoDBTyped or @DynamoDBTypeConverted」が発生します。
これでアプリケーションが作成しました。SpringBoot起動クラスを実行し、アプリケーションを実行しましょう。「http://localhost:8080/index.html」へブラウザからアクセスすると以下の様な画面が表示され、以下の5つのサービスが実行できます。
このように、DynamoDBへCRUD処理するアプリケーションをSpring Data Cassandraを用いて簡単に実装することができます。 実際のアプリケーションでは様々ユースケースに応じて、データモデルを検討しておく必要がありますが、応用編等で追々その辺りを触れたいと思います。 次回以降はApache Cassandraを構築して、同様にSpring Data Cassandraを使ってCRUD処理するアプリケーションを実装していきます。
川畑 光平(KAWABATA Kohei)
某システムインテグレータにて、金融機関システム業務アプリケーション開発・システム基盤担当を経て、現在はソフトウェア開発自動化関連の研究開発・推進に従事。
Red Hat Certified Engineer、Pivotal Certified Spring Professional、AWS Certified Solutions Architect Professional等の資格を持ち、アプリケーション基盤・クラウドなど様々な開発プロジェクト支援にも携わる。
2019 APN AWS Top Engineers & Ambassadors 選出。
本連載記事の内容に対するご意見・ご質問は Facebook まで。