EBeanのquery cachingで異なるクエリの結果が返される


EBean（6.17.3）にて、クエリーキャッシングを行なったとき、  
<http://ebean-orm.github.io/docs/features/l2caching/>  
異なるWHERE句のクエリに対して、キャッシュの値が返される場合があるようです。

いわゆるハッシュ値の衝突が起きているようなのですが、  
あまりにも容易に衝突が起きているようなので、回避方法はないでしょうか？

サンプルはPostgreSQLですが、特にDBMLには依存しないと思います。

[code]

    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;
    
[/code]

DBデータは以下です。

[code]

    id;column_a;column_b
    1;"SK08";"320"
    2;"SK09";"310"
    3;"SK01";"201"
    4;"SK11";"101"
    
[/code]

再現コードは以下です。  
Entityの定義

[code]

    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;
    
    }
    
[/code]

検索部分のコードは以下です。  
list1とlist2, list3とlist4が同じ値を返します。  
（list2の検索時にlist1のキャッシュが返る。list4検索時にlist3のキャッシュが返る）

[code]

    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();
    
[/code]

【追記 2016-11-07 16:50】

根本的には、以下のコードでhash1とhash2が同値になるのが原因です（EBeanのクエリーキャッシュのハッシュ値算出アルゴリズムを元に作成）。

[code]

    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();
    
[/code]

String.hashCode()のコア部分は以下のような実装になっているようですので、char
codeと桁ズレの組み合わせで容易にハッシュの衝突が起こるようです。

[code]

    char val[] = value;
    
    for (int i = 0; i < value.length; i++) {
        h = 31 * h + val[i];
    }
    hash = h;
    
[/code]

これは（最もキャッシュが有効に機能するはずの）コードテーブルでは普通に想定できる事態だと思います。（ふたつの文字列カラムでレコードが決定する場合）  
問題が大きいですね･･･。




今回の問題はEBean ORMのissue #869として登録され、Version 9.1.1で解決するようです。  
<https://github.com/ebean-orm/ebean/issues/869>

根本的な原因はハッシュキーを生成する際に31を用いた実装になっていたことです。




今回の問題はEBean ORMのissue #869として登録され、Version 9.1.1で解決するようです。  
<https://github.com/ebean-orm/ebean/issues/869>

根本的な原因はハッシュキーを生成する際に31を用いた実装になっていたことです。

