Hi all,
I am having an issue doing a HQL query with a map using entities as keys, when join fetching those keys : the returned map has only 1 entry while there are several !equals() keys in the persistence.
A quick description of the example below :
class Root has a Map<Key, Integer>.
Key has a property on which the business hashcode() and equals() are based.
With 3 key-value pairs in the database, the following HQL query returns a map with only 1 key-value pair :
Code:
from Root f
left join fetch f.map map
join fetch map.index
The SQL looks good (see below), fetching the property of Key.
Without the join fetch on the key ("from Root f left join fetch f.map"), it works, but I want to avoid the n+1 selects.
From what I saw with the debugger, here is what I understood :
- the key entities are Map.put() in the map "hydrated", but before being "materialized"
- as they are join fetched, they are put in the map not as proxies but as real instances of Key
- so their business hashcode & equals are used
- but as they are not materialized, they all are in the same state (no-arg constructor)
=> each key-value pair overwrites the last one as the keys are all the same, and materialisation will only find 1 entry.
Perhaps I made a mistake somewhere, or perhaps join fetching map keys is not supposed to be done (as I used a rather obscure CollectionPropertyNames.COLLECTION_INDEX="index" pseudo-property to refer to the keys in the from).
But I am stuck for a few days now, searching the jira/forums and debugging, so any help is welcome.
Thanks in advance.
Hibernate version: 3.3.1.GA (and 3.2.6.GA)
Mapping documents:Code:
<class name="Root" table="Root">
<id name="id" access="field">
<generator class="native" />
</id>
<map name="map" table="Root_map" access="field">
<key column="root_id" />
<map-key-many-to-many class="Key" column="key_id"/>
<element type="java.lang.Integer" column="value" />
</map>
</class>
<class name="Key" table="Key">
<id name="id" access="field">
<generator class="native" />
</id>
<property name="info" column="info"/>
</class>
SQL (oracle)Code:
CREATE TABLE Root (
id INTEGER
);
CREATE TABLE Root_map (
root_id INTEGER,
key_id integer,
value integer
);
CREATE TABLE Key (
id INTEGER,
info VARCHAR2(4000)
);
insert into Key (info, id) values ('i0', 0);
insert into Key (info, id) values ('i1', 1);
insert into Key (info, id) values ('i2', 2);
insert into Root (id) values (0);
insert into Root_map (root_id, key_id, value) values (0, 0, 0);
insert into Root_map (root_id, key_id, value) values (0, 1, -1);
insert into Root_map (root_id, key_id, value) values (0, 2, -2);
Java POJOCode:
public class Root {
public static final String PROP_MAP = "map";
@SuppressWarnings("unused")
private Long id;
private Map<Key, Integer> map = new HashMap<Key, Integer>();
public Map<Key, Integer> getMap() {
return map;
}
public Long getId() {
return id;
}
}
and
Code:
public class Key {
public static final String PROP_INFO = "info";
private Long id;
private String info;
public Key() {
}
public Key(String info) {
this.info = info;
}
public Long getId() {
return id;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((info == null) ? 0 : info.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Key other = (Key) obj;
if (info == null) {
if (other.info != null)
return false;
}
else if (!info.equals(other.info))
return false;
return true;
}
}
Code between sessionFactory.openSession() and session.close():Code:
Query q = session.createQuery(
"from " + Root.class.getName() + " f "
+ " left join fetch f." + Root.PROP_MAP + " map"
+ " join fetch map." + CollectionPropertyNames.COLLECTION_INDEX
);
List<Root> list = q.list();
// database is initialized by SQL with 3 entries
assertEquals(3, list.get(0).getMap().size());
Full stack trace of any exception that occurs:Code:
junit.framework.AssertionFailedError: expected:<3> but was:<1>
Name and version of the database you are using:Oracle 9.2.0.1.0
The generated SQL (show_sql=true):Code:
SELECT
root0_.id as id3_0_,
key2_.id as id5_1_,
map1_.root_id as root1_0__,
map1_.value as value0__,
map1_.key_id as key3_0__,
key2_.info as info5_1_
FROM
Root root0_,
Root_map map1_,
Key key2_
WHERE
root0_.id=map1_.root_id(+) and
map1_.key_id=key2_.id
Debug level Hibernate log excerpt:Beginning at q.list() :
Code:
17:23:30,763 DEBUG AbstractBatcher:410 - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
17:23:30,763 DEBUG ConnectionManager:444 - opening JDBC connection
17:23:30,779 DEBUG SQL:111 - select root0_.id as id3_0_, key2_.id as id5_1_, map1_.root_id as root1_0__, map1_.value as value0__, map1_.key_id as key3_0__, key2_.info as info5_1_ from Root root0_, Root_map map1_, Key key2_ where root0_.id=map1_.root_id(+) and map1_.key_id=key2_.id
17:23:30,795 DEBUG AbstractBatcher:426 - about to open ResultSet (open ResultSets: 0, globally: 0)
17:23:30,810 DEBUG Loader:1197 - result row: EntityKey[Root#0], EntityKey[Key#0]
17:23:30,857 DEBUG Loader:1008 - found row of collection: [Root.map#0]
17:23:30,920 DEBUG Loader:1197 - result row: EntityKey[Root#0], EntityKey[Key#1]
17:23:30,920 DEBUG Loader:1008 - found row of collection: [Root.map#0]
17:23:30,935 DEBUG Loader:1197 - result row: EntityKey[Root#0], EntityKey[Key#2]
17:23:30,935 DEBUG Loader:1008 - found row of collection: [Root.map#0]
17:23:30,935 DEBUG AbstractBatcher:433 - about to close ResultSet (open ResultSets: 1, globally: 1)
17:23:30,951 DEBUG AbstractBatcher:418 - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
17:23:30,951 DEBUG TwoPhaseLoad:130 - resolving associations for [Root#0]
17:23:30,966 DEBUG TwoPhaseLoad:226 - done materializing entity [Root#0]
17:23:30,982 DEBUG TwoPhaseLoad:130 - resolving associations for [Key#0]
17:23:30,982 DEBUG TwoPhaseLoad:226 - done materializing entity [Key#0]
17:23:30,998 DEBUG TwoPhaseLoad:130 - resolving associations for [Key#1]
17:23:30,998 DEBUG TwoPhaseLoad:226 - done materializing entity [Key#1]
17:23:30,998 DEBUG TwoPhaseLoad:130 - resolving associations for [Key#2]
17:23:31,013 DEBUG TwoPhaseLoad:226 - done materializing entity [Key#2]
17:23:31,013 DEBUG CollectionLoadContext:240 - 1 collections were found in result set for role: Root.map
17:23:31,045 DEBUG CollectionLoadContext:283 - collection fully initialized: [Root.map#0]
17:23:31,045 DEBUG CollectionLoadContext:249 - 1 collections initialized for role: Root.map
17:23:31,060 DEBUG StatefulPersistenceContext:860 - initializing non-lazy collections
17:23:31,060 DEBUG ConnectionManager:325 - transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources!