2018年5月21日 星期一

透過 Spring Data JPA 存取 DynamoDB

因為 Spring Data JPA 用來存取資料庫時很方便,然後 DynamoDB 又是屬於特別囉唆的東西,所以之前在研究 Spring Boot 時,就在想不知道 Spring 是否有支援 DynamoDB 的實作。很幸運地,社群真的有人做了 Spring Data JPA + DynamoDB [1-2] 造福大家!XD

Maven

使用 Spring Data JPA + DynamoDB 的狀況,自然需要的一定是 Spring 的環境以及橋接 DynamoDB 的實作。這裡使用的 Spring 環境是透過 Spring Boot 建立的,Maven 設定如下:

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>com.amazonaws</groupId>
			<artifactId>aws-java-sdk-bom</artifactId>
			<version>1.11.301</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.0.2.RELEASE</version>
</parent>

<dependencies>
	<!-- Spring Boot -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context-indexer</artifactId>
	</dependency>
	<!-- Amazon SDKs -->
	<dependency>
		<groupId>com.amazonaws</groupId>
		<artifactId>aws-java-sdk-dynamodb</artifactId>
	</dependency>
	<dependency>
		<groupId>com.github.derjust</groupId>
		<artifactId>spring-data-dynamodb</artifactId>
		<version>5.0.2</version>
	</dependency>
</dependencies>

上頭的第一部份(1~11 行)是指定 AWS SDK 的版本,這個版本其實是以 [1] 所使用的版本(1.11.301)為準;第二部份(13~17 行)是指定 Spring Boot 的版本,也就是後面的 dependency 中用到 Spring Boot 的時候,都會從這裡決定版號;第三部份就是真的需要的函式庫的引用了,其中 35~39 行宣告的 spring-data-dynamodb 就是本篇的重點 [1],也就是真正提供 DynamoDB 和 Spring Data JPA 串接的函式庫。

如果說想要使用的 Spring 或者 Spring Boot 版本不是 Spring Boot 2.0 以上,那麼可以參考 [1] 的版本資訊去尋找合適的對應版本。

建立 Entity

假設我在 DynamoDB 的 table 中存放的每個 record 要叫做 MyRecord,並且他是個具有複合主鍵(也就是有 Partition Key 和 Sort Key)的特性,那麼就需要按照 Spring Data JPA 本來的結構,做出一個代表主鍵的物件(這裡命名為 RecordId)。不過除此之外,還有一點特殊需要注意的地方。

首先先假設 record 的結構包含了 my_pk、my_sk、display_name 和 flag 這四個欄位,其中 my_pk、my_sk 組成複合主鍵,則 Entity 可以寫成以下的樣子:

MyRecord.java

@DynamoDBTable(tableName = "my_table")
public class MyRecord implements Serializable {
  @Id
  private RecordId id;
  
  private String displayName;
  private Boolean flag;
  
  @DynamoDBHashKey(attributeName = "my_pk")
  public String getPartition () {
	return this.id.getPartition();
  }
  
  @DynamoDBRangeKey(attributeName = "my_sk")
  public Long getEpochTime () {
	return this.id.getEpochTime();
  }
  
  @DynamoDBAttribute(attributeName = "display_name")
  public String getDisplayName () {
	return this.displayName;
  }
  
  @DynamoDBAttribute(attributeName = "flag")
  public Boolean getFlag () {
	return this.flag;
  }
  
  public void setPartition (String val) {
	if (this.id == null) {
	  this.id = new RecordId();
	}
	this.id.setPartition(val);
  }
  
  public void setEpochTime (String val) {
	if (this.id == null) {
	  this.id = new RecordId();
	}
	this.id.setEpochTime(val);
  }
  
  public void setDisplayName (String val) {
	this.displayName = val;
  }
  
  public void setFlag (Boolean val) {
	this.flag = val;
  }
}

MyRecord.java

public class RecordId implements Serializable {
  private String partition;
  private Long epochTime;
  
  @DynamoDBHashKey(attributeName = "my_pk")
  public String getPartition () {
	return this.getPartition();
  }
  
  @DynamoDBRangeKey(attributeName = "my_sk")
  public Long getEpochTime () {
	return this.getEpochTime();
  }
  
  public void setPartition (String val) {
	this.partition = val;
  }
  
  public void setEpochTime (Long val) {
	this.epochTime = val;
  }
}

這裡可以看到比較奇妙的地方,在 MyRecord 以及 RecordId 兩處都同時寫了 @DynamoDBHashKey 和 @DynamoDBRangeKey。雖然不太確定為什麼要這樣寫,不過實務上有一邊沒寫都會導致在存取 Repository 時丟出 Exception。

這裡順利做完以後,其實最困難(?!)的地方已經完成了 XD

Entity 注意事項
  1. Entity 上的所有 get method 都應該要有 @DynamoDBHashKey、@DynamoDBRangeKey 或者 @DynamoDBAttribute。
  2. Entity 的 get method 命名一定要以 get 開頭,不能是 is…. 或者 has…. 之類的其他動詞。
  3. Boolean 預設會以數字形式儲存,想要強制使用 Boolean 時,需要透過 @DynamoDBTyped 指定。
建立 Repository

接下來就是標準的 Spring Data JPA 程序了。假設要使用的是 CrudRepository,那麼 Repository 的介面就這麼寫:

public interface MyRepository extends CrudRepository<MyRecord, RecordId> {
}
建立 DynamoDB 設定

DynamoDB 設定主要是要提供 Spring 能夠用來存取 DynamoDB 的 Bean,因為在 DynamoDB 這個案例,建立資料庫連線的方式不同於一般 RDBMS 那樣,它不是透過 JDBC,所以需要比較不同的方式注入供 spring-data-dynamodb 存取。

@Configuration
@EnableDynamoDBRepositories(basePackages = "com.example")
public class DynamodbConfiguration {
  @Bean
  public AmazonDynamoDB amazonDynamoDB() {
    return AmazonDynamoDBClientBuilder.defaultClient();
  }
}

這段主要目的就是提供初始化 AmazonDynamoDB 的方法,因為我的狀況通常都會讓程式自己去環境上取得 credential(例如由 VM 的 Role 取得權限),所以這裡單純就是產生一個預設的 client 即可。如果要產生的 client 有特殊的產生過程,也可以在這裡做修改。

設定 Spring Boot 應用程式

@ComponentScan
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application {
  public static void main (String[] args) {
    SpringApplication.run(Application.class, ARGS);
  }
}

這段稍微特別一點的地方在於,@SpringBootApplication 的宣告上,同時宣告了要關閉 DataSource 的自動設定。關閉的原因是因為正常來說,Spring Data JPA 會去尋找 application.xml 設定檔,以得知如何透過 JDBC 模式取得資料庫的連線。但是 DynamoDB 有自己獨特的連線方法,因此要讓 Spring Data JPA 略過這個動作。不過如 [1] 的說明那樣,直接提供一個沒有內容的設定檔其實也是可以的。

做到這裡,其實就已經差不多了,最後剩下的就是讓 Spring 注入上面建立的 MyRepository,然後就可以開始快樂(?)地操作 DynamoDB 了。

參考資料
  1. derjust/spring-data-dynamodb
  2. derjust/spring-data-dynamodb-examples
  3. DateTimeFormat not supporting
  4. Spring Data JPA with a Hash & Range Key DynamoDB Table

沒有留言: