⛓️
個人的ORM(O/Rマッピング)の段階
- ソースコードはほとんどreplitに貼り付けて実行できます。
- lombok使いたいですが、コピペで実行してもらいたいので使ってません。
- 図は気が向いたら整理します
やりたい事
仕様:CSVファイルからRDBにデータをマッピングしたい状況です。
仕様:O/Rマッパーを使ってRDBへCSVデータを入力します
制限:RDBで使えない列名がCSVにあります。そのため、規約として CSVの列名=RDBの列名
は出来ません
CSVの列名 != RDBの列名
仕様:CSVは1行目がヘッダ行で、2行目以降がデータ行です。
項目A,項目B,項目C
1,Tom,22
2,Mike,30
3,Jon,40
マッピング方法の段階
段階はありますが、Input -> Output に違いはありません。手段の違いなだけです。
- 第1段階:単一機能を意識して項目名とメソッド名を直接マッピング
- 第2段階:共通化の始まり
- 第3段階:共通インターフェースを発見
- 第4段階:条件文を共通化
- 第5段階(終わり):リフレクションを使って一括処理
第1段階:単一機能を意識して項目名とメソッド名を直接マッピング
単純に一対一マッピングします。
O/RMapper, RDB部分はコーディングしてません。
import java.util.*;
class Main {
public static void main(String args[]) {
new Controller().exec("項目A,項目B,項目C\n1,Tom,22\n2,Mike,30\n3,Jon,40");
}
}
class Controller {
public void exec(String csv) {
//////////////////////////////////////////
// ヘッダ行解析
// ヘッダ行の列名と列位置を保持するためのマップです
Map<String, Integer> indexMap = new LinkedHashMap<>();
// 改行コードで区切ります
String[] csvLinesArray = csv.split("\n");
// ヘッダ行だけをカンマで区切ります
String[] headerArray = csvLinesArray[0].split(",");
for (int i = 0; i < headerArray.length; i++) {
// ヘッダ行の列名と列位置を保持します
indexMap.put(headerArray[i], i);
}
//////////////////////////////////////////
// O/Rマッピング用エンティティへ変換する
List<OutputModel> list = new ArrayList<>();
List<String> csvLinesList = List.of(csvLinesArray).subList(1, csvLinesArray.length);
csvLinesList.forEach(csvLine -> {
String[] csvArray = csvLine.split(",");
OutputModel output = new OutputModel();
list.add(output);
indexMap.entrySet().stream().forEach(e -> {
// ここで実際にマッピング、[項目名] -> [メソッド名] というマッピングです
if (e.getKey().equals("項目A")) {
output.setItemA(csvArray[e.getValue()]);
} else if (e.getKey().equals("項目B")) {
output.setItemB(csvArray[e.getValue()]);
} else if (e.getKey().equals("項目C")) {
output.setItemC(csvArray[e.getValue()]);
}
});
});
//////////////////////////////////////////
// TODO: ここでO/Rマッピングします
}
}
class OutputModel {
public void setItemA(String v) { System.out.println("setItemA:" + v); }
public void setItemB(String v) { System.out.println("setItemB:" + v); }
public void setItemC(String v) { System.out.println("setItemC:" + v); }
}
第2段階:共通化の始まり
クラスを使って一対一マッピングを分かり易くしようとします。
import java.util.*;
class Main {
public static void main(String args[]) {
new Controller().exec("項目A,項目B,項目C\n1,Tom,22\n2,Mike,30\n3,Jon,40");
}
}
class Controller {
public void exec(String csv) {
//////////////////////////////////////////
// 入力をモデル化する
InputModel input = new InputModel();
input.parse(csv);
//////////////////////////////////////////
// モデル化した入力をO/Rマッピング用エンティティへ変換する
List<OutputModel> output = new ObjectMapper().map(input);
//////////////////////////////////////////
// TODO: ここでO/Rマッピングします
}
}
class InputModel {
Map<String, Integer> indexMap;
String[] csvLinesArray;
Map<String, Integer> getIndexMap() { return indexMap; }
String[] getCsvLinesArray() { return csvLinesArray; }
public void parse(String csv) {
//////////////////////////////////////////
// ヘッダ行解析
// ヘッダ行の列名と列位置を保持するためのマップです
this.indexMap = new LinkedHashMap<>();
// 改行コードで区切ります
this.csvLinesArray = csv.split("\n");
// ヘッダ行だけをカンマで区切ります
String[] headerArray = csvLinesArray[0].split(",");
for (int i = 0; i < headerArray.length; i++) {
// ヘッダ行の列名と列位置を保持します
indexMap.put(headerArray[i], i);
}
}
}
class OutputModel {
public void setItemA(String v) { System.out.println("setItemA:" + v); }
public void setItemB(String v) { System.out.println("setItemB:" + v); }
public void setItemC(String v) { System.out.println("setItemC:" + v); }
}
class ObjectMapper {
public List<OutputModel> map(InputModel input) {
//////////////////////////////////////////
// O/Rマッピング用エンティティへ変換する
List<OutputModel> list = new ArrayList<>();
List<String> csvLinesList = List.of(input.getCsvLinesArray()).subList(1, input.getCsvLinesArray().length);
csvLinesList.forEach(csvLine -> {
String[] csvArray = csvLine.split(",");
OutputModel output = new OutputModel();
list.add(output);
input.getIndexMap().entrySet().stream().forEach(e -> {
// ここで実際にマッピング、[項目名] -> [メソッド名] というマッピングです
if (e.getKey().equals("項目A")) {
output.setItemA(csvArray[e.getValue()]);
} else if (e.getKey().equals("項目B")) {
output.setItemB(csvArray[e.getValue()]);
} else if (e.getKey().equals("項目C")) {
output.setItemC(csvArray[e.getValue()]);
}
});
});
return list;
}
}
第3段階:共通インターフェースを発見
更に処理を共通化します。
import java.util.*;
class Main {
public static void main(String args[]) {
new Controller().exec("項目A,項目B,項目C,項目X,項目Y,項目Z\n1,Tom,22,Today,git,repo1\n2,Mike,30,Yesterday,svn,repo2\n3,Jon,40,Future,vss,repo3");
}
}
class Controller {
public void exec(String csv) {
//////////////////////////////////////////
// 入力をモデル化する
InputModel input = new InputModel();
input.parse(csv);
//////////////////////////////////////////
// モデル化した入力をO/Rマッピング用エンティティへ変換する
List<OutputModelX> outputX = new ObjectMapper().mapX(input);
List<OutputModelY> outputY = new ObjectMapper().mapY(input);
//////////////////////////////////////////
// TODO: ここでO/Rマッピングします
}
}
class InputModel {
Map<String, Integer> indexMap;
String[] csvLinesArray;
Map<String, Integer> getIndexMap() { return indexMap; }
String[] getCsvLinesArray() { return csvLinesArray; }
public void parse(String csv) {
//////////////////////////////////////////
// ヘッダ行解析
// ヘッダ行の列名と列位置を保持するためのマップです
this.indexMap = new LinkedHashMap<>();
// 改行コードで区切ります
this.csvLinesArray = csv.split("\n");
// ヘッダ行だけをカンマで区切ります
String[] headerArray = csvLinesArray[0].split(",");
for (int i = 0; i < headerArray.length; i++) {
// ヘッダ行の列名と列位置を保持します
indexMap.put(headerArray[i], i);
}
}
}
interface OutputModel {
void setItem(String key, int index, String[] csvArray);
}
class OutputModelX implements OutputModel {
@Override
public void setItem(String key, int index, String[] csvArray) {
if (key.equals("項目A")) {
setItemA(csvArray[index]);
} else if (key.equals("項目B")) {
setItemB(csvArray[index]);
} else if (key.equals("項目C")) {
setItemC(csvArray[index]);
}
}
public void setItemA(String v) { System.out.println("setItemA:" + v); }
public void setItemB(String v) { System.out.println("setItemB:" + v); }
public void setItemC(String v) { System.out.println("setItemC:" + v); }
}
class OutputModelY implements OutputModel {
@Override
public void setItem(String key, int index, String[] csvArray) {
if (key.equals("項目X")) {
setItemX(csvArray[index]);
} else if (key.equals("項目Y")) {
setItemY(csvArray[index]);
} else if (key.equals("項目Z")) {
setItemZ(csvArray[index]);
}
}
public void setItemX(String v) { System.out.println("setItemX:" + v); }
public void setItemY(String v) { System.out.println("setItemY:" + v); }
public void setItemZ(String v) { System.out.println("setItemZ:" + v); }
}
class ObjectMapper {
public List<OutputModelX> mapX(InputModel input) {
//////////////////////////////////////////
// O/Rマッピング用エンティティへ変換する
List<OutputModelX> list = new ArrayList<>();
List<String> csvLinesList = List.of(input.getCsvLinesArray()).subList(1, input.getCsvLinesArray().length);
csvLinesList.forEach(csvLine -> {
String[] csvArray = csvLine.split(",");
list.add(new OutputModelX());
input.getIndexMap().entrySet().stream().forEach(e -> {
// ここで実際にマッピング、[項目名] -> [メソッド名] というマッピングです
list.get(list.size() - 1).setItem(e.getKey(), e.getValue(), csvArray);
});
});
return list;
}
public List<OutputModelY> mapY(InputModel input) {
//////////////////////////////////////////
// O/Rマッピング用エンティティへ変換する
List<OutputModelY> list = new ArrayList<>();
List<String> csvLinesList = List.of(input.getCsvLinesArray()).subList(1, input.getCsvLinesArray().length);
csvLinesList.forEach(csvLine -> {
String[] csvArray = csvLine.split(",");
list.add(new OutputModelY());
input.getIndexMap().entrySet().stream().forEach(e -> {
// ここで実際にマッピング、[項目名] -> [メソッド名] というマッピングです
list.get(list.size() - 1).setItem(e.getKey(), e.getValue(), csvArray);
});
});
return list;
}
}
第4段階:条件文を共通化
if
文ばかりだとしんどいですね、 Map
を使ってデータと処理をマッピングします。
ついでに、エンティティ毎に書いてた同じ処理も共通化しました。
import java.util.*;
import java.util.function.*;
class Main {
public static void main(String args[]) {
new Controller().exec("項目A,項目B,項目C,項目X,項目Y,項目Z\n1,Tom,22,Today,git,repo1\n2,Mike,30,Yesterday,svn,repo2\n3,Jon,40,Future,vss,repo3");
}
}
class Controller {
public void exec(String csv) {
//////////////////////////////////////////
// 入力をモデル化する
InputModel input = new InputModel();
input.parse(csv);
//////////////////////////////////////////
// モデル化した入力をO/Rマッピング用エンティティへ変換する
List<OutputModelX> outputX = new ObjectMapper().mapX(input);
List<OutputModelY> outputY = new ObjectMapper().mapY(input);
//////////////////////////////////////////
// TODO: ここでO/Rマッピングします
}
}
class InputModel {
Map<String, Integer> indexMap;
String[] csvLinesArray;
Map<String, Integer> getIndexMap() { return indexMap; }
String[] getCsvLinesArray() { return csvLinesArray; }
public void parse(String csv) {
//////////////////////////////////////////
// ヘッダ行解析
// ヘッダ行の列名と列位置を保持するためのマップです
this.indexMap = new LinkedHashMap<>();
// 改行コードで区切ります
this.csvLinesArray = csv.split("\n");
// ヘッダ行だけをカンマで区切ります
String[] headerArray = csvLinesArray[0].split(",");
for (int i = 0; i < headerArray.length; i++) {
// ヘッダ行の列名と列位置を保持します
indexMap.put(headerArray[i], i);
}
}
}
interface OutputModel {
List<OutputModelMap> getMap();
default void setItem(String key, int index, String[] csvArray) {
getMap().stream().filter(e -> e.getKey().equals(key))
.findFirst()
.ifPresent(e -> e.getSetter().accept(csvArray[index]));
}
}
class OutputModelMap {
String key;
String getKey() { return key; }
Consumer<String> setter;
Consumer<String> getSetter() { return setter; }
public static OutputModelMap build(String key, Consumer<String> setter) {
OutputModelMap obj = new OutputModelMap();
obj.key = key;
obj.setter = setter;
return obj;
}
}
class OutputModelX implements OutputModel {
static final List<OutputModelMap> MAP = new ArrayList<>() {{
add(OutputModelMap.build("項目A", (v) -> {
setItemA(v);
}));
add(OutputModelMap.build("項目B", (v) -> {
setItemB(v);
}));
add(OutputModelMap.build("項目C", (v) -> {
setItemC(v);
}));
}};
@Override
public List<OutputModelMap> getMap() {
return MAP;
}
public static void setItemA(String v) { System.out.println("setItemA:" + v); }
public static void setItemB(String v) { System.out.println("setItemB:" + v); }
public static void setItemC(String v) { System.out.println("setItemC:" + v); }
}
class OutputModelY implements OutputModel {
static final List<OutputModelMap> MAP = new ArrayList<>() {{
add(OutputModelMap.build("項目X", (v) -> {
setItemX(v);
}));
add(OutputModelMap.build("項目Y", (v) -> {
setItemY(v);
}));
add(OutputModelMap.build("項目Z", (v) -> {
setItemZ(v);
}));
}};
@Override
public List<OutputModelMap> getMap() {
return MAP;
}
public static void setItemX(String v) { System.out.println("setItemX:" + v); }
public static void setItemY(String v) { System.out.println("setItemY:" + v); }
public static void setItemZ(String v) { System.out.println("setItemZ:" + v); }
}
class ObjectMapper {
private <O extends OutputModel> List<O> map(InputModel input, Supplier<O> outputSupplier) {
//////////////////////////////////////////
// O/Rマッピング用エンティティへ変換する
List<O> list = new ArrayList<>();
List<String> csvLinesList = List.of(input.getCsvLinesArray()).subList(1, input.getCsvLinesArray().length);
csvLinesList.forEach(csvLine -> {
String[] csvArray = csvLine.split(",");
list.add(outputSupplier.get());
input.getIndexMap().entrySet().stream().forEach(e -> {
// ここで実際にマッピング、[項目名] -> [メソッド名] というマッピングです
list.get(list.size() - 1).setItem(e.getKey(), e.getValue(), csvArray);
});
});
return list;
}
public List<OutputModelX> mapX(InputModel input) {
return map(input, () -> new OutputModelX());
}
public List<OutputModelY> mapY(InputModel input) {
return map(input, () -> new OutputModelY());
}
}
第5段階(終わり):リフレクションを使って一括処理
名前が違っていても、規則を導入すればマッピングは可能です。
リフレクションも使って一気にやってしまいます。
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
class Main {
public static void main(String args[]) {
new Controller().exec("項目A,項目B,項目C,項目X,項目Y,項目Z\n1,Tom,22,Today,git,repo1\n2,Mike,30,Yesterday,svn,repo2\n3,Jon,40,Future,vss,repo3");
}
}
class Controller {
public void exec(String csv) {
//////////////////////////////////////////
// 入力をモデル化する
InputModel input = new InputModel();
input.parse(csv);
//////////////////////////////////////////
// モデル化した入力をO/Rマッピング用エンティティへ変換する
List<OutputModelX> outputX = new ObjectMapper().mapX(input);
List<OutputModelY> outputY = new ObjectMapper().mapY(input);
Consumer<List<?>> println = (l) -> l.forEach(o ->
List.of(o.getClass().getDeclaredFields()).forEach(f -> { try {
System.out.println("field:" + f.getName() + ":" + f.get(o));
} catch (Exception e) {}}));
println.accept(outputX);
println.accept(outputY);
//////////////////////////////////////////
// TODO: ここでO/Rマッピングします
}
}
class InputModel {
Map<String, Integer> indexMap;
String[] csvLinesArray;
Map<String, Integer> getIndexMap() { return indexMap; }
String[] getCsvLinesArray() { return csvLinesArray; }
public void parse(String csv) {
//////////////////////////////////////////
// ヘッダ行解析
// ヘッダ行の列名と列位置を保持するためのマップです
this.indexMap = new LinkedHashMap<>();
// 改行コードで区切ります
this.csvLinesArray = csv.split("\n");
// ヘッダ行だけをカンマで区切ります
String[] headerArray = csvLinesArray[0].split(",");
for (int i = 0; i < headerArray.length; i++) {
// ヘッダ行の列名と列位置を保持します
indexMap.put(headerArray[i], i);
}
}
}
interface OutputModel {
default void setItem(String key, int index, String[] csvArray) {
Stream.of(this.getClass().getDeclaredFields()).filter(f -> {
// TODO: なにがしかの名前変換処理を作ります
// ここで実際にマッピング、[項目名] -> [フィールド名] というマッピングです
String convertedKey = key.replace("項目", "item");
// System.out.println(convertedKey + ":" + f.getName());
return convertedKey.toLowerCase().equals(f.getName().toLowerCase());
}).findFirst()
.ifPresent(f -> {
try {
f.setAccessible(true);
f.set(this, csvArray[index]);
} catch (Exception e) {
// TODO: なにがしかのロギング処理を作ります
System.out.println(e.getMessage());
}
});
}
}
class OutputModelX implements OutputModel {
String itemA;
String itemB;
String itemC;
}
class OutputModelY implements OutputModel {
String itemX;
String itemY;
String itemZ;
}
class ObjectMapper {
private <O extends OutputModel> List<O> map(InputModel input, Supplier<O> outputSupplier) {
//////////////////////////////////////////
// O/Rマッピング用エンティティへ変換する
List<O> list = new ArrayList<>();
List<String> csvLinesList = List.of(input.getCsvLinesArray()).subList(1, input.getCsvLinesArray().length);
csvLinesList.forEach(csvLine -> {
String[] csvArray = csvLine.split(",");
list.add(outputSupplier.get());
input.getIndexMap().entrySet().stream().forEach(e -> {
list.get(list.size() - 1).setItem(e.getKey(), e.getValue(), csvArray);
});
});
return list;
}
public List<OutputModelX> mapX(InputModel input) {
return map(input, () -> new OutputModelX());
}
public List<OutputModelY> mapY(InputModel input) {
return map(input, () -> new OutputModelY());
}
}
Discussion