この記事について
将来的に書く予定の「JavaFX で DynamoDB Viewer作ってみた」記事の1ステップ。
結構大きな話になると思うので、少しずつ技術ポイント毎に記事を書いて、ある一定程度の要件を満たせた段階で前述まとめ記事書く予定。
前回記事:DynamoDBの情報を読み込んでJavaFXで表示してみる
今回は、前回時点での課題になっていた、JavaFXのTableViewのカラム情報の動的設定。
テーブル名を指定してDynamoDBから情報を読み込んで、その情報に従って列を設定する。
各データ型に応じて変更したい部分もあるが、今回はすべて文字列扱いにする。
※前回の記事が基本になってます。メソッドなど再説明していない部分があります。
今回終了時のソースはこちら。Github
今回の進捗
画面レイアウト変更
- 既存列を削除
- テーブル名、パーティションキー名、指定値入力用に TextField3つ追加
- これらのTextFieldに fx:id を指定
java側修正
TableViewのカラムを動的にする。
- TableViewのクラス指定を
ObservableList<String>
にする。 - TableViewのカラムを読み込む度にクリア&再作成
- カラム再作成時には、setCellValueFactoryを使用してデータマッピングを指定する。
package com.silverboxsoft.controller;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import com.silverboxsoft.classes.DynamoDbCondition;
import com.silverboxsoft.classes.DynamoDbConditionJoinType;
import com.silverboxsoft.classes.DynamoDbConditionType;
import com.silverboxsoft.classes.DynamoDbResult;
import com.silverboxsoft.dao.DynamicDao;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
public class DynamoDbToolController implements Initializable {
// テーブルの表示データクラスをObservableList<String>にする
@FXML
TableView<ObservableList<String>> tableResultList;
@FXML
TextField txtFldTableName;
@FXML
TextField txtFldColumnName;
@FXML
TextField txtFldCondValue;
@Override
public void initialize(URL location, ResourceBundle resources) {
}
@FXML
protected void actLoad(ActionEvent ev) {
String tableName = txtFldTableName.getText();
DynamicDao dao = new DynamicDao();
List<DynamoDbCondition> conditionList = new ArrayList<>();
DynamoDbCondition cond = new DynamoDbCondition();
cond.setColumnName(txtFldColumnName.getText());
cond.setConditionType(DynamoDbConditionType.EQUAL);
cond.setValue(txtFldCondValue.getText());
conditionList.add(cond);
// DynamoDBの結果を取得してテーブルにセットする
DynamoDbResult result = dao.getResult(tableName, DynamoDbConditionJoinType.AND, conditionList);
setTable(result);
}
private void setTable(DynamoDbResult result) {
tableResultList.getItems().clear();
tableResultList.getColumns().clear();
for (int colIdx = 0; colIdx < result.getColumnCount(); colIdx++) {
// 後述するDynamoDbResult からデータ名取得して設定
String columnName = result.getDynamoDbColumn(colIdx).getColumnName();
TableColumn<ObservableList<String>, String> dataCol = new TableColumn<>(columnName);
final int finalColIdx = colIdx;
//データマッピングを指定する
dataCol.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().get(finalColIdx)));
tableResultList.getColumns().add(dataCol);
}
tableResultList.getItems().addAll(result.getResultItems());
}
}
DynamoDBの結果を取得するクラス作成
後々の為に、検索条件を指定できるようにしておく。
package com.silverboxsoft.dao;
import java.util.HashMap;
import java.util.List;
import com.silverboxsoft.classes.DynamoDbCondition;
import com.silverboxsoft.classes.DynamoDbConditionJoinType;
import com.silverboxsoft.classes.DynamoDbResult;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.QueryRequest;
import software.amazon.awssdk.services.dynamodb.model.QueryResponse;
public class DynamicDao {
private DynamoDbClient ddb;
public DynamicDao() {
Region region = Region.AP_NORTHEAST_1;
this.ddb = DynamoDbClient.builder().region(region).build();
}
public DynamoDbResult getResult(String tableName, DynamoDbConditionJoinType conditionJoinType,
List<DynamoDbCondition> conditionList) {
HashMap<String, String> attrNameAlias = new HashMap<String, String>();
HashMap<String, AttributeValue> attrValues = new HashMap<String, AttributeValue>();
StringBuilder conditionExpression = new StringBuilder();
for (DynamoDbCondition dbCond : conditionList) {
attrNameAlias.put(dbCond.getAlias(), dbCond.getColumnName());
attrValues.put(":" + dbCond.getColumnName(), AttributeValue.builder().s(dbCond.getValue()).build());
if (conditionExpression.length() > 0) {
conditionExpression.append(conditionJoinType.getJoinStr());
}
conditionExpression.append(dbCond.getConditionExpression());
}
System.out.println(conditionExpression.toString());
QueryRequest queryReq = QueryRequest.builder()
.tableName(tableName)
.keyConditionExpression(conditionExpression.toString())
.expressionAttributeNames(attrNameAlias)
.expressionAttributeValues(attrValues)
.build();
QueryResponse response = ddb.query(queryReq);
return new DynamoDbResult(response);
}
}
DynamoDBの結果を保持するクラス作成
QueryResponse(JavaSDKにおける、DynamoDBの結果クラス)からTableViewに表示する形式に変更する。
AttributeVal が参考になる。
package com.silverboxsoft.classes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.QueryResponse;
public class DynamoDbResult {
private List<DynamoDbColumn> columnList = new ArrayList<>();
private Map<String, Integer> colNameIndex = new HashMap<>();
private List<ObservableList<String>> resItems = new ArrayList<>();
public DynamoDbResult(QueryResponse response) {
for (Map<String, AttributeValue> resItem : response.items()) {
analyzeOneItem(resItem);
ObservableList<String> record = FXCollections.observableArrayList();
for (int colIdx = 0; colIdx < columnList.size(); colIdx++) {
DynamoDbColumn dbCol = getDynamoDbColumn(colIdx);
String columnName = dbCol.getColumnName();
record.add(getAttrString(dbCol, resItem.get(columnName)));
}
this.resItems.add(record);
}
}
public int getColumnCount() {
return columnList.size();
}
public int getRecordCount() {
return resItems.size();
}
public List<ObservableList<String>> getResultItems() {
return resItems;
}
public DynamoDbColumn getDynamoDbColumn(int index) {
return columnList.get(index);
}
// DynamoDBの結果からカラム名とデータ形式抽出して保存
private void analyzeOneItem(Map<String, AttributeValue> resItem) {
for (String colName : resItem.keySet()) {
AttributeValue attrVal = resItem.get(colName);
if (!colNameIndex.containsKey(colName)) {
DynamoDbColumn dbCol = new DynamoDbColumn();
dbCol.setColumnName(colName);
dbCol.setColumnType(getDynamoDbColumnType(attrVal));
colNameIndex.put(colName, columnList.size());
columnList.add(dbCol);
}
}
}
// https://sdk.amazonaws.com/java/api/2.0.0/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html
private DynamoDbColumnType getDynamoDbColumnType(AttributeValue attrVal) {
if (attrVal.hasSs()) {
return DynamoDbColumnType.STRING_SET;
} else if (attrVal.hasNs()) {
return DynamoDbColumnType.NUMBER_SET;
} else if (attrVal.hasBs()) {
return DynamoDbColumnType.BINARY_SET;
} else if (attrVal.hasM()) {
return DynamoDbColumnType.MAP;
} else if (attrVal.hasL()) {
return DynamoDbColumnType.LIST;
}
// StringとNumberを判断するメソッドが無い様なので、AttributeValのString出力から判断
String attrValStr = attrVal.toString();
if (attrValStr.startsWith("AttributeValue(N=")) {
return DynamoDbColumnType.NUMBER;
}
System.out.println(attrValStr);
return DynamoDbColumnType.STRING;
}
// 今はString型とNumberだけ対応
private String getAttrString(DynamoDbColumn dbCol, AttributeValue attrVal) {
if (dbCol.getColumnType() == DynamoDbColumnType.STRING) {
return attrVal.s();
} else if (dbCol.getColumnType() == DynamoDbColumnType.NUMBER) {
return attrVal.n();
}
return attrVal.toString();
}
}
DynamoDbColumnType というenumクラスも作成。
package com.silverboxsoft.classes;
public enum DynamoDbColumnType {
STRING, NUMBER, BOOLEAN, STRING_SET, NUMBER_SET, MAP, LIST, BINARY, BINARY_SET, NULL;
}
あとがき
今回はソースコードばかりになってしまった。基本技術は、後述の参考ページで書かれている感じ。
あと、今回提示したソース以外にも細かいソースは色々あるので、興味のある方は Github で。
次回は開発時などに使うlocalDynamoDbに対応とか、テーブルリスト表示とかしてみる予定。