Spring + JUnit4 + JPA / Hibernate - Странность изоляции транзакций? - PullRequest
0 голосов
/ 03 сентября 2011

Я пытаюсь протестировать JPA / Hibernate DAO на основе Spring, используя JUnit и H2.У меня есть @Before аннотированный метод инициализации, который загружает файл SQL и создает базовый набор данных для каждого теста.Транзакции настраиваются таким образом, чтобы после каждого теста он откатывался и начинался снова.Таким образом, этот базовый набор данных создается для каждого отдельного теста, а затем откатывается.

Все это прекрасно работает, за исключением того, что я вижу странность с уникальными ограничениями.Я довольно новичок во всех этих технологиях, так что, может быть, я просто что-то упускаю.Я хотел бы иметь возможность проверить, что уникальные ограничения работают должным образом для определенных значений.

Во-первых, метод 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); 
}

Я загружаю существующего пользователя, меняю имя пользователя на неуникальное значение, а затем сохраняю.

Вот мой объект 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 для имени пользователя, но оно все же позволяет мне вносить изменения.

Сам класс Test помечается следующим текстом:

@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(), доступны, так как присутствуют идентификаторы и загружаются объекты.Однако уникальные ограничения, похоже, не работают в этой транзакции.

Однако в следующем тесте они работают:

@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);
}

Я надеюсь, что просто что-то пропускаюпросто.Будем благодарны за любую помощь или объяснение того, почему это может произойти.

Конфигурация подключения к БД:

# 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

Конфигурация пружины:

<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>

1 Ответ

2 голосов
/ 03 сентября 2011

В вашем домене пользователя вы отметили поле имени пользователя как обновляемое значение false. Поэтому hibernate не будет включать это поле ни в какие операторы обновления. Таким образом, изменение имени пользователя и сохранение вызова ничего не изменит.

Поскольку имя пользователя является ключом для этой таблицы, почему бы не использовать его в качестве @Id?

...