2011-09-03 77 views
0

我想用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> 

回答

2

在您的用戶域實體中,您已將用戶名字段標記爲可更新的false。因此,hibernate不會在任何更新語句中包含該字段。所以改變用戶名和調用保存不會改變任何東西。

由於用戶名是該表的關鍵,爲什麼不把它用作@Id?

+0

這很有道理,除了在演示測試用例中,我通過ID重新加載實體並且用戶名看起來已經更新。我在這裏遇到某種緩存問題,它重新使用同一個實體嗎? – Jason

+0

根據@Id問題,這是針對生產中的MySQL運行的。我的理解(儘管這可能是我一直採用的誤解)是數字ID比MySQL中的字符串快得多,所以我傾向於使用它們。 – Jason

+0

謝謝亞歷克斯。經過一些閱讀後,我看到了這個問題。我沒有意識到同一個實體實例是在同一個上下文中返回的。 – Jason