View
3.552
Download
1
Category
Preview:
Citation preview
Reladomo入門
株式会社FOLIO 伊藤博志
JJUGナイトセミナー 2017.7.26
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
Agenda
1. 自己紹介
2. Reladomoの基本
3. Reladomoのコード生成
4. Reladomoの検索・挿入・更新・削除処理
5. Reladomoの関連
6. GS Collectionsサポート
7. ユニットテストサポート
8. バイテンポラルモデル
2
自己紹介
趣味:ドラム演奏
JavaOneコミュニティバンド
Null Pointersで演奏経験あり(日本人初)
Head of Engineering @ FOLIO
伊藤 博志
Eclipse Collections:共同プロジェクトリード兼コミッターReladomo:コントリビューターOpenJDK:コントリビューターJJUG CCC、Java Day Tokyo、JavaOne San Francisco登壇
2017年5月に株式会社FOLIO入社。
4
Reladomoとは
https://github.com/goldmansachs/reladomo
ゴールドマン・サックス社が2016年9月にGitHubにOSSとして公開したJava ORMフレームワーク
Apache License 2.0
ORMフレームワークであり、オブジェクト指向の徹底
xmlからコード/DDLの自動生成
バイテンポラルデータモデルをネイティブサポート
強力に型付けられたクエリー言語(SQLは書かない)
ユニットテストのフルサポート
etc.
5
Reladomoの哲学
Reladomo Philosophy & Vision
Long Lived Code [LLC]
Don’t Repeat Yourself [DRY]
Agile [AGL]
Domain based Object Oriented paradigm [DOO]
Correctness & Consistency [CC]
7
コード生成:xmlファイル
<MithraObject objectType="transactional"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="reladomoobject.xsd">
<PackageName>sample.domain</PackageName><ClassName>Person</ClassName><DefaultTable>PERSON</DefaultTable>
<Attribute javaType="int" name="personId" columnName="PERSON_ID" primaryKey="true" primaryKeyGeneratorStrategy="Max"/>
<Attribute javaType="String" name="firstName" columnName="FIRST_NAME"nullable="false" maxLength="64"/>
<Attribute javaType="String" name="lastName" columnName="LAST_NAME"nullable="false" maxLength="64"/>
</MithraObject>
16
Finder API
Finderクラスを用いてOperationを生成
型安全に検索
sqlは一切書かない
–検索条件: Operation - Finder APIから作成
–1件検索:Finder.findOne(Operation)
–複数検索:Finder.findMany(Operation)
17
Finder API:一件検索
Operation findTaroOp = PersonFinder.firstName().eq(“太郎");
Person john = PersonFinder.findOne(findTaroOp);
Finder APIを用いてOperationを作成
Finder.findOne()で一件検索結果はEntityオブジェクトで取得
18
Finder API:複数検索
Operation findAllOp = PersonFinder.all();
PersonList people = PersonFinder.findMany(findAllOp);
Finder APIを用いてOperationを作成
Finder.findMany()で複数検索結果はListオブジェクトで取得
19
Finder API:Operationの例1
Operation op1 = PersonFinder.firstName().eq("大輔");// SQL: WHERE first_name = '大輔’
Operation op2 = PersonFinder.lastName().endsWith("藤");// SQL: WHERE last_name LIKE '%藤’
Operation op1OrOp2 = op1.or(op2);// SQL: WHERE (( first_name = '大輔') OR ( last_name LIKE '%藤'))
Operation op1AndOp2 = op1.and(op2);// SQL: WHERE (( first_name = '大輔') AND ( last_name LIKE '%藤'))
20
Finder API:Operationの例2
Operation op3 =PersonFinder.age().in(IntHashSet.newSetWith(22, 24, 30));
// SQL: WHERE age in (22, 24, 30)
Operation op4 =PersonFinder.age().notIn(IntHashSet.newSetWith(22, 25));
// SQL: WHERE age in (22, 25)
Operation op5 =PersonFinder.age().greaterThan(25);
// SQL: WHERE age > 25
その他複雑なクエリを型安全に記述することが可能
22
ReIadomoのキャッシュ
Reladomoは複数のキャッシュ戦略を持つ
–Partial Cache
–Full Cache
–None
ランタイムの設定でエンティティごとにキャッシュ戦略を選択可能
クエリーキャッシュとオブジェクトキャッシュ
キャッシュ機構は高度に最適化されており、データベースへのアクセスを最小に保つことができる
23
Operation findTaroOp = PersonFinder.firstName().eq("太郎");Person taro = PersonFinder.findOne(findTaroOp);
Operation op1 = PersonFinder.firstName().eq("大輔");Operation op2 = PersonFinder.lastName().endsWith("藤"); PersonList people2 = PersonFinder.findMany(op1.or(op2));people2.forceResolve();
Operation op3 = PersonFinder.age().greaterThan(25);PersonList people3 = PersonFinder.findMany(op3);people3.forceResolve();
3種類の条件の違うクエリーは直接DBを叩く
ReIadomoのキャッシュ
24
2017-07-23 18:02:06:881 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person -connection:2005169944 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 where t0.first_name = '太郎’2017-07-23 18:02:06:891 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 1 objects, 83.0 ms per2017-07-23 18:02:06:992 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person -connection:2005169944 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 where (( t0.first_name = '大輔') or ( t0.last_name like '%藤'))2017-07-23 18:02:06:994 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 3 objects, 1.3333333333333333 ms per2017-07-23 18:02:06:997 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person -connection:2005169944 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 where t0.age > 252017-07-23 18:02:07:005 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 1 objects, 8.0 ms per
ReIadomoのキャッシュ
3回のDBアクセス
25
Operation findAllOp = PersonFinder.all();PersonList people = PersonFinder.findMany(findAllOp);people.forceResolve(); //Reladomoは通常遅延ロードするので例示のために強制ロード
Operation findTaroOp =PersonFinder.firstName().eq("太郎");
… 前ページと同じ3つのクエリー
前ページと同じ3種類のクエリーの直前に全選択のクエリーを走らせると…
ReIadomoのキャッシュ
26
2017-07-23 19:34:04:415 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person -connection:112049309 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t02017-07-23 19:34:04:458 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 4 objects, 25.5 ms per
ReIadomoのキャッシュ
1回のDBアクセスで全選択
DBへのIOは全選択クエリーの1回のみ
その後のクエリーはキャッシュから取得
28
Reladomoのトランザクション処理
トランザクション内で行う必要のある処理に関してはラムダ式内に記述しexecuteTransactionalCommand()
にわたす。以下、例によってはこのトランザクションのコードを省略している。
MithraManagerProvider.getMithraManager().executeTransactionalCommand(tx -> {
//検索・挿入・更新・削除処理の記述
return someObject;});
30
Reladomoの挿入処理:単一挿入
Person jiro = new Person("二郎", "山田", 45);
jiro.insert();
Entityインスタンスを作成
Insert()メソッドで挿入
31
PersonList newPeople = new PersonList();
newPeople.add(new Person("二郎", "山田", 45));newPeople.add(new Person("さくら", "鈴木", 28));
newPeople.insertAll();
Reladomoの挿入処理:バッチ挿入
Listインスタンスを作成
新規作成したEntityをListに追加
InsertAll()メソッドで複数挿入
32
Reladomoの挿入処理:オブジェクトの同一性
// 参照を取得後に挿入処理Person jiro = new Person("二郎", "山田", 45);jiro.insert();
// DBから取得Operation op = PersonFinder.firstName().eq(“二郎");Person jiroDb = PersonFinder.findOne(op);
// 作成されたオブジェクトはDBから取得したものと同値かつ同一Assert.assertTrue(jiro.equals(jiroFromDb));Assert.assertTrue(jiro == jiroFromDb);
Reladomoは永続化されたオブジェクトはメモリ上に唯一つのみ存在することを保証する
34
Operation tanakaOp = PersonFinder.lastName().eq("田中");Person tanaka = PersonFinder.findOne(tanakaOp);
tanaka.setAge(25);
Reladomoの更新処理:単一更新
Entityインスタンスを検索
setXxx()メソッドで更新*トランザクション外での更新は即時反映される
35
Reladomoの更新処理:複数更新
複数検索でListオブジェクトを取得
Listに対してsetXxx()メソッドで複数更新
PersonList people = PersonFinder.findMany(PersonFinder.all());
people.setAge(20);
2017-07-23 21:45:08:489 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - multi update of 6 objects with: update person set age = ? where person_id in (?...)2017-07-23 21:45:08:489 [main] DEBUG com.gs.fw.common.mithra.batch.sqllogs.Person - multi updating with: update person set age = 20 where person_id in (0,1,2,3,4,5)
36
Reladomoの更新処理:バッチ更新
Entityインスタンスを検索
トランザクション内での複数更新はバッチ更新される
Person tanaka = PersonFinder.findOne(PersonFinder.lastName().eq("田中"));Person sato = PersonFinder.findOne(PersonFinder.lastName().eq("佐藤"));
MithraManagerProvider.getMithraManager().executeTransactionalCommand(tx -> {tanaka.setAge(25);sato.setAge(23);return null;
});
2017-07-23 21:38:39:403 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - batch update of 2 objects with: update person set age = ? where person_id = ?2017-07-23 21:38:39:404 [main] DEBUG com.gs.fw.common.mithra.batch.sqllogs.Person - batch updating with: update person set age = 25 where person_id = 02017-07-23 21:38:39:405 [main] DEBUG com.gs.fw.common.mithra.batch.sqllogs.Person - batch updating with: update person set age = 23 where person_id = 1
38
Reladomoの削除処理:単一削除
Entityインスタンスを検索
delete()メソッドで削除
Operation op = PersonFinder.lastName().eq("田中");Person tanaka = PersonFinder.findOne(op);
tanaka.delete();
39
Reladomoの削除処理:複数削除
複数検索でListオブジェクトを取得
Listに対してdeleteAll()メソッドで複数削除
Operation op = PersonFinder.lastName().in(Sets.mutable.of(“田中”, “佐藤"));
PersonList people = PersonFinder.findMany(op);
people.deleteAll();
42
Reladomoの関連:Person.xml
<Relationship name="pets"relatedObject="Pet"cardinality="one-to-many"relatedIsDependent="true"reverseRelationshipName="owner">
this.personId = Pet.ownerId</Relationship>
Person PetPetPetpets
owner
43
Reladomoの関連:Pet.xml
<Relationship name="petType"relatedObject="PetType"cardinality="many-to-one">
this.petTypeId = PetType.petTypeId</Relationship>
Pet PetTypepetType
44
Reladomoの関連:Finderによる柔軟な検索1
//犬を飼っている飼い主を取得Operation op =
PersonFinder.pets().petTypeId().eq(PetType.DOG);
Person dogOwner = PersonFinder.findOne(op);
テーブル間のjoinを柔軟に記述
通常の一件検索と同様
45
Reladomoの関連:Finderによる柔軟な検索1
2017-07-24 21:51:55:924 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person -connection:1954406292 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 inner join (select distinct t1.owner_id c0 from pet t1 where t1.pet_type_id = 0) as d1 on t0.person_id = d1.c02017-07-24 21:51:55:977 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 1 objects, 347.0 ms per
生成されるSQL
46
Reladomoの関連:Finderによる柔軟な検索2
reverseRelationshipNameで指定した逆引きのAPI(e.g. owner)も活用可
通常の複数検索と同様
//佐藤さんが飼っているペットを取得Operation op =
PetFinder.owner().lastName().eq("佐藤");
PetList satoPets = PetFinder.findMany(op);
47
Reladomoの関連:Finderによる柔軟な検索2
2017-07-25 07:09:31:305 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - connection:708533063 find with: select t0.pet_id,t0.name,t0.owner_id,t0.age,t0.pet_type_id from pet t0 inner join person t1 on t0.owner_id = t1.person_id where t1.last_name = '佐藤’2017-07-25 07:09:31:316 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - retrieved 2 objects, 49.5 ms per
生成されるSQL
48
Reladomoの関連:deepFetch
Relationshipを解決する際のN+1問題を避けるための機構
//ペット飼っている人を取得Operation op = PersonFinder.pets().exists();
PersonList petOwners = PersonFinder.findMany(op);
petOwners.deepFetch(PersonFinder.pets());petOwners.deepFetch(PersonFinder.pets().petType());
petOwners.forEach(petOwner -> {petOwner.getPets().forEach(pet -> {
System.out.println(petOwner.getLastName() + "さんは" +pet.getName() + "という名の" +pet.getPetType().getPetType() + "を飼っています");
});});
取得したい関連をdeepFetch指定
49
Reladomoの関連:deepFetch
deepFetchを使わない場合に発行されるSQL
2017-07-25 07:23:05:473 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - connection:576020159 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 inner join (select distinct t1.owner_id c0 from pet t1) as d1 on t0.person_id = d1.c02017-07-25 07:23:05:493 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 2 objects, 51.0 ms per2017-07-25 07:23:05:604 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - connection:576020159 find with: select t0.pet_id,t0.name,t0.owner_id,t0.age,t0.pet_type_id from pet t0 where t0.owner_id = 12017-07-25 07:23:05:608 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - retrieved 2 objects, 6.0 ms per2017-07-25 07:23:05:645 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - connection:576020159 find with: select t0.pet_type_id,t0.pet_type from pet_type t0 where t0.pet_type_id = 02017-07-25 07:23:05:647 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - retrieved 1 objects, 3.0 msper2017-07-25 07:23:05:658 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - connection:576020159 find with: select t0.pet_type_id,t0.pet_type from pet_type t0 where t0.pet_type_id = 32017-07-25 07:23:05:658 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - retrieved 1 objects, 2.0 ms per2017-07-25 07:23:05:659 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - connection:576020159 find with: select t0.pet_id,t0.name,t0.owner_id,t0.age,t0.pet_type_id from pet t0 where t0.owner_id = 22017-07-25 07:23:05:660 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - retrieved 2 objects, 0.5 ms per2017-07-25 07:23:05:661 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - connection:576020159 find with: select t0.pet_type_id,t0.pet_type from pet_type t0 where t0.pet_type_id = 12017-07-25 07:23:05:662 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - retrieved 1 objects, 2.0 ms per2017-07-25 07:23:05:663 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - connection:576020159 find with: select t0.pet_type_id,t0.pet_type from pet_type t0 where t0.pet_type_id = 22017-07-25 07:23:05:664 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - retrieved 1 objects, 1.0 ms per
50
Reladomoの関連:deepFetch
deepFetchを使った場合に発行されるSQL
2017-07-25 07:27:16:540 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - connection:587153993 find with: select t0.person_id,t0.first_name,t0.last_name,t0.age from person t0 inner join (select distinct t1.owner_id c0 from pet t1) as d1 on t0.person_id = d1.c02017-07-25 07:27:16:564 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Person - retrieved 2 objects, 58.0 ms per2017-07-25 07:27:16:650 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - connection:587153993 find with: select t0.pet_id,t0.name,t0.owner_id,t0.age,t0.pet_type_id from pet t0 where t0.owner_id in ( 1,2)2017-07-25 07:27:16:652 [main] DEBUG com.gs.fw.common.mithra.sqllogs.Pet - retrieved 4 objects, 1.75 ms per2017-07-25 07:27:16:661 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - connection:587153993 find with: select t0.pet_type_id,t0.pet_type from pet_type t0 where t0.pet_type_id in ( 0,1,2,3)2017-07-25 07:27:16:666 [main] DEBUG com.gs.fw.common.mithra.sqllogs.PetType - retrieved 4 objects, 1.25 ms per
52
GS Collectionsサポート:使用例
データ取得後にインメモリーでさまざまなデータ処理をしたい場合に最適
//苗字を取得List<String> lastNames =
people.asGscList().collect(Person::getLastName);
//猫を飼っている飼い主を取得List<Person> catOwner =
people.asGscList().select(person -> person.hasPet(PetType.CAT));
//PetTypeごとに飼い主をグルーピングMutableListMultimap<PetType, Person> peopleByPetType =
people.asGscList().groupByEach(person -> person.getPets().asGscList().collect(Pet::getPetType));
60
バイテンポラルモデル
詳しくはJJUG CCCのプレゼン資料をごらんください
62
田中さん Petサクラpets
owner
バイテンポラルモデル:例 - 関連も自由自在
田中さん チビpets
owner
田中さん
1月1日時点ではペットを飼っていなかった田中さん
3月1日から犬のチビを飼い始めました
5月1日から猫のサクラを飼い始めました
63
バイテンポラルモデルサポート:初期状態
Operation findTanakaOp = PersonFinder.lastName().eq("田中").and(PersonFinder.businessDate().eq(parse("2017/01/01")));
Person tanaka = PersonFinder.findOne(findTanakaOp);
Assert.assertEquals(0, tanaka.getPets().size());
1月1日時点ではペットを飼っていなかった田中さん
64
バイテンポラルモデルサポート:3/1付で挿入処理
3月1日から犬のチビを飼い始めました
Pet chibi = new Pet(parse("2017/03/01"));chibi.setName("チビ");chibi.setAge(3);chibi.setPetTypeId(PetType.DOG);chibi.setOwner(tanaka);
*このケースでは関連の解決のためsetOwnerの時点でchibiがinsertされます
65
バイテンポラルモデルサポート:5/1付で挿入処理
5月1日から猫のサクラを飼い始めました
Pet sakura = new Pet(parse(“2017/05/01”));sakura.setName(“サクラ");sakura.setAge(5);sakura.setPetTypeId(PetType.CAT);sakura.setOwner(tanaka);
*このケースでは関連の解決のためsetOwnerの時点でsakuraがinsertされます
67
Operation findTanakaAsOf20170201 = PersonFinder.lastName().eq("田中").and(PersonFinder.businessDate()
.eq(parse("2017/02/01")));Person tanakaAsOf20170201 =
PersonFinder.findOne(findTanakaAsOf20170201);
System.out.println(tanakaAsOf20170201.getPets().size()); //0
バイテンポラルモデルサポート:2/1付の検索結果
2月1日付で田中さんはペットの関連を持ちません
68
Operation findTanakaAsOf20170302 =PersonFinder.lastName().eq("田中").and(PersonFinder.businessDate()
.eq(parse("2017/03/02")));Person tanakaAsOf20170302 =
PersonFinder.findOne(findTanakaAsOf20170302);
System.out.println(tanakaAsOf20170302
.getPets()
.asGscList()
.collect(Pet::getName)); //[チビ]
バイテンポラルモデルサポート:3/2付の検索結果
3月2日付で田中さんは「チビ」への関連を持っています
69
Operation findTanakaAsOf20170502 =PersonFinder.lastName().eq("田中").and(PersonFinder.businessDate()
.eq(parse("2017/05/02")));Person tanakaAsOf20170502 =
PersonFinder.findOne(findTanakaAsOf20170502);
System.out.println(tanakaAsOf20170502
.getPets()
.asGscList()
.collect(Pet::getName)); //[チビ, サクラ]
バイテンポラルモデルサポート:5/2付の検索結果
5月2日付で田中さんは「チビ」と「サクラ」への関連を持っています
70
「関連」を絡めたバイテンポラルモデルにおいて実際にデータやクエリーがどう表現されているのか気になる方は懇親会
で伊藤とお話ししましょう
*そのうちブログに書くかも
バイテンポラルモデルサポート:データの表現は割愛
71
まとめ
Reladomoはオブジェクト指向の極みのようなORM
検索はFinderによる型安全なクエリー
挿入・更新・削除処理も一切SQLを書かずにオブジェクト上のAPIで完結
複雑なビジネスロジックもJUnit上で安全にテスト
DBクエリはReladomo / インメモリ処理はGS Collections
(or Eclipse Collections)
テンポラルデータモデルの扱いが非常に容易
72
入門を超えて
Reladomoは非常に高度な機能を持ち合わせたエンタープライズグレードのORMフレームワークです
シャーディングのネイティブサポート
Temp tableを用いたクエリーのサポート
入力集合と出力集合をMulti-threadで比較更新するMulti-
Threaded DB Loader
Off-Heapキャッシュ
73
Reladomoをもっと学ぶには
つづきはReladomo Kata / Reladomo Tourで
Reladomo Kata GitHub (Reladomo チュートリアル)
Guided Tour of Reladomo
Reladomo Kataは、JUnit上でテストをパスしながら学べるトレーニング教材
本資料を学んだ方はKata内のmini-kataで手を動かして試してみることをおススメします
75
Scala関西Summit 2017(9月9日 土)
ScalikeJDBCやSkinny Frameworkでおなじみ
グッドフロー・テクノロジーズ瀬良さんと共同で
Reladomo in Scalaというセッションをします!
お楽しみに。
77
リンク集
データ履歴管理のためのテンポラルデータモデルとReladomoの紹介
Reladomo GitHub
Reladomo Kata GitHub (Reladomo チュートリアル)
Guided Tour of Reladomo
Reladomo Documentations
Recommended