With some help on StackOverflow I think I've got to grips with how maps work. I'm posting my current conclusion here for completeness and in case someone in the community has some input.
In summary,
you can't fetch a PersistedMap with a Hibernate query and immediately start using it like a typical Java hash map. The keys are always proxies; eager fetching / joining only fetches the map values, not the keys.
This means any code that deals with
the hash map needs to be wrapped in a Hibernate transaction, which caused me some architectural problems as my data and service layers are separate.
I worked around this by iterating the hash map within a transaction (as described here: http://stackoverflow.com/a/21867936/43662) and replacing the keys with the ones originally passed in. I kept performance up by batching up the keys I want to fetch and retrieving them in one go:
Code:
// Build a list of keys we want to fetch in one go
final List<PkgItem> pkgItems = Arrays.asList(pkgItem1, pkgItem2, ...);
Map<PkgItem, Document> bundles = transactionTemplate.execute(new TransactionCallback< Map<PkgItem, Document> >() {
@Override
public Map<PkgItem, Document> doInTransaction(TransactionStatus transactionStatus) {
if (doc1.getId() == null) return null;
// Merge the parent document into this transaction
Document container = hibernateTemplate.merge(doc1);
// Copy the original package items into the key set
Map<PkgItem, Document> out = new HashMap<PkgItem, Document>();
for (PkgItem dbKey : container.getDocumentbundles().keySet()) {
int keyIndex = pkgItems.indexOf(dbKey);
if (keyIndex > -1) out.put(pkgItems.get(keyIndex), container.getDocumentbundles().get(dbKey));
}
return out;
}
});
// Now we can perform a standard lookup
assertEquals("doc2", result.get(pkgItem1).getName());
I can now use the map without Hibernate in the resulting code, with only a minimal performance hit. I've also updated the test in my example GitHub project to demonstrate how this can work.