Date Tags Java / JPA

使用JTA + hibernate 做持久层,Unitils 做Repository的单元测试。

Test类中的repository是用Unitils 提供的@SpringBean进行注入。 而repository中的entityManager是用@PersistenceContext的方式注入, 这个注入是由spring来管理的,所以repository的实现类中的entityManager 的datasource是属于spring的。

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

然而, Unitils却使用自己带的datasource启动 Junit的测试类, 就是@DataSet和@ExpectedDataSet这两个annotation所进行的db操作的, datasouce是由Unitils从自己的配置文件unitils.properties中读入的。

database.driverClassName=org.hsqldb.jdbcDriver
database.url=jdbc:hsqldb:mem:edc
database.dialect = hsqldb
database.userName=sa
database.password=
database.schemaNames=public

好了,我们的测试代码如下,大概就是运行是这样的, unitils先用DataSet定义的excel文件构造db的原始数据, 然后到了测试体中的对数据库insert一条数据, 然后unitils读入ExpectedDataSet中的数据,再读入 database的整个表,用两者进行比对。

@Test
@DataSet("Question.xls")
@ExpectedDataSet("QuestionAfterInsert.xls")
public void testAddQuestion() throws Exception {
    Question question = new Question();
    // question.setxxx ....
    questionRepository.add(question);
}

这时候问题来了, spring管理的repository实现类在程序结束前没有提交, 所以如果该Entity Bean使用了@ID和自增的strategy的定义的话, 我个人觉得数据库在没有提交的情况下,identity的约束会把整个表给锁住, 所以就会出现 unitils在最后读取整个表进行比对的操作中遇到死锁而定住了。

<id name="id">
    <generated-value strategy="IDENTITY" />
</id>

对这个问题的解决办法就是不要使用strategy="IDENTITY"这种策略, 推荐使用自己定义的唯一key作为primary key.

但在这种情况下,如果在repository的实现方法中加入

em.persist(bean);
em.flush();

结果刚解决的dead lock的问题又再次出现了。 我觉得其中的原因是因为em.flush的时候spring的transaction已经将 bean同步到数据库,但却不能commit而锁住表了, 而unitils在最后读取整个表进行比对的操作中又遇到死锁。

所以只能把两个datasoure结合在一起,将spring的xml中dataSource部分 替换为以下内容,这样就可以用unitils去管理一个dataSource。

<bean id="dataSource" class="org.unitils.database.UnitilsDataSourceFactoryBean" />

不过,以上问题是解决了,但很多锁表的原因都只是个人猜测,要进一步 查看unitils的源代码对于transaction的控制才能知道。

请参考 JPA 使用字符串作为ID