データ履歴管理のためのテンポラルデータモデルとreladomoの紹介 #jjug_ccc...
TRANSCRIPT
データ履歴管理のための
テンポラルデータモデルと
Reladomoの紹介
株式会社FOLIO 伊藤博志
JJUG CCC 2017 Spring 2017.5.20
#ccc_g3
Reladomo is an open source software Licensed under Apache 2.0 License,Copyright 2016 Goldman Sachs, Its name may be a trademark of its owner.
1
株式会社FOLIOの紹介 hashtag: #ccc_g3
https://www.wantedly.com/companies/folio/projectsエンジニア募集中!
2
Agenda
1. 自己紹介
2. RDBMSで履歴データを扱う
スナップショットデータモデル
トランザクション時間データモデル
有効時間データモデル
バイテンポラルデータモデル
3. Javaからバイテンポラルモデルを容易に扱うReladomoの紹介
hashtag: #ccc_g3
3
自己紹介
趣味:ドラム演奏
JavaOneコミュニティバンド
Null Pointersで演奏経験あり(日本人初)
Tech Lead @ FOLIO
伊藤 博志
Eclipse Collections:共同プロジェクトリード兼コミッターReladomo:コントリビューターOpenJDK:コントリビューターJJUG CCC、Java Day Tokyo、JavaOne San Francisco登壇
2017年5月17日に株式会社FOLIO入社。
hashtag: #ccc_g3
4
RDBMSで変更履歴をどう扱うか
hashtag: #ccc_g3
5
RDBMSで履歴データを扱う
単純なシナリオを考えてみましょう
シンプルな人事システム。扱う情報は姓名のみ。
4月1日に「鈴木花子」さん入社。3月1日にシステムに登録
6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録
6月8日にシステム上で修正(斉藤=>斎藤) 。事実としては6月6日づけで直したい
12月1日、退職にともない同日にシステム上で情報を無効化
hashtag: #ccc_g3
6
RDBMSで履歴データを扱う
シナリオをよく観察すると、2種類の履歴が存在
hashtag: #ccc_g3
3/1 4/1 6/6 6/8 12/1
鈴木花子
事実情報の履歴
システムに反映された
履歴
入社🎉
「鈴木花子」をシステムに登録
結婚して斎藤になる👰
名字を「斉藤」に変更
名字を「斎藤」に変更
退職†
データを無効化
7
RDBMSで履歴データを扱う
最新の情報さえ分かれば良い
いつシステムに反映されたか履歴が知りたい
2種類の履歴から考えうる4種類の要件
事実情報の履歴が知りたい
事実情報と、いつシステムに反映されたか両方の履歴が知りたい
hashtag: #ccc_g3
8
RDBMSで履歴データを扱う
スナップショットデータモデル
トランザクション時間データモデル
有効時間データモデル
バイテンポラルデータモデル
各要件を満たすために、4つのデータモデルが考えられる
hashtag: #ccc_g3
9
スナップショットデータモデル
hashtag: #ccc_g3
最新の情報さえ分かれば良い
10
スナップショットデータモデル
スナップショットデータモデルで表現できるのは、最新の情報のみ(履歴は表現できない)
hashtag: #ccc_g3
3/1 4/1 6/6 6/8 12/1
鈴木花子
事実情報の履歴
システムに反映された
履歴
入社🎉
「鈴木花子」をシステムに登録
結婚して斎藤になる👰
名字を「斉藤」に変更
名字を「斎藤」に変更
退職†
データを無効化
11
スナップショットデータモデル
姓 名 inserted_at updated_at
鈴木 花子 2017/3/1 2017/3/1
4月1日に「鈴木花子」さん入社。3月1日にシステムに登録
鈴木さん入社!
入社日というカラムがあってもいいような気がする
hashtag: #ccc_g3
12
スナップショットデータモデル
6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録
間違えて「斉藤」と入力してしまいました
姓 名 inserted_at updated_at
斉藤 花子 2017/3/1 2017/6/6
hashtag: #ccc_g3
13
スナップショットデータモデル
6月8日にシステム上で修正(斉藤=>斎藤)
正しい姓は「斎藤」さんですね。。。
姓 名 inserted_at updated_at
斎藤 花子 2017/3/1 2017/6/8
hashtag: #ccc_g3
14
スナップショットデータモデル
12月1日、退職にともない同日に人事情報をシステム上で無効化
_人人人人人人人人人人人_> 物理削除 < ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
物理削除がイヤなら禁断の論理削除フラグという手もあるよ。フフッ
姓 名 inserted_at updated_at
hashtag: #ccc_g3
15
スナップショットデータモデル
スナップショットデータモデルで履歴を実装しようとすると、バージョンカラムや履歴テーブルを別に用意するなど、力技が求められる
世に存在する履歴系のデータは、この力技で実装されていることが多い(印象)
さらにスナップショットデータモデルでは、データを無効化するのに「論理削除」か「物理削除」か、と悩むケースも
hashtag: #ccc_g3
16
トランザクション時間データモデル
hashtag: #ccc_g3
いつシステムに反映されたか履歴が知りたい
17
トランザクション時間データモデル
トランザクション時間データモデルで表現できるのは、「システムに反映された」履歴
hashtag: #ccc_g3
3/1 4/1 6/6 6/8 12/1
鈴木花子
事実情報の履歴
システムに反映された
履歴
入社🎉
「鈴木花子」をシステムに登録
結婚して斎藤になる👰
名字を「斉藤」に変更
名字を「斎藤」に変更
退職†
データを無効化
18
トランザクション時間データモデル
姓 名 IN OUT
鈴木 花子 2017/3/1 9999/12/1
鈴木さん入社!
INはデータベースに挿入された日付、OUTはその行が無効になった(なる)日付、OUTはデフォルトでINFINITY(この場合9999年)とするよ
やっぱり入社日カラムが欲しくなる。。
4月1日に「鈴木花子」さん入社。3月1日にシステムに登録
hashtag: #ccc_g3
19
トランザクション時間データモデル
姓 名 IN OUT
鈴木 花子 2017/3/1 2017/6/6
斉藤 花子 2017/6/6 9999/12/1
鈴木さん姓が2017/6/6に無効(OUT)になり、斉藤さん姓が同日入力(IN)されたということだね
6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録
hashtag: #ccc_g3
20
トランザクション時間データモデル
姓 名 IN OUT
鈴木 花子 2017/3/1 2017/6/6
斉藤 花子 2017/6/6 2017/6/8
斎藤 花子 2017/6/8 9999/12/1
斉藤さん姓が2017/6/8に無効(OUT)になり、斎藤さん姓が同日入力(IN)されたということだね
6月8日にシステム上で修正(斉藤=>斎藤) 。事実としては6月6日づけで直したい
「斎藤」姓がシステム上有効なのは6/8からか。。
hashtag: #ccc_g3
21
トランザクション時間データモデル
姓 名 IN OUT
鈴木 花子 2017/3/1 2017/6/6
斉藤 花子 2017/6/6 2017/6/8
斎藤 花子 2017/6/8 2017/12/1
2017/12/1日付で無効化(OUT)!
12月1日、退職にともない同日に人事情報をシステム上で無効化
hashtag: #ccc_g3
22
トランザクション時間データモデル
履歴データをどう検索するのか?
hashtag: #ccc_g3
23
トランザクション時間データモデル
任意の時間Xにデータベース上で最新だった情報をとってくる
select * from EMPLOYEE
where IN < X
and OUT >= X
hashtag: #ccc_g3
24
トランザクション時間データモデル
姓 名 IN OUT
鈴木 花子 2017/3/1 2017/6/6
3月15日時点でデータベース上で最新だった情報をとってくる
select * from EMPLOYEE
where IN < “2017/3/15”
and OUT >= “2017/3/15”
姓 名 IN OUT
鈴木 花子 2017/3/1 2017/6/6
斉藤 花子 2017/6/6 2017/6/8
斎藤 花子 2017/6/8 2017/12/1
hashtag: #ccc_g3
25
トランザクション時間データモデル
姓 名 IN OUT
斉藤 花子 2017/6/6 2017/6/8
select * from EMPLOYEE
where IN < “2017/6/7”
and OUT >= “2017/6/7”
姓 名 IN OUT
鈴木 花子 2017/3/1 2017/6/6
斉藤 花子 2017/6/6 2017/6/8
斎藤 花子 2017/6/8 2017/12/1
6月7日時点でデータベース上で最新だった情報をとってくる
hashtag: #ccc_g3
26
トランザクション時間データモデル
姓 名 IN OUT
斎藤 花子 2017/6/8 2017/12/1
姓 名 IN OUT
鈴木 花子 2017/3/1 2017/6/6
斉藤 花子 2017/6/6 2017/6/8
斎藤 花子 2017/6/8 2017/12/1
6月9日時点でデータベース上で最新だった情報をとってくる
select * from EMPLOYEE
where IN < “2017/6/9"
and OUT >= “2017/6/9”
hashtag: #ccc_g3
27
トランザクション時間データモデル
姓 名 IN OUT
select * from EMPLOYEE
where IN < “2017/12/5"
and OUT >= “2017/12/5”
姓 名 IN OUT
鈴木 花子 2017/3/1 2017/6/6
斉藤 花子 2017/6/6 2017/6/8
斎藤 花子 2017/6/8 2017/12/1
2017/12/1以降に有効な行が存在しないので、論理削除フラグを使わなくてもシステム上で無効化されている!
12月5日時点でデータベース上で最新だった情報をとってくる
hashtag: #ccc_g3
28
トランザクション時間データモデル
ある時点でデータベース上で最新だった情報を一意に取得することができる
過去の履歴を一気に取得することもでき、時間軸でソートも可
最新の行を無効化するにはOUTコラムを更新するだけでOK(物理削除も論理削除フラグも不要)
監査情報を自然な形でモデリングできる
トランザクション時間データモデルの適用例
ドキュメントの履歴管理
状態遷移の履歴管理
etc.
hashtag: #ccc_g3
29
有効時間データモデル
hashtag: #ccc_g3
事実情報の履歴がほしい
30
有効時間データモデル
有効時間データモデルで表現できるのは、「事実情報」の履歴
hashtag: #ccc_g3
3/1 4/1 6/6 6/8 12/1
鈴木花子
事実情報の履歴
システムに反映された
履歴
入社🎉
「鈴木花子」をシステムに登録
結婚して斎藤になる👰
名字を「斉藤」に変更
名字を「斎藤」に変更
退職†
データを無効化
31
有効時間データモデル
姓 名 FROM THRU
鈴木 花子 2017/4/1 9999/12/1
4月1日に「鈴木花子」さん入社。3月1日にシステムに登録
鈴木さん入社!FROMは当該データが事実として有効である最初の日、THRUは当該データが事実として有効である最終日の翌日*、THRUはデフォルトでINFINITY(この場合9999年)とするよ
これで入社日がわかる。。
*ここではTHRUを最終日翌日としていますが、FROMを最初の日の前日とする方法もあります
hashtag: #ccc_g3
*Throughの略
32
有効時間データモデル
姓 名 FROM THRU
鈴木 花子 2017/4/1 2017/6/6
斉藤 花子 2017/6/6 9999/12/1
6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録
事実として「鈴木」姓の最終日が2017/6/5(THRU-1)になり、2017/6/6(FROM)から間違った「斉藤」姓が有効に
hashtag: #ccc_g3
33
有効時間データモデル
姓 名 FROM THRU
鈴木 花子 2017/4/1 2017/6/6
斎藤 花子 2017/6/6 9999/12/1
2017/6/6(FROM)から正しい「斎藤」さん姓になった。間違えた「斉藤」姓は上書きされたので、システム上の履歴はわからないね。
6月8日にシステム上で修正(斉藤=>斎藤) 。事実としては6月6日づけで直したい
hashtag: #ccc_g3
34
有効時間データモデル
姓 名 FROM THRU
鈴木 花子 2017/4/1 2017/6/6
斎藤 花子 2017/6/6 2017/12/1
12月1日、退職にともない同日に人事情報をシステム上で無効化
これで鈴木さんが2017/4/1に入社、2017/6/6に斎藤姓に代わり、2017/12/1に退社した、という事実情報がすべて表現されるね!
hashtag: #ccc_g3
35
有効時間データモデル
検索方法はトランザクション時間
データモデルの場合と同様なので省略
hashtag: #ccc_g3
36
有効時間データモデル
ある時点での「事実情報」を一意に取得することができる
過去の事実の履歴を一気に取得することもでき、時間軸でソートも可
最新の行を無効化するにはTHRUコラムを更新するだけでOK(物理削除も論理削除フラグも不要)
事実の履歴を自然な形でモデリングできる
有効時間データモデルの適用例
変更履歴を気にしないビジネスデータ
etc.
hashtag: #ccc_g3
37
バイテンポラルデータモデル
hashtag: #ccc_g3
事実情報と、いつシステムに反映されたか両方の履歴が知りたい
38
バイテンポラルデータモデル
バイテンポラルデータモデルで表現できるのは、「システムに反映された」履歴と「事実情報」の履歴両方
hashtag: #ccc_g3
3/1 4/1 6/6 6/8 12/1
鈴木花子
事実情報の履歴
システムに反映された
履歴
入社🎉
「鈴木花子」をシステムに登録
結婚して斎藤になる👰
名字を「斉藤」に変更
名字を「斎藤」に変更
退職†
データを無効化
39
バイテンポラルデータモデル
姓 名 FROM THRU IN OUT
鈴木 花子 2017/4/1 9999/12/1 2017/3/1 9999/12/1
4月1日に「鈴木花子」さん入社。3月1日にシステムに登録
鈴木さん入社!
4月1日に入社したこともわかるし、3月1日にシステムに反映されたこともわかる!
hashtag: #ccc_g3
40
バイテンポラルデータモデル
姓 名 FROM THRU IN OUT
鈴木 花子 2017/4/1 9999/12/1 2017/3/1 2017/6/6
鈴木 花子 2017/4/1 2017/6/6 2017/6/6 9999/12/1
斉藤 花子 2017/6/6 9999/12/1 2017/6/6 9999/12/1
6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録
最初の行が「システム上」無効になり、新しい「事実」が2つの期間にわたって挿入されているね。
hashtag: #ccc_g3
41
バイテンポラルデータモデル
姓 名 FROM THRU IN OUT
鈴木 花子 2017/4/1 9999/12/1 2017/3/1 2017/6/6
鈴木 花子 2017/4/1 2017/6/6 2017/6/6 9999/12/1
斉藤 花子 2017/6/6 9999/12/1 2017/6/6 2017/6/8
斎藤 花子 2017/6/6 9999/12/1 2017/6/8 9999/12/1
名字の漢字が間違っており、6月8日にシステム上で修正(斉藤=>斎藤)
間違えた行が「システム上」無効になり、正しい「事実」が挿入されているね。
3つ目の行は「斉藤」という間違った事実が「システム上有効だった」期間が6/6から6/8の間存在するという情報を表しているよ。
hashtag: #ccc_g3
42
バイテンポラルデータモデル
姓 名 FROM THRU IN OUT
鈴木 花子 2017/4/1 9999/12/1 2017/3/1 2017/6/6
鈴木 花子 2017/4/1 2017/6/6 2017/6/6 9999/12/1
斉藤 花子 2017/6/6 9999/12/1 2017/6/6 2017/6/8
斎藤 花子 2017/6/6 9999/12/1 2017/6/8 2017/12/1
斎藤 花子 2017/6/6 2017/12/1 2017/12/1 9999/12/1
12月1日、退職にともない同日に人事情報をシステム上で無効化
無事、事実情報(入社、姓変更、退社)と変更履歴がすべて記録されました!
hashtag: #ccc_g3
43
バイテンポラルデータモデル
バイテンポラルデータモデルは「システム履歴」と「事実情報の履歴」を同時に正しく表現することができる非常に強力なモデル
バイテンポラルデータモデルの適用例
監査履歴が重要な金融システムの基幹データ
その他、時系列に紐づくビジネスデータで変更履歴が重要なユースケース
etc.
hashtag: #ccc_g3
44
どう実装する?
バイテンポラルデータモデルの雰囲気はわかった
でも、ぱっと見複雑そう。結局、実装するのが大変なのでは。。。?
安心してください
Reladomoを使えば簡単です!
hashtag: #ccc_g3
45
Javaからバイテンポラルモデルを容易に扱えるReladomoの紹介
hashtag: #ccc_g3
46
Reladomoとは
https://github.com/goldmansachs/reladomo
ゴールドマン・サックス社が2016年10月にGitHubにOSSとして公開したJava ORMフレームワーク
Apache License 2.0
バイテンポラルデータモデルをネイティブサポート
強力に型付けられたクエリー言語
シャーディングのネイティブサポート
ユニットテストのフルサポート
etc.
hashtag: #ccc_g3
47
Reladomoとは
Reladomoのコード生成
xmlによるエンティティ定義を用いてコード生成
同xmlからddlの生成も可能
Abstract Classはxmlに変更のある都度生成される
Concrete Classは初回のみ生成しVCSにコミット、ビジネスロジックを記述。コード生成で上書きされない。
hashtag: #ccc_g3
48
Employeeエンティティ定義ファイル hashtag: #ccc_g3
<MithraObject objectType="transactional"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="reladomoobject.xsd">
<PackageName>sample.domain</PackageName><ClassName>Employee</ClassName><DefaultTable>EMPLOYEE</DefaultTable>
<Attribute name="employeeId" javaType="int" columnName="EMPLOYEE_ID" primaryKey="true"primaryKeyGeneratorStrategy="SimulatedSequence">
<SimulatedSequence sequenceName="Employee"sequenceObjectFactoryName="sample.util.ObjectSequenceObjectFactory"hasSourceAttribute="false"batchSize="1"initialValue="1"incrementSize="1"/>
</Attribute><Attribute name="firstName" javaType="String" columnName="FIRST_NAME" nullable="false" maxLength="64"/><Attribute name="lastName" javaType="String" columnName="LAST_NAME" nullable="false" maxLength="64"/>
</MithraObject>
49
Employeeエンティティ hashtag: #ccc_g3
トランザクション時間
生成されたAbstract Class
50
Employeeエンティティ hashtag: #ccc_g3
Concrete Class (初回のみ生成)
さまざまなビジネスロジックを記述できる
51
DBトランザクション hashtag: #ccc_g3
テンポラルオブジェクトの挿入、更新はトランザクション内でのみ可能
(以降のコード例では省略している場合も)
Employee hanako = MithraManagerProvider.getMithraManager().executeTransactionalCommand(tx -> {
Employee hanakoNew = new Employee(); hanakoNew.setLastName(“鈴木”);hanakoNew.setFirstName(“花子”);hanakoNew.insert(); return hanakoNew; //トランザクション終了後に使用したいオブジェクトを返り値とし
て与えることができる。});
52
ReladomoのFinder DSL
型付けられた強力なクエリー言語
自動生成されたフィールドを用いてOperationを構築
hashtag: #ccc_g3
Operation op = EmployeeFinder.lastName().eq("鈴木").and(EmployeeFinder.firstName().eq("花子"));
final Employee hanako = EmployeeFinder.findOne(op);
53
Reladomoでトランザクション時間を扱う例
hashtag: #ccc_g3
54
Employeeエンティティ定義ファイル hashtag: #ccc_g3
<MithraObject objectType="transactional"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="reladomoobject.xsd">
<PackageName>sample.domain</PackageName><ClassName>Employee</ClassName><DefaultTable>EMPLOYEE</DefaultTable>
<AsOfAttribute name="processingDate" fromColumnName="IN_Z" toColumnName="OUT_Z"toIsInclusive="false"isProcessingDate="true"infinityDate="[com.gs.fw.common.mithra.util.DefaultInfinityTimestamp.getDefaultInfinity()]"defaultIfNotSpecified="[com.gs.fw.common.mithra.util.DefaultInfinityTimestamp.getDefaultInfinity()]"
/>
<Attribute name="employeeId" javaType="int" columnName="EMPLOYEE_ID" primaryKey="true"primaryKeyGeneratorStrategy="SimulatedSequence">
<SimulatedSequence sequenceName="Employee"sequenceObjectFactoryName="sample.util.ObjectSequenceObjectFactory"hasSourceAttribute="false"batchSize="1"initialValue="1"incrementSize="1"/>
</Attribute><Attribute name="firstName" javaType="String" columnName="FIRST_NAME" nullable="false" maxLength="64"/><Attribute name="lastName" javaType="String" columnName="LAST_NAME" nullable="false" maxLength="64"/>
</MithraObject>
トランザクション時間
55
トランザクション時間データモデル
姓 名 IN OUT
鈴木 花子 2017/3/1 9999/12/1
hashtag: #ccc_g3
//3月1日にこのコードが実行されるEmployee hanakoNew = new Employee(); //メモリ上にオブジェクト作成hanakoNew.setLastName("鈴木");hanakoNew.setFirstName("花子");hanakoNew.insert(); // データベースに挿入
4月1日に「鈴木花子」さん入社。3月1日にシステムに登録
56
トランザクション時間データモデル
姓 名 IN OUT
鈴木 花子 2017/3/1 2017/6/6
斉藤 花子 2017/6/6 9999/12/1
6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録
hashtag: #ccc_g3
//6月6日にこのコードが実行されるhanako.setLastName("斉藤"); // アップデート
57
トランザクション時間データモデル
姓 名 IN OUT
鈴木 花子 2017/3/1 2017/6/6
斉藤 花子 2017/6/6 2017/6/8
斎藤 花子 2017/6/8 9999/12/1
6月8日にシステム上で修正(斉藤=>斎藤) 。事実としては6月6日づけで直したい
hashtag: #ccc_g3
//6月8日にこのコードが実行されるhanako.setLastName(“斎藤"); // アップデート
58
トランザクション時間データモデル
姓 名 IN OUT
鈴木 花子 2017/3/1 2017/6/6
斉藤 花子 2017/6/6 2017/6/8
斎藤 花子 2017/6/8 2017/12/1
12月1日、退職にともない同日に人事情報をシステム上で無効化
hashtag: #ccc_g3
//12月1日にこのコードが実行されるhanako.terminate(); // OUT = NOW
59
トランザクション時間データモデル hashtag: #ccc_g3
Timestamp mar5 = Timestamp.valueOf(LocalDateTime.of(2017, 3, 5, 0, 0));Operation op = EmployeeFinder.firstName().eq("花子")
.and(EmployeeFinder.processingDate().eq(mar5));Employee hanakoMar5 = EmployeeFinder.findOne(op);logger.info("Mar 5: " + hanakoMar5.getFullName());
3月5日時点の花子さんを検索
[main] INFO sample.HelloReladomoApp - Mar 5: 鈴木 花子
select t0.EMPLOYEE_ID,t0.FIRST_NAME,t0.LAST_NAME,t0.IN,t0.OUT from EMPLOYEE t0 where t0.FIRST_NAME = '花子' and t0.IN <= '2017-03-05 00:00:00.000' and t0.OUT > '2017-03-05 00:00:00.000'
60
トランザクション時間データモデル hashtag: #ccc_g3
Timestamp jun7 = Timestamp.valueOf(LocalDateTime.of(2017, 6, 7, 0, 0));Operation op = EmployeeFinder.firstName().eq("花子")
.and(EmployeeFinder.processingDate().eq(jun7));Employee hanakoJum7 = EmployeeFinder.findOne(op);logger.info(”Jun 7: " + hanakoJun7.getFullName());
6月7日時点の花子さんを検索
[main] INFO sample.HelloReladomoApp - June 7: 斉藤 花子
select t0.EMPLOYEE_ID,t0.FIRST_NAME,t0.LAST_NAME,t0.IN,t0.OUT from EMPLOYEE t0 where t0.FIRST_NAME = '花子' and t0.IN <= '2017-06-07 00:00:00.000' and t0.OUT > '2017-06-07 00:00:00.000'
61
トランザクション時間データモデル hashtag: #ccc_g3
Timestamp jun8 = Timestamp.valueOf(LocalDateTime.of(2017, 6, 8, 0, 0));Operation op = EmployeeFinder.firstName().eq("花子")
.and(EmployeeFinder.processingDate().eq(jun8));Employee hanakoJum8 = EmployeeFinder.findOne(op);logger.info(”Jun 8: " + hanakoJun8.getFullName());
6月8日時点の花子さんを検索
[main] INFO sample.HelloReladomoApp - June 8: 斎藤 花子
select t0.EMPLOYEE_ID,t0.FIRST_NAME,t0.LAST_NAME,t0.IN,t0.OUT from EMPLOYEE t0 where t0.FIRST_NAME = '花子' and t0.IN <= '2017-06-08 00:00:00.000' and t0.OUT > '2017-06-08 00:00:00.000'
62
トランザクション時間データモデル
Reladomoのトランザクション時間データ操作
IN/OUTカラムにまつわる処理はユーザーが気にすることなくフレームワークが吸収してくれる
デフォルトで最新のデータが検索される(OUT =
INFINITY)
データをある日付(トランザクション時間)から無効化するにはterminate()を使う
過去の履歴の検索の際はprocessingDate()を明示的に指定してやる
hashtag: #ccc_g3
63
Reladomoでバイテンポラルデータモデルを扱う例
hashtag: #ccc_g3
64
<MithraObject objectType=“transactional”xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”xsi:noNamespaceSchemaLocation=“reladomoobject.xsd”>
<PackageName>sample.domain</PackageName><ClassName>Employee</ClassName><DefaultTable>EMPLOYEE</DefaultTable>
<AsOfAttribute name=“processingDate” fromColumnName=“IN_Z” toColumnName=“OUT_Z”toIsInclusive=“false”isProcessingDate=“true”timezoneConversion=“none”infinityDate=“[com.gs.fw.common.mithra.util.DefaultInfinityTimestamp.getDefaultInfinity()]”defaultIfNotSpecified=“[com.gs.fw.common.mithra.util.DefaultInfinityTimestamp.getDefaultInfinity()]”
/>
<AsOfAttribute name=“businessDate” fromColumnName=“FROM_Z” toColumnName=“THRU_Z”toIsInclusive=“false”isProcessingDate=“false”timezoneConversion=“none”infinityDate=“[com.gs.fw.common.mithra.util.DefaultInfinityTimestamp.getDefaultInfinity()]”futureExpiringRowsExist=“true”
/>
・・・省略・・・</MithraObject>
Employeeエンティティ定義ファイル hashtag: #ccc_g3
有効時間
65
バイテンポラルデータモデル
姓 名 FROM THRU IN OUT
鈴木 花子 2017/4/1 9999/12/1 2017/3/1 9999/12/1
4月1日に「鈴木花子」さん入社。3月1日にシステムに登録
hashtag: #ccc_g3
Timestamp apr1 = Timestamp.valueOf(LocalDateTime.of(2017, 4, 1, 0, 0));Employee hanakoNew = new Employee(apr1); //メモリ上にオブジェクト作成hanakoNew.setLastName("鈴木");hanakoNew.setFirstName("花子");hanakoNew.insert(); // データベースに挿入
66
バイテンポラルデータモデル
6月6日に結婚し、姓が「斎藤」に変更。同日システム上に間違えて「斉藤」と登録
hashtag: #ccc_g3
//まずは6月6日付(有効時間)でフェッチTimestamp jun6 = Timestamp.valueOf(LocalDateTime.of(2017, 6, 6, 0, 0));Operation opJun6 = EmployeeFinder.firstName().eq(“花子”).
and(EmployeeFinder.businessDate().eq(jun6)); //6月6日付でフェッチfinal Employee hanakoJun6 = EmployeeFinder.findOne(opJun6); //遅延ロードされるのでこの時点ではまだフェッチされていない
//6月6日付(有効時間)でフェッチしたオブジェクトをアップデートMithraManagerProvider.getMithraManager().executeTransactionalCommand(tx -> {
//6月6日(トランザクション時間)にこのコードが実行されるhanakoJun6.setLastName("斉藤"); // この時点で遅延フェッチ&アップデートreturn null;
});
*スペースの都合上、以降テーブルは省略
67
バイテンポラルデータモデル
名字の漢字が間違っており、6月8日にシステム上で修正(斉藤=>斎藤)
hashtag: #ccc_g3
//まずは6月6日付(有効時間)でフェッチTimestamp jun6 = Timestamp.valueOf(LocalDateTime.of(2017, 6, 6, 0, 0));Operation opJun6 = EmployeeFinder.firstName().eq(“花子”).
and(EmployeeFinder.businessDate().eq(jun6)); //6月6日付でフェッチfinal Employee hanakoJun6 = EmployeeFinder.findOne(opJun6); //遅延ロードされるのでこの時点ではまだフェッチされていない
//6月6日付(有効時間)でフェッチしたオブジェクトをアップデートMithraManagerProvider.getMithraManager().executeTransactionalCommand(tx -> {
//6月8日(トランザクション時間)にこのコードが実行されるhanakoJun6.setLastName(“斎藤"); // この時点で遅延フェッチ&アップデートreturn null;
});
68
バイテンポラルデータモデル
12月1日、退職にともない同日に人事情報をシステム上で無効化
hashtag: #ccc_g3
//まずは12月1日付(有効時間)でフェッチTimestamp dec1 = Timestamp.valueOf(LocalDateTime.of(2017, 12, 1, 0, 0));Operation opDec1 = EmployeeFinder.firstName().eq(“花子”).
and(EmployeeFinder.businessDate().eq(dec1)); //12月1日付でフェッチfinal Employee hanakoDec1 = EmployeeFinder.findOne(opDec1); //遅延ロードされるのでこの時点ではフェッチされない
//12月1日付(有効時間)でフェッチしたオブジェクトをアップデートMithraManagerProvider.getMithraManager().executeTransactionalCommand(tx -> {
//12月1日(トランザクション時間)にこのコードが実行されるhanakoDec1.terminate(); // THRU = 12/1return null;
});
69
バイテンポラルデータモデル
Reladomoのバイテンポラルデータ操作
オブジェクト作成の際は有効時間の開始時を引数に与える
更新の際は、まず更新したい日付(有効時間)でフェッチする。フェッチしたオブジェクトを更新すると自動でIN/OUT/FROM/THRUが更新・挿入される
データをある日付(有効時間)から無効化するにはterminate()を使う
バイテンポラルデータモデルのテーブル構造を意識することなく処理を記述することが可能
hashtag: #ccc_g3
70
Reladomoを用いたテンポラルデータモデルの操作
ここで挙げた例はReladomoの機能のさわりのみ
つづきはReladomo Kata / Reladomo Tourで
Reladomo Kata GitHub (Reladomo チュートリアル)
Guided Tour of Reladomo
hashtag: #ccc_g3
71
まとめ
RDBで履歴を扱う際にはテンポラルデータモデルを用いると素直に表現できる
トランザクション時間と有効時間を組み合わせたテンポラルデータモデルが存在
Reladomoを使うとテンポラルデータモデルの扱いが抽象化できる
ぜひ、みなさんも試してみてください!
hashtag: #ccc_g3
72
株式会社FOLIOの紹介 hashtag: #ccc_g3
https://www.wantedly.com/companies/folio/projectsエンジニア募集中!
73
APPENDIX
74
テンポラルデータモデル hashtag: #ccc_g3
Temporal Data Models(ppt)
Temporal Databases - Richard T. Snodgrass 1998
Temporal Databases - Richard T. Snodgrass and Ilsoo
Ahn 1986
Temporal and Real-Time Databases: A Survey(ppt)
Temporal Data and The Relational Model
75
Reladomo hashtag: #ccc_g3
Reladomo GitHub
Reladomo Kata GitHub (Reladomo チュートリアル)
Guided Tour of Reladomo
Reladomo Documentations