更新:我创建了example on GitHub来证明我的问题;由于HashMap密钥是代理对象,HibernateMapTest当前失败。我希望有人能提出一种方法,我可以查询实体并获取地图,以便测试通过......
我只是想获取Hibernate中持久化的HashMap的内容,但是我找不到正确的方法来做这件事......
HBM映射如下,我没有创建它,但是从我的研究中看来它似乎是一个具有多对多关系的ternary association mapping。 (更新:为了简化我的问题,我强迫地图懒惰=“假”以避免我加入):
<hibernate-mapping>
<class name="database.Document" table="document">
...
<map name="documentbundles" table="document_bundles" lazy="false">
<key column="id"/>
<index-many-to-many column="pkgitemid" class="database.PkgItem"/>
<many-to-many column="child" class="database.Document" />
</map>
</class>
</hibernate-mapping>
为简单起见,我目前只是尝试使用填充的地图数据来获取所有记录:
DetachedCriteria criteria = DetachedCriteria.forClass(Document.class);
criteria.add(Restrictions.eq("id", 1));
List<Document> result = hibernateTemplate.findByCriteria(criteria);
最后为假,我现在得到地图的内容而不抛出LazyInitializationException
;但没有一个关键对象已正确初始化。我翻了一个截图来澄清我的意思:
我知道这些字段填充在数据库中,我怀疑我的提取策略仍然是罪魁祸首。你如何正确地在Hibernate中获取<map>
?
答案 0 :(得分:3)
错误是由于HibernateTemplate
打开Hibernate会话来执行此查询:
List results = hibernateTemplate.find("from database.Document d where d.name = 'doc1'");
然后在运行查询后立即关闭会话。然后,当循环键时,映射链接到的会话将关闭,因此无法再加载数据,导致代理抛出LazyInitializationException
。
此异常意味着代理无法再透明地加载数据,因为链接到的会话现在也已关闭。
HibernateTemplate
的主要目标之一是知道何时打开和关闭会话。如果正在进行交易,模板将使会话保持打开状态。
所以这里的关键是将单元测试包装在TransactionTemplate
(相当于@Transactional
的模板)中,这会导致会话由HibernateTemplate
保持打开状态。由于会话保持打开状态,因此不再发生延迟初始化异常。
像这样修改测试将解决问题(请注意使用TransactionTemplate
):
import database.Document;
import database.PkgItem;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
public class HibernateMapTest {
private static final String TEST_DIALECT = "org.hibernate.dialect.HSQLDialect";
private static final String TEST_DRIVER = "org.hsqldb.jdbcDriver";
private static final String TEST_URL = "jdbc:hsqldb:mem:adportal";
private static final String TEST_USER = "sa";
private static final String TEST_PASSWORD = "";
private HibernateTemplate hibernateTemplate;
private TransactionTemplate transactionTemplate;
@Before
public void setUp() throws Exception {
hibernateTemplate = new HibernateTemplate();
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.getHibernateProperties().put("hibernate.dialect", TEST_DIALECT);
sessionFactory.getHibernateProperties().put("hibernate.connection.driver_class", TEST_DRIVER);
sessionFactory.getHibernateProperties().put("hibernate.connection.password", TEST_PASSWORD);
sessionFactory.getHibernateProperties().put("hibernate.connection.url", TEST_URL);
sessionFactory.getHibernateProperties().put("hibernate.connection.username", TEST_USER);
sessionFactory.getHibernateProperties().put("hibernate.hbm2ddl.auto", "create");
sessionFactory.getHibernateProperties().put("hibernate.show_sql", "true");
sessionFactory.getHibernateProperties().put("hibernate.jdbc.batch_size", "0");
sessionFactory.getHibernateProperties().put("hibernate.cache.use_second_level_cache", "false");
sessionFactory.setMappingDirectoryLocations(new Resource[]{new ClassPathResource("database")});
sessionFactory.afterPropertiesSet();
hibernateTemplate.setSessionFactory(sessionFactory.getObject());
transactionTemplate = new TransactionTemplate(new HibernateTransactionManager(sessionFactory.getObject()));
}
@After
public void tearDown() throws Exception {
hibernateTemplate.getSessionFactory().close();
}
@Test
public void testFetchEntityWithMap() throws Exception {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
// Store the entities and mapping
PkgItem key = new PkgItem();
key.setName("pkgitem1");
hibernateTemplate.persist(key);
Document doc2 = new Document();
doc2.setName("doc2");
hibernateTemplate.persist(doc2);
Document doc1 = new Document();
doc1.setName("doc1");
HashMap<PkgItem, Document> documentbundles = new HashMap<PkgItem, Document>();
documentbundles.put(key, doc2);
doc1.setDocumentbundles(documentbundles);
hibernateTemplate.persist(doc1);
// Now attempt a query
List results = hibernateTemplate.find("from database.Document d where d.name = 'doc1'");
Document result = (Document)results.get(0);
// Check the doc was returned
Assert.assertEquals("doc1", result.getName());
key = (PkgItem)hibernateTemplate.find("from database.PkgItem").get(0);
Set<PkgItem> bundleKeys = result.getDocumentbundles().keySet();
// Check the key is still present in the map. At this point the test fails because
// the map contains a proxy object of the key...
Assert.assertEquals(key, bundleKeys.iterator().next());
}
});
}
}
这些是测试结果和更改后的日志:
答案 1 :(得分:1)
这是对jhadesdev答案的补充,因为我需要做更多的工作才能得到我想要的东西。
总之,您无法使用Hibernate查询获取PersistedMap,并立即像典型的Java哈希映射一样开始使用它。密钥始终是代理;渴望获取/加入只获取地图值,而不是键。
这意味着任何处理哈希映射的代码都需要包装在Hibernate事务中,这会导致一些架构问题,因为我的数据和服务层是分开的。
我通过在单个事务中迭代哈希映射并将密钥替换为最初传入的密钥来解决这个问题。我通过批量处理我想要获取的密钥并一次性检索它来保持性能提升:
// 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());
我现在可以在结果代码中使用没有Hibernate的地图,只有最小的性能影响。我还在我的示例GitHub项目中更新了测试,以演示它是如何工作的。