#parse("./solr/ControlFreeGenSolrJava.vm")
SolrPagingResultBean<SolrGeneral> list = solrGeneralBhv.selectPage(cb -> {
cb.query().dismax("君の", queryField -> {
// queryFieldのvalueはfieldに対する重みを表す
queryField.put(SolrGeneralDbm.Name, null);
queryField.put(SolrGeneralDbm.Kana, 10);
queryField.put(SolrGeneralDbm.NameGeneral, 20);
queryField.put(SolrGeneralDbm.Synonym, null);
});
cb.specify().fieldUid();
cb.paging(10, 2)
});
SolrPagingResultBean<SolrGeneral> list = solrGeneralBhv.selectPage(cb -> {
// qパラメータに登録される
cb.query().dismax("君の", queryField -> {
queryField.put(SolrGeneralDbm.Name, null);
queryField.put(SolrGeneralDbm.Kana, 10);
queryField.put(SolrGeneralDbm.NameGeneral, 20);
queryField.put(SolrGeneralDbm.Synonym, null);
});
cb.specify().fieldUid();
// fqパラメータに登録される
cb.filterQuery().setCategory_Equal(someCategory);
cb.filterQuery().setProductionYear_RangeSearchTo(year);
cb.paging(10, 2)
});
SolrFacetResultBean itemFacetByGenre = solrItemBhv.selectFacetQuery(cb -> {
cb.query().setStatus_Equal("new");
// addFacetQueryで指定した条件をファセットとして検索
cb.addFacetQuery(queryBean -> queryBean.setGenre_Equal("pops"));
cb.addFacetQuery(queryBean -> queryBean.setGenre_Equal("classical"));
cb.addFacetQuery(queryBean -> queryBean.setGenre_Equal("jazz"));
});
// => { "pops": 100, "classical": 40, "jazz": 80 } みたいな結果が返ってくる
SolrFacetResultBean itemFacetByYear = solrItemBhv.selectFacetQuery(cb -> {
// 存在する発売年ごとのファセットとして検索させる
cb.facetSpecify().fieldProductionYear();
});
// => { "2010": 50, "2011": 40, "2012": 20,
// "2013": 30, "2014": 10, "2015": 20, "2016": 30 } とかそんな
#parse("./kvs/ControlFreeGenKvsJava.vm")
#parse("./dbfluteOptional/ControlFreeGenDbfluteOptionalJava.vm")
schemaファイルを作成 (schema毎)
// e.g.) kvs-store-schema-sample.json
{
"sample_schema": {
"$comment": "schema name",
"$type": "table",
"foo": {
"type": "String", "comment": "description for this key", "kvsKey": true, "notNull": true
},
"bar": {
"type": "Integer", "comment": "this is a part of value for associated key", "notNull": true
}
}
}
freeGenMap.dfpropに各schemaに対する定義を追加
; KvsStoreSample = map:{
; resourceMap = map:{
; baseDir = ../src/main
; resourceType = JSON_SCHEMA
; resourceFile = $$baseDir$$/resources/kvs/schema/kvs-store-schema-sample.json
}
; outputMap = map:{
; templateFile = unused
; outputDirectory = $$baseDir$$/java
; package = com.example.kvs.store
; className = unused
}
; tableMap = map:{
; tablePath = map
; schema = sample
; schemaPrefix = Eg
; kvsPoolDiFile = kvs/di/kvs-pool-sample.xml
}
}
schemaファイルを作成 (RDBのschema毎)
// e.g.) kvs-cache-schema-sample.json
{
"target_table": {
"$comment": "description",
"singleColumn": {
"$comment": "description",
"many": false,
"kvsKeys": ["kvsKeyNameForSingle"]
},
"multipleColumns": {
"$comment": "description",
"many": true,
"kvsKeys": ["kvsKeyNameForMultiple"],
"orderBy": ["foo", "bar", "baz"]
}
}
}
freeGenMap.dfpropに各schemaに対する定義を追加
; KvsCacheSample = map:{
; resourceMap = map:{
; baseDir = ../src/main
; resourceType = JSON_SCHEMA
; resourceFile = $$baseDir$$/resources/kvs/schema/kvs-cache-schema-sample.json
}
; outputMap = map:{
; templateFile = unused
; outputDirectory = $$baseDir$$/java
; package = com.example.kvs.cache
; className = unused
}
; tableMap = map:{
; tablePath = map
; schema = sample
; schemaPrefix =
; kvsPoolDiFile = kvs/di/kvs-pool-sample.xml
; dbfluteDiFile = dbflute.xml
; dbflutePackage = com.example.dbflute
; databaseMap = map:{
; sample = map:{
; schemaDir = ./schema
}
}
}
}
littleAdjustmentMap.dfpropにcolumnNullOpjectMapの設定を追加:
対象columnに対して KvsCacheColumnNullObject.getInstance().findColumn() を設定
; columnNullObjectMap = map:{
; providerPackage = org.dbflute.kvs.cache
; isGearedToSpecify = true
; columnMap = map:{
; MEMBER_STATUS = map:{
; DESCRIPTION = KvsCacheColumnNullObject.getInstance().findColumn(this, "$$columnName$$", $$primaryKey$$)
}
; MEMBER_SECURITY = map:{
; REMINDER_ANSWER = KvsCacheColumnNullObject.getInstance().findColumn(this, "$$columnName$$", $$primaryKey$$)
; REMINDER_QUESTION = KvsCacheColumnNullObject.getInstance().findColumn(this, "$$columnName$$", $$primaryKey$$)
; UPDATE_DATETIME = KvsCacheColumnNullObject.getInstance().findColumn(this, "$$columnName$$", $$primaryKey$$)
}
}
}
※これのみfreegenではなく(re)generateを実行
(DBFluteのEntityクラスに組み込まれるため)
// Insert/Update
KvsEgStoreExample insertedEntity = kvsEgStoreExampleBhv.insertOrUpdate(() -> {
// 対象スキーマのEntity
KvsEgStoreExample entity = new KvsEgStoreExample();
entity.setEgId(id);
entity.setEgName(name);
entity.setExpireDatetime(expireDatetime);
return entity;
});
String egkey = insertedEntity.getEgkey();
// Select
OptionalEntity<KvsEgStoreExample> result = kvsEgStoreExampleBhv.selectEntity(cb -> {
// Keyを指定する
cb.acceptPK(egkey);
});
// Delete
kvsEgStoreExampleBhv.delete(() -> {
KvsEgStoreExample entity = new KvsEgStoreExample();
entity.setEgkey(egkey);
return entity;
});
// KvsEgBsStoreExampleBhv.java
public OptionalEntity<KvsEgStoreExample> selectEntity(Consumer<KvsEgStoreExampleCB> cbLambda) {
KvsEgStoreExampleCB cb = new KvsEgStoreExampleCB();
cbLambda.accept(cb);
KvsEgStoreExampleDbm kvsEgstoreExampleDbm = asDBMeta();
kvsEgstoreExampleDbm.validateKeyColumn(cb);
return examplestoreKvsStoreFacade.findEntity(kvsEgstoreExampleDbm, kvsEgstoreExampleDbm.extractKeyList(cb));
}
// AbstractKvsStoreFacade.java (↑の ExamplestoreKvsStoreFacade の抽象クラス)
public <ENTITY extends KvsStoreEntity> OptionalEntity<ENTITY> findEntity(KvsStoreDBMeta kvsStoreDBMeta, List<Object> serchKeyList) {
// Manager -> Delegator と続く
String value = kvsStoreManager.findString(generateKey(kvsStoreDBMeta.getProjectName(), kvsStoreDBMeta.getTableName(), serchKeyList));
// OptionalEntity (DBFluteのOptional拡張実装) として結果を返却
if (value == null) {
return OptionalEntity.empty();
}
return OptionalEntity.of(kvsStoreConverterHandler.toEntity(value, kvsStoreDBMeta));
}
// AbstractKvsRedisDelegator.java (jedisを使ってるのはここ)
public String findString(String key) {
try (Jedis jedis = kvsRedisPool.getResource()) {
return jedis.get(key);
}
}
// Insert/Update
kvsProductBhv.insertOrUpdateByCategoryCode(() -> {
// 対象テーブルのEntity
Product product = new Product();
// 必ずkvsKeysに指定したcolumnに値を登録する
product.setProductCategoryCode(categoryCode);
product.setProductName(productName);
product.setProductHandleCode(productHandleCode);
product.setProductStatusCode_OnSaleProduction();
product.setRegularPrice(price);
// DBFluteの機能としてPKは自動裁判してくれる (明示的に指定しても良い)
return product;
});
// Select (selectList)
List<Product> productList = kvsProductBhv.selectListByCategoryCode(cb -> {
// 必ず (ry
cb.query().setProductCategoryCode_Equal(categoryCode);
cb.query().setProductStatusCode_Equal(statusCode);
cb.query().addOrderBy_RegisterDatetime_Desc();
});
// Delete
kvsProductBhv.deleteByProductId(() -> {
Product product = new Product();
// かならz (ry
product.setProductCategoryCode(categoryCode);
return product;
});
キャッシュが存在する時
キャッシュが存在しない時
// e.g.) KvsBsProductBhv.java
public List<Product> selectListByCategoryCode(Consumer<KvsProductCB> cbLambda) {
KvsProductCB kvsCB = createCB(cbLambda);
ProductCB cb = adjustKvsConditionBeanOfCategoryCode(kvsCB);
// Redisへのクエリを実行する
List<Product> list = maihamadbKvsCacheFacade.findList(createKvsKeyListOfCategoryCode(kvsCB), cb);
Predicate<Product> filter = kvsCB.query().getWherePredicate();
Comparator<Product> sorted = kvsCB.query().getOrderByComparator();
// kvsKeysに指定されてないcolumnに対する検索条件もあるのでfilterする
Stream<Product> stream = list.stream().filter(filter);
if (sorted != null) {
stream = stream.sorted(sorted);
}
if (kvsCB.xgetFetchFirst().isPresent()) {
stream = stream.limit(kvsCB.xgetFetchFirst().get());
}
return stream.collect(Collectors.toList());
}
// AbstractKvsCacheFacade.java (MaihamadbKvsCacheFacade の抽象クラス)
public <ENTITY extends Entity> List<ENTITY> findList(List<Object> searchKeyList, ConditionBean cb) {
return kvsCacheBusinessAssist.findList(mySelector(), cb.asDBMeta().getProjectName(), searchKeyList, cb, cacheTtl());
}
// KvsCacheBusinessAssist.java
public <ENTITY extends Entity> List<ENTITY> findList(BehaviorSelector selector, String dbName, List<Object> searchKeyList,
ConditionBean cb, Integer ttl) {
final String kvsKey = generateKey(dbName, cb.asTableDbName(), searchKeyList);
...
// この先Manager -> Delegator と続いて AbstractKvsRedisDelegator で jedis.lrange(key, 0, -1) している
final List<String> found = kvsCacheManager.findList(kvsKey);
// Redisにキャッシュが存在したらそれをEntityのリストにして返す
if (isNotEmpty(found)) {
List<ENTITY> entityList = kvsCacheConverterHandler.toEntityList(found, cb.asDBMeta());
if (isNotEmpty(entityList)) {
registerThreadCache(kvsKey, entityList);
return entityList;
}
}
// キャッシュが存在しなかったらDBFluteでRDBにクエリを投げる
final List<ENTITY> entityList = (List<ENTITY>) selectList(selector, cb.asDBMeta().getColumnInfoList(), cb);
// Redisにクエリ結果を登録する
asyncManager.async(() -> {
// こちらも Manager -> Delegator と続いて pipeline で del -> rpush -> expireAt と実行している
kvsCacheManager.registerList(kvsKey, kvsCacheConverterHandler.toMapStringList(entityList), calcAvailableDateTime(ttl));
});
return entityList;
}
// e.g.) KvsBsProductBhv.java
public Product insertOrUpdateByCategoryCode(Supplier<Product> entityLambda) {
Product product = entityLambda.get();
maihamadbKvsCacheFacade.insertOrUpdate(createKvsKeyListOfCategoryCode(product), product);
return product;
}
// AbstractKvsCacheFacade.java
public <ENTITY extends Entity> void insertOrUpdate(List<Object> searchKeyList, ENTITY entity) {
kvsCacheBusinessAssist.insertOrUpdate(mySelector(), entity.asDBMeta().getProjectName(), searchKeyList, entity);
}
// KvsCacheBusinessAssist.java
public <ENTITY extends Entity> void insertOrUpdate(BehaviorSelector selector, String dbName, List<Object> searchKeyList, ENTITY entity) {
// RDBに対してUPSERTを実行する。delete の場合はここがdelete (queryDelete) になる
insertOrUpdate(selector, entity);
removeCache(dbName, entity.asTableDbName(), searchKeyList);
}
protected <ENTITY extends Entity> void removeCache(String dbName, String tableDbName, List<Object> searchKeyList) {
final String kvsKey = generateKey(dbName, tableDbName, searchKeyList);
// Insert/Update an entity into(in) KVS asynchronously
asyncManager.async(() -> {
// 最終的に jedis.del(key) を呼び出す
kvsCacheManager.delete(kvsKey);
});
}
キャッシュ処理の挙動はKVS Cacheとほぼ同じ
Redisにキャッシュが存在するならそれを返却し、なければRDBにクエリを投げて結果をRedisに登録た上で返却
; columnNullObjectMap = map:{
; providerPackage = org.dbflute.kvs.cache
; isGearedToSpecify = true
; columnMap = map:{
; MEMBER_STATUS = map:{
; DESCRIPTION = KvsCacheColumnNullObject.getInstance().findColumn(this, "$$columnName$$", $$primaryKey$$)
}
; MEMBER_SECURITY = map:{
; REMINDER_ANSWER = KvsCacheColumnNullObject.getInstance().findColumn(this, "$$columnName$$", $$primaryKey$$)
; REMINDER_QUESTION = KvsCacheColumnNullObject.getInstance().findColumn(this, "$$columnName$$", $$primaryKey$$)
; UPDATE_DATETIME = KvsCacheColumnNullObject.getInstance().findColumn(this, "$$columnName$$", $$primaryKey$$)
}
}
}
public <PROP> PROP findColumn(Entity entity, String columnName, Object primaryKey) {
...
// 対象columnのメタ情報を取得する
final DBMeta dbMeta = entity.asDBMeta();
final Set<ColumnInfo> specifiedColumnInfoSet = dbMeta.getColumnInfoList()
.stream()
.filter(columnInfo -> columnInfo.getPropertyName().equalsIgnoreCase(columnName))
.limit(1)
.collect(Collectors.toSet());
if (specifiedColumnInfoSet.isEmpty()) { specifiedColumnInfoSet.addAll(dbMeta.getColumnInfoList()); }
...
// Redisへクエリの実行; キャッシュが無ければRDBへクエリを投げる
final OptionalEntity<Entity> optCached = getKvsCacheFacade(dbMeta).findEntityById(primaryKey, dbMeta, specifiedColumnInfoSet);
if (!optCached.isPresent()) { return null; }
Entity cached = optCached.get();
...
try {
PROP value = read(columnName, cached);
...
return value;
} finally {
...
}
}
// KvsCacheBusinessAssist.java
public <ENTITY extends Entity> OptionalEntity<ENTITY> findEntityById(Object id, DBMeta dbmeta, Set<ColumnInfo> specifiedColumnInfoSet) {
final List<Object> searchKeyList = new ArrayList<Object>(1);
searchKeyList.add(id);
// RDBへのクエリ用にDBFluteのBehaviorを取得する
final BehaviorReadable readable = behaviorSelector.byName(dbmeta.getTableDbName());
// 複合keyを許容していないのでそれのassert
final PrimaryInfo primaryInfo = dbmeta.getPrimaryInfo();
assertOnlyOnePrimaryKey(primaryInfo);
final ConditionBean cb = readable.newConditionBean();
final Map<String, Object> primaryKeyMap = new HashMap<String, Object>(1);
primaryKeyMap.put(primaryInfo.getFirstColumn().getColumnDbName(), id);
cb.acceptPrimaryKeyMap(primaryKeyMap);
// クエリを実行して結果を返却
return kvsCacheBusinessAssist.findEntity(mySelector(), cb.asDBMeta().getProjectName(), searchKeyList, cb, cacheTtl(),
specifiedColumnInfoSet);
}
https://connpass.com/event/43457/
LTで一足先に紹介しました