EBeanのquery cachingで異なるクエリの結果が返される
EBean(6.17.3)にて、クエリーキャッシングを行なったとき、
http://ebean-orm.github.io/docs/features/l2caching/
異なるWHERE句のクエリに対して、キャッシュの値が返される場合があるようです。
いわゆるハッシュ値の衝突が起きているようなのですが、
あまりにも容易に衝突が起きているようなので、回避方法はないでしょうか?
サンプルはPostgreSQLですが、特にDBMLには依存しないと思います。
CREATE TABLE test_table
(
id bigserial NOT NULL,
column_a character varying(10),
column_b character varying(10),
CONSTRAINT test_table_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
ALTER TABLE test_table
OWNER TO postgres;
DBデータは以下です。
id;column_a;column_b
1;"SK08";"320"
2;"SK09";"310"
3;"SK01";"201"
4;"SK11";"101"
再現コードは以下です。
Entityの定義
import javax.persistence.*;
import com.avaje.ebean.Model;
/**
* テストテーブル
*/
@Entity
@Table(name = "test_table")
public class TestTable extends Model {
@Id
@Column(name = "id", columnDefinition = "int8", nullable = false, unique = true)
public Long id;
@Column(name = "column_a", columnDefinition = "varchar(10)", nullable = true)
public String columnA;
@Column(name = "column_b", columnDefinition = "varchar(10)", nullable = true)
public String columnB;
}
検索部分のコードは以下です。
list1とlist2, list3とlist4が同じ値を返します。
(list2の検索時にlist1のキャッシュが返る。list4検索時にlist3のキャッシュが返る)
List<TestTable> list1 =
Ebean.getServer(null)
.find(TestTable.class)
.setUseQueryCache(true)
.where()
.eq("columnA", "SK08")
.eq("columnB", "320")
.findList();
List<TestTable> list2 =
Ebean.getServer(null)
.find(TestTable.class)
.setUseQueryCache(true)
.where()
.eq("columnA", "SK09")
.eq("columnB", "310")
.findList();
List<TestTable> list3 =
Ebean.getServer(null)
.find(TestTable.class)
.setUseQueryCache(true)
.where()
.eq("columnA", "SK01")
.eq("columnB", "201")
.findList();
List<TestTable> list4 =
Ebean.getServer(null)
.find(TestTable.class)
.setUseQueryCache(true)
.where()
.eq("columnA", "SK11")
.eq("columnB", "101")
.findList();
【追記 2016-11-07 16:50】
根本的には、以下のコードでhash1とhash2が同値になるのが原因です(EBeanのクエリーキャッシュのハッシュ値算出アルゴリズムを元に作成)。
int hash1 = 0;
hash1 = hash1 * 31 + "08".hashCode();
hash1 = hash1 * 31 + "20".hashCode();
int hash2 = 0;
hash2 = hash2 * 31 + "09".hashCode();
hash2 = hash2 * 31 + "10".hashCode();
String.hashCode()のコア部分は以下のような実装になっているようですので、char codeと桁ズレの組み合わせで容易にハッシュの衝突が起こるようです。
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
これは(最もキャッシュが有効に機能するはずの)コードテーブルでは普通に想定できる事態だと思います。(ふたつの文字列カラムでレコードが決定する場合)
問題が大きいですね・・・。