我想用JUnit和H2来测试Spring支持的JPA/Hibernate DAO。我有一个@Before
带注释的初始化方法,它将加载一个SQL文件并为每个测试创建一个基础数据集。事务的设置使得在每次测试之后,它都会回滚并重新开始。因此,这个基础数据集是为每个单独的测试创建的,然后再回滚。Spring + JUnit4 + JPA/Hibernate - 事务隔离怪异?
这一切都很好,除了我看到奇特的约束。我对所有这些技术都很陌生,所以也许我只是忽略了一些东西。我希望能够测试独特的约束在某些值上按预期工作。
首先,init()
方法:
@Before
public void init() throws IOException {
// Setup default data
Query query = em.createNativeQuery(getSqlFromFile());
query.executeUpdate();
}
现在的测试方法证明问题:
public void testSaveExistingNonUniqueUsername() {
// EXISTING_USER_ID added in @Before annotated init() method
User existingUser = testDao.get(EXISTING_USER_ID);
// Set to a non-unique username also added in @Before annotated init() method
existingUser.setUsername(SECOND_EXISTING_USER);
// Save. Here I would expect an exception because of the unique constraint violation. None. Save method simply calls EntityManager.persist()
testDao.save(existingUser);
Long count = testDao.countByUsername(SECOND_EXISTING_USER);
// Count method still returns 1
assertEquals(Long.valueOf(1), count);
// Re-load the user
User savedUser = testDao.get(EXISTING_USER_ID);
// Fails. Username is set to the non-unique value after re-loading, even though the count returned 1, it appears we have two with the same username
assertEquals(savedUser.getUsername(), EXISTING_USER);
}
我加载的现有用户,更改用户名的非唯一值,然后保存。
这里是我的用户实体:
@Entity
@Table(name="users")
public class User implements DomainObject {
@Id
@GeneratedValue
private Long id;
@Column(updatable = false, nullable = false, length=25, unique = true)
private String username;
@Column(nullable = false, length=50)
private String password;
@Column(nullable = false)
private boolean enabled = true;
@ManyToOne(optional = false)
@JoinColumn(name="role", referencedColumnName="name")
private UserRole role;
@OneToOne(optional = false, orphanRemoval = true, cascade=CascadeType.ALL)
@JoinColumn(name="contact_details_id")
private UserContactDetail contactDetails;
// .... getters and setters omitted .....
}
你会注意到,我也有可更新和可空设置为false,用户名,但它仍然可以让我做出改变。
测试类本身标注有以下几点:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "file:src/test/resources/spring/spring-test-master.xml" })
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional(propagation=Propagation.REQUIRED)
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class })
它使用我的生产配置,但我重写数据源豆与H2的数据源。这完全是香草,没什么特别的。
该测试方法演示了在init()
方法中加载的数据是可访问的,因为ID存在并且实体加载。但是,这个独特的约束在这个事务中似乎不起作用。
然而,在下面的测试中,他们这样做:
@Test(expected=DataIntegrityViolationException.class)
public void testSaveExistingNonUniqueUsername() {
// getValidTestUser() just creates a new User() and fills it in with valid data. No ID is set.
User firstUser = getValidTestUser();
testDao.save(firstUser);
// secondUser will be identical to the first user, but will have a different ID when saved
User secondUser = getValidTestUser();
// This DOES throw an exception
testDao.save(secondUser);
}
我希望我只是忽视的东西简单。任何帮助或解释为什么这可能会发生,将不胜感激。
DB连接配置:
# configure h2 data source
jdbc.url=jdbc\:h2\:mem\:junitTest;DB_CLOSE_DELAY\=-1
jdbc.user=sa
jdbc.pass=
jdbc.driver=org.h2.Driver
# configure hibernate specific properties
hibernate.hbm2ddl.auto=update
hibernate.show_sql=true
hibernate.cache.provider_class=net.sf.ehcache.hibernate.SingletonEhCacheProvider
hibernate.dialect=org.hibernate.dialect.H2Dialect
Spring配置:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="${jdbc.driver}"
p:url="${jdbc.url}"
p:username="${jdbc.user}"
p:password="${jdbc.pass}"/>
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
<util:properties id="jpaProperties">
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.cache.provider_class">${hibernate.cache.provider_class}</prop>
</util:properties>
<bean id="defaultLobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler" />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource"
p:persistenceUnitName="PersistenceUnit"
p:jpaDialect-ref="jpaDialect"
p:jpaVendorAdapter-ref="jpaVendorAdapter"
p:jpaProperties-ref="jpaProperties" />
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/>
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
的persistence.xml:
<persistence-unit name="PersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
</persistence-unit>
这很有道理,除了在演示测试用例中,我通过ID重新加载实体并且用户名看起来已经更新。我在这里遇到某种缓存问题,它重新使用同一个实体吗? – Jason
根据@Id问题,这是针对生产中的MySQL运行的。我的理解(尽管这可能是我一直采用的误解)是数字ID比MySQL中的字符串快得多,所以我倾向于使用它们。 – Jason
谢谢亚历克斯。经过一些阅读后,我看到了这个问题。我没有意识到同一个实体实例是在同一个上下文中返回的。 – Jason