More Related Content Similar to ESFluteによるElasticsearchでのO/Rマッパーを用いた開発 (20) More from Shinsuke Sugaya (11) ESFluteによるElasticsearchでのO/Rマッパーを用いた開発2. DBFluteフェス 2016
■ 名前: 菅谷信介
■ オープンソース活動:
➔ Fess, DBFlute関連, Apache Portals, Codehaus,
Seasar2,...
■ Blog: http://www.chazine.com/
■ Twitter: https://twitter.com/shinsuke_sugaya/
自己紹介
2
8. DBFluteフェス 2016
■ DBFluteのElasticsearch版
➔ CBやBhvが使えます
■ インデックス設定情報からソースの自動生成
■ Elasticsearch 1.7と2.xに対応
➔ 5.x系対応は近日対応予定
➔ バージョンごとにブランチ管理
■ DBFlute本体にバンドル
➔ 将来的には単独の配布にしたい
■ Exampleプロジェクト
https://github.com/lastaflute/lastaflute-example-waterfront
テストコードもあります!
ESFluteとは
8
12. DBFluteフェス 2016
分散環境的な話…
■ 基本的な構成要素
➔ クラスタ
➔ ノード
➔ インデックス
➔ シャード
■ クラスタは複数のノードから構成される
■ クラスタは複数のインデックスを保持できる
■ インデックスは複数のシャードから構成される
■ シャードは1つのプライマリと複数のレプリカで構成
される
■ ノードは複数のシャードを保持できる
構成要素
12
21. DBFluteフェス 2016
■ 文字列: string
■ 数値: long, integer, short, byte, double, float
■ 日付: date
■ 論理値: boolean
■ バイナリ: binary
■ その他: object, geo_point, ip,...
ESFluteではバイナリやその他の型はまだ未対応
基本的な型
21
25. DBFluteフェス 2016
■ 構造的なQuery DSLを利用
■ HTTPまたはTransportでリクエスト
➔現時点ではESFluteはTransportを利用
■ 様々なクエリーをサポート(aggs, geo,...)
$ curl -XPOST ‘localhost:9200/company/_search -d ‘{
“query” : {
"match_phrase" : {
"content" : "fess"
}
},
“size”: 10
}
検索クエリー
25
29. DBFluteフェス 2016
■ dbflute_[project]/dfprop/esfluteMap.dfprop
esfluteMap.dfprop
map:{
# base package of generated classes
; basePackage = org.docksidestage.esflute
# base path to JSON resource, URL or relative path
; basePath = ./playes/index ←インデックスの設定情報ファイルを置いておくディレクトリ
# settings for indexes
; indexMap = map:{
; maihama = map:{ ←インデックス名
; package = maihama
}
}
# version for elasticsearch's jar file (no version means latest)
; elasticsearchVersion = 2.3.0
}
29
31. DBFluteフェス 2016
必要に応じて…
コンポーネント定義
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//DBFLUTE//DTD LastaDi 1.0//EN"
"http://dbflute.org/meta/lastadi10.dtd">
<components>
<include path="esclient.xml"/>
<!-- The components of DBFlute Runtime. -->
<component name="behaviorCommandInvoker" class="org.dbflute.bhv.core.BehaviorCommandInvoker"/>
<!-- The components of Behavior. -->
<component name="userBhv" class="org.codelibs.fess.es.user.exbhv.UserBhv"/>
<component name="roleBhv" class="org.codelibs.fess.es.user.exbhv.RoleBhv"/>
<component name="groupBhv" class="org.codelibs.fess.es.user.exbhv.GroupBhv"/>
</components>
31
33. DBFluteフェス 2016
■ Bhvインスタンスでinsert
➔ オプションで即時反映も可能
➔ 更新も同じ感じで
データの追加
@Resource
private ProductBhv productBhv;
…
Product product = new Product();
product.setProductDescription(form.productDescription);
product.setProductCategoryCode(form.productCategoryCode);
...
productBhv.insert(product, op -> {
op.setRefresh(true); // 即時反映
});
33
34. DBFluteフェス 2016
■ Bhvインスタンスでdelete
➔ オプションで即時反映も可能
➔ Delete By Queryも内部的にScrollで実現
データの削除
productBhv.selectByPK(form.productId).ifPresent(entity -> {
productBhv.delete(entity, op -> {
op.setRefresh(true);
});
}).orElse(() -> {
throw404("Not found the product: " + form.productId);
});
34
37. DBFluteフェス 2016
■ BhvでselectPage, selectList, selectByPK,
selectCount, selectEntity, selectCursorに対応
■ CBで検索条件を指定
➔ pagingやaddOrder〜も対応
■ 戻り値はDBFluteと同様
検索系メソッド
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.query().addOrderBy_Id_Asc();
cb.paging(5, 1);
});
37
38. DBFluteフェス 2016
■ 全件にマッチするクエリー
➔ count等で利用
Match All Query
38
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.query().addOrderBy_Id_Asc();
cb.paging(5, 1);
});
39. DBFluteフェス 2016
■ 解析された文字列にマッチするクエリー
➔ 全文検索で利用
■ 解析された文字列にマッチするので、以下のflute
はFluteにも一致する
➔ フレーズとしてはマッチしない
➔ 日本語bi-gramでは期待通りの結果にならない
Match Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setProductName_Match("flute");
cb.query().addOrderBy_Id_Asc();
cb.paging(5, 1);
});
39
40. DBFluteフェス 2016
■ 解析された文字列のフレーズでマッチする
➔ タームの順番も含めて一致する
➔ 日本語の場合、ほぼこのクエリーを利用する
➔ Match Queryの場合、並び順に関係なく、ヒットする
Match Phrase Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setProductName_MatchPhrase("Low Price Flute");
cb.query().addOrderBy_Id_Asc();
cb.paging(5, 1);
});
40
41. DBFluteフェス 2016
■ フレーズでの前方一致にマッチするクエリー
➔ Match Phraseの前方一致版クエリー
➔ 入力された文字列の最後の単語では前方一致を行う
Match Phrase Prefix Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setProductName_MatchPhrasePrefix("Low P", op -> {
op.maxExpansions(10);
});
cb.query().addOrderBy_Id_Asc();
cb.paging(5, 1);
});
41
42. DBFluteフェス 2016
■ 頻出単語を除外してマッチするクエリー
■ 頻度の指定が可能
➔ 以下の例だと、0.1%より多く登場する単語を除外
Common Terms Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setProductName_CommonTerms("What is Grand Piano", op -> {
op.cutoffFrequency(0.001f);
});
cb.query().addOrderBy_Id_Asc();
cb.paging(5, 1);
});
42
43. DBFluteフェス 2016
■ 検索クエリーを文字列で指定するクエリー
➔ サーチエンジンに利用
➔ Luceneの構文に従う
■ 入力によって幅広い検索が可能
➔ 範囲検索、あいまい検索、正規表現等
Query String Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().queryString("Flute OR Piano", op -> {
op.defaultField("product_name");
});
cb.query().addOrderBy_Id_Asc();
cb.paging(5, 1);
});
43
44. DBFluteフェス 2016
■ 指定された値にマッチするクエリー
➔ テキスト解析はされず、値そのものにマッチする
➔ 区分値など、値のまま、インデックスしたものの検索
➔ not_analyzedのフィールドに対して利用
➔ set〜_EqualはTerm Queryを利用
Term Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setProductHandleCode_Term("FLUTE-01", op -> {
op.queryName("exact_value");
});
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
44
45. DBFluteフェス 2016
■ Term Queryの複数指定版クエリー
➔ 複数の値にマッチする検索をする
➔ set〜_InScopeはTerms Queryを利用
Terms Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
ArrayList<String> terms = new ArrayList<>();
terms.add("piano");
terms.add("flute");
cb.query().setProductName_Terms(terms);
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
45
46. DBFluteフェス 2016
■ 範囲指定検索
➔ 数値や日付を範囲指定する場合に利用する
➔ 通常のCBと同様
Range Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setLatestPurchaseDate_GreaterEqual(
LocalDateTime.of(2014, 1, 1, 0, 0, 0));
cb.query().addOrderBy_LatestPurchaseDate_Asc();
cb.paging(5, 1);
});
46
47. DBFluteフェス 2016
■ フィールドに値が存在するものにマッチする
➔ 値があるものを検索する場合に利用する
■ 存在しないものを検索したい場合
➔ boolのmust_notのexistsクエリーを利用する
■ null値はElasticsearch上で設定可能
Exists Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setProductName_Exists();
cb.query().addOrderBy_ProductName_Asc();
cb.paging(5, 1);
});
47
48. DBFluteフェス 2016
■ 前方一致のクエリー
➔ Term Queryの前方一致検索版
➔ テキスト解析されないフィールドで利用する
Prefix Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setProductCategory_Prefix("Ins");
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
48
49. DBFluteフェス 2016
■ ワイルドカードによる検索
➔ Term Queryのワイルドカード検索版
➔ テキスト解析されないフィールドで利用する
Wildcard Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setProductCategoryCode_Wildcard("H?B");
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
49
50. DBFluteフェス 2016
■ 正規表現による検索
➔ Term Queryで正規表現を利用したい場合に利用する
➔ 遅い
Regexp Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setProductHandleCode_Regexp("[A-Z]{5}-<01-02>");
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
50
51. DBFluteフェス 2016
■ あいまい検索
➔ レーベンシュタイン距離による検索
➔ 類似する単語を見つける
Fuzzy Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setProductCategory_Fuzzy("Insturument");
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
51
52. DBFluteフェス 2016
■ and/or/notを合成するクエリー
➔ and条件: mustで指定する(複数指定可能)
➔ or条件: shouldで指定する(複数指定可能)
➔ not条件: mustNotで指定する(複数指定可能)
■ filter条件も設定可能
➔ and/or/notの前にフィルタ条件を指定する
Bool Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().bool((must, should, mustNot, filter) -> {
must.setProductName_Match("flute");
mustNot.setProductName_Match("gold");
});
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
52
53. DBFluteフェス 2016
■ スコアを調整するクエリー
➔ functionsにマッチしたものをもとにスコアを変える
➔ queryには通常の検索条件を指定する
Function Score Query
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().functionScore(query -> {
query.setProductCategory_Equal("MusicCD");
}, functions -> {
functions.filter(cq -> {
cq.setProductName_Match("street");
}, ScoreFunctionBuilders.weightFactorFunction(10.0f));
}, op -> {});
cb.paging(5, 1);
});
53
56. DBFluteフェス 2016
■ 平均値を集計する
➔ Avgのインスタンスが返却される
Avg Aggregation
56
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setRegularPrice_Avg();
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Avg avg = (Avg) aggregations.get("regular_price");
assertEquals(353455.0, avg.getValue());
57. DBFluteフェス 2016
■ フィールドでユニークな項目数を取得する
➔ Cardinalityが返却される
Cardinality Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setProductCategory_Cardinality();
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Cardinality cardinality = (Cardinality) aggregations.get("product_category");
assertEquals(3, cardinality.getValue());
57
58. DBFluteフェス 2016
■ 様々な統計的数値を得る
➔ ExtendedStatsが返却される
➔ 最大、最小、平均、分散、標準偏差等
Extended Stats Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setRegularPrice_ExtendedStats();
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
ExtendedStats extendedStats = (ExtendedStats) aggregations.get("regular_price");
assertEquals(353455.0, extendedStats.getAvg());
58
59. DBFluteフェス 2016
■ 最大値を取得する
➔ Maxが返却される
Max Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setRegularPrice_Max();
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Max max = (Max) aggregations.get("regular_price");
assertEquals(4000000.0, max.getValue());
59
60. DBFluteフェス 2016
■ 最小値を取得する
➔ Minが返却される
Min Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setRegularPrice_Min();
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Min min = (Min) aggregations.get("regular_price");
assertEquals(340.0, min.getValue());
60
61. DBFluteフェス 2016
■ 百分位数を計算する
➔ 小さい順に並べて、X%にある値を取得する
➔ 外れ値を検出するなど
➔ 以下ではRegularPriceの下位1%の位置に347.6
Percentiles Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setRegularPrice_Percentiles();
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Percentiles percentiles = (Percentiles) aggregations.get("regular_price");
assertEquals(347.6, percentiles.percentile(1));
61
62. DBFluteフェス 2016
■ 百分位数における順位を計算する
➔ 指定した値のランクを取得する
➔ 以下ではRegularPriceが1000のときが23.125%
Percentile Ranks Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setRegularPrice_PercentileRanks(op -> op.percentiles(1000, 10000));
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
PercentileRanks percentileRanks = (PercentileRanks) aggregations.get("regular_price");
assertEquals(23.125, percentileRanks.percent(1000));
62
63. DBFluteフェス 2016
■ 基本的な統計的数値を算出する
➔ Statsが返却される
➔ 項目数、最小値、最大値、平均値、合計値
Stats Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setRegularPrice_Stats();
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Stats stats = (Stats) aggregations.get("regular_price");
assertEquals(353455.0, stats.getAvg());
63
64. DBFluteフェス 2016
■ 合計値を取得する
➔ Sumが返却される
Sum Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setRegularPrice_Sum();
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Sum sum = (Sum) aggregations.get("regular_price");
assertEquals(7069100.0, sum.getValue());
64
65. DBFluteフェス 2016
■ フィールドに値を持つ件数を取得する
➔ ValueCountが返却される
➔ スクリプトによる指定も可能
Value Count Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setProductCategory_Count();
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
ValueCount valueCount = (ValueCount) aggregations.get("product_category");
assertEquals(20, valueCount.getValue());
65
66. DBFluteフェス 2016
■ 指定したフィルタで項目を絞り込む
➔ 子のAggregationをフィルタするときに利用
■
➔
➔
Filter Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().filter("filter", cq -> cq.setProductName_Equal("flute"),
op -> {}, aggs -> aggs.setRegularPrice_Avg());
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Filter filter = (Filter) aggregations.get("filter");
Avg avg = (Avg) filter.getAggregations().get("regular_price");
assertEquals(1516666.6666666667, avg.getValue());
66
67. DBFluteフェス 2016
■ ヒストグラムを生成する
➔ Histogramが返却される
Histogram Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().setRegularPrice_LessThan(2000);
cb.aggregation().setRegularPrice_Histogram(op -> {
op.interval(200).extendedBounds(0L, 2000L);
}, aggs -> {});
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Histogram histogram = (Histogram) aggregations.get("regular_price");
assertEquals(11, histogram.getBuckets().size());
assertEquals(3L, histogram.getBuckets().get(1).getDocCount());
67
68. DBFluteフェス 2016
■ フィールドに値が存在しない項目を得る
➔ Missingが返却される
Missing Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setRegularPrice_Missing();
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Missing missing = (Missing) aggregations.get("regular_price");
assertEquals(0, missing.getDocCount());
68
69. DBFluteフェス 2016
■ フィールド値が範囲内にある件数を取得する
➔ Rangeが返却される
Range Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setRegularPrice_Range(op -> {
op.addUnboundedFrom("all", 0)
.addRange("average", 1000, 5000)
.addUnboundedTo("cheap", 1000);
}, aggs -> {});
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Range range = (Range) aggregations.get("regular_price");
assertEquals(4, range.getBuckets().get(0).getDocCount());
69
70. DBFluteフェス 2016
■ 指定した単語が含まれる件数を取得する
➔ Termsが返却される
➔ 含まれる単語と件数が取得できる
Terms Aggregation
PagingResultBean<Product> list1 = productBhv.selectPage(cb -> {
cb.query().matchAll();
cb.aggregation().setProductName_Terms();
cb.query().addOrderBy_ProductHandleCode_Asc();
cb.paging(5, 1);
});
EsPagingResultBean<Product> list2 = (EsPagingResultBean<Product>) list1;
Aggregations aggregations = list2.getAggregations();
Terms terms = (Terms) aggregations.get("product_name");
List<Bucket> buckets = terms.getBuckets();
assertEquals("100g", buckets.get(0).getKey());
assertEquals(4, buckets.get(0).getDocCount());
70