最近在使用 Spring 2.0 和 Hibernate 3.2.0 進行開發,在對 DAO 進行單元測試的時候,出現了一些問題,因爲對新環境不太熟悉,折騰了很久才把問題略爲妥善的解決。
程序員喜歡用代碼說話,所以先將測試的相關代碼展示如下:
public class FilterSetDaoTest extends TestCase {
private FilterSetDao filterSetDao;
public void testCreateFilterSet() {
FilterSet filterSet = new FilterSet();
filterSet.setName("test10");
filterSet.setCreateTime(new Date());
Set<Filter> filters = new HashSet<Filter>();
Filter filter = new Filter();
filter.setRule("testrule10");
filter.setType(FilterType.PLAIN);
filter.setCreateTime(new Date());
filters.add(filter);
filterSet.setFilters(filters);
filterSet.setUpdateTime(new Date());
filterSetDao.saveOrUpdate(filterSet);
FilterSet persistedFilterSet = filterSetDao.find(filterSet.getId());
assertEquals(filterSet, persistedFilterSet);
assertEquals(1, persistedFilterSet.getFilters().size());
}
}
public interface FilterSetDao {
public FilterSet find(int id);
}
public class FilterSetDaoHibernateImpl extends HibernateDaoSupport implements FilterSetDao {
public FilterSet find(int id) {
return (FilterSet) getHibernateTemplate().load(FilterSet.class, id);
}
}
@Entity
@Table(name = "filterset")
public class FilterSet implements Serializable {
private Set<Filter> filters;
@ManyToMany(cascade = CascadeType.ALL)
public Set<Filter> getFilters() {
return filters;
}
}
因爲在 Hibernate 中,Session.load() 方法返回的是實體類的一個代理類的實例,而此時因爲沒有合適的事務處理代碼,相應的 session 已經關閉,所以在執行第一條 assertEquals() 方法時,即報告“LazyInitializationException: could not initialize proxy-the owning Session was closed”異常。可以將 DAO 實現中的 load() 換成 get()。
return (FilterSet) getHibernateTemplate().get(FilterSet.class, id);
因爲 get() 是返回的不是代理類的實例,而直接返回實體類的實例,所以上面的異常將不會出現。但是 FilterSet 中的 filters 屬性爲 Set 類型,而在 Hibernate 3 中,默認對所有 Collection 和 Map 都採用 lazy initialization 的方式,因此在這裏,基於上面所說的原因,又會報告“org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: net.patrickhe.FilterSet.filters, no session or session was closed” 這樣的錯誤。所以,將 load() 換成 get() 其實並不是良好的解決辦法。
按照 Spring Reference 上的說明,如果想在事務支持的環境下進行單元測試,可以讓自己的測試用例類繼承 org.springframework.test.annotation.AbstractAnnotationAwareTransactionalTests,如此即可。AbstractAnnotationAwareTransactionalTests 默認會關閉自動提交的特性,在測試方法執行完畢之後即進行回滾操作,以便清除方纔測試時對數據庫造成的修改變更。如果需要將測試數據提交到數據庫中的話,那也很容易,直接調用 繼承而來的 setComplete() 方法即可。關於 AbstractAnnotationAwareTransactionalTests 的更加詳盡說明可以參考 Spring Reference - 8.3.3 Transaction management 一節。
注:在需要事務支持的環境中進行開發,如果使用 MySQL 作爲持久化介質,一定要採用 InnoDB 之類的支持事務管理的存儲引擎,否則一定會出現各種奇怪的邏輯錯誤。