为什么我不能在内存数据库上进行回滚?
Why I can't make rollback on in-memory DB?
我有测试 class 来测试我的 DAO class。理论上应该运行一个事务中每一个before → test → after的链条,然后回滚,但貌似不是。每次创建一个新的 id(123->456 而不是 123->123)。我猜内存数据库(我使用 H2)就是这样工作的,我没有看错。使用 Postgres 设置,它工作得很好。
我检查过:
- 配置、注释和传播级别
- 我尝试使用
hibernate.connection.autocommit = false
- HSQLDB
但我没有发现错误。
TransactionSynchronizationManager.isActualTransactionActive()
returns true
.
持久性配置:
@Configuration
@ComponentScan("com.beginnercourse.softcomputer")
@PropertySource({"classpath:persistence-postgres.properties"})
@PropertySource({"classpath:persistence-h2.properties"})
@EnableTransactionManagement
public class PersistenceConfig {
@Autowired
private Environment environment;
@Bean
@Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory);
return transactionManager;
}
@Bean
@Profile("postgres")
public DataSource postgresDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(requireNonNull(environment.getProperty("jdbc.postgres.driverClassName")));
dataSource.setUrl(requireNonNull(environment.getProperty("jdbc.postgres.connection_url")));
dataSource.setUsername(requireNonNull(environment.getProperty("jdbc.postgres.username")));
dataSource.setPassword(requireNonNull(environment.getProperty("jdbc.postgres.password")));
return dataSource;
}
@Bean
@Profile("postgres")
public LocalSessionFactoryBean postgresSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(postgresDataSource());
sessionFactory.setPackagesToScan(
new String[]{"com.beginnercourse.softcomputer"});
sessionFactory.setHibernateProperties(postgresAdditionalProperties());
return sessionFactory;
}
private Properties postgresAdditionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", requireNonNull(environment.getProperty("hibernate.postgres.hbm2ddl.auto")));
hibernateProperties.setProperty("hibernate.dialect", requireNonNull(environment.getProperty("hibernate.postgres.dialect")));
hibernateProperties.setProperty("hibernate.show_sql", requireNonNull(environment.getProperty("hibernate.postgres.show_sql")));
hibernateProperties.setProperty("hibernate.default_schema", requireNonNull(environment.getProperty("hibernate.postgres.default_schema")));
// hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", requireNonNull(environment.getProperty("hibernate.cache.use_second_level_cache")));
// hibernateProperties.setProperty("hibernate.cache.use_query_cache", requireNonNull(environment.getProperty("hibernate.cache.use_query_cache")));
return hibernateProperties;
}
@Bean
@Profile("oracle")
public DataSource oracleDataSource() throws NamingException {
return (DataSource) new JndiTemplate().lookup(requireNonNull(environment.getProperty("jdbc.url")));
}
@Bean
@Profile("test")
public LocalSessionFactoryBean testSessionFactory(DataSource dataSource ) {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
// postgresSessionFactory.setDataSource(postgresDataSource());
sessionFactory.setDataSource(dataSource);
sessionFactory.setPackagesToScan(
new String[]{"com.beginnercourse.softcomputer"});
sessionFactory.setHibernateProperties(testAdditionalProperties());
return sessionFactory;
}
@Bean
@Profile("test")
public DataSource h2DataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(requireNonNull(environment.getProperty("jdbc.h2.driverClassName")));
dataSource.setUrl(requireNonNull(environment.getProperty("jdbc.h2.connection_url")));
dataSource.setUsername(requireNonNull(environment.getProperty("jdbc.h2.username")));
dataSource.setPassword(requireNonNull(environment.getProperty("jdbc.h2.password")));
return dataSource;
}
private Properties testAdditionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", requireNonNull(environment.getProperty("hibernate.h2.hbm2ddl.auto")));
hibernateProperties.setProperty("hibernate.dialect", requireNonNull(environment.getProperty("hibernate.h2.dialect")));
hibernateProperties.setProperty("hibernate.show_sql", requireNonNull(environment.getProperty("hibernate.h2.show_sql")));
hibernateProperties.setProperty("hibernate.globally_quoted_identifiers", requireNonNull(environment.getProperty("hibernate.h2.globally_quoted_identifiers")));
hibernateProperties.setProperty("hibernate.connection.autocommit", requireNonNull(environment.getProperty("hibernate.h2.connection.autocommit")));
return hibernateProperties;
}
}
H2 属性
jdbc.h2.driverClassName=org.h2.Driver
jdbc.h2.connection_url=jdbc:h2:mem:e-commerce
jdbc.h2.username=sa
jdbc.h2.password=sa
hibernate.h2.dialect=org.hibernate.dialect.H2Dialect
hibernate.h2.show_sql=false
hibernate.h2.hbm2ddl.auto=update
hibernate.h2.globally_quoted_identifiers=true
hibernate.h2.connection.autocommit = false
TestDaoImpl
@ActiveProfiles(profiles = "test")
//@ActiveProfiles(profiles = "postgres")
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
WebConfig.class,
PersistenceConfig.class,
})
@WebAppConfiguration
@Transactional
@Rollback
public class CustomerDaoImplTest {
@Autowired
private CustomerDao customerDao;
@Before
public void setUp() throws Exception {
CustomerEntity veronicaCustomer = new CustomerEntity();
veronicaCustomer.setName("Veronica");
customerDao.create(veronicaCustomer);
CustomerEntity hannaCustomer = new CustomerEntity();
hannaCustomer.setName("Hanna");
customerDao.create(hannaCustomer);
CustomerEntity ericCustomer = new CustomerEntity();
ericCustomer.setName("Eric");
customerDao.create(ericCustomer);
}
@After
public void tearDown() throws Exception {
customerDao.remove((long) 1);
customerDao.remove((long) 2);
customerDao.remove((long) 3);
}
@Test
public void find_must_return_an_object_by_id() throws NoCustomerWithSuchParametersException {
CustomerEntity customer = new CustomerEntity();
customer.setName("Veronica");
assertEquals(customerDao.find((long) 1).get().getName(), customer.getName());
}
@Test(expected = EntityNotFoundException.class)
public void should_optional_empty() {
assertEquals(customerDao.find((long) 55), Optional.empty());
}
}
有没有其他人遇到过类似的事情?
是什么让您认为事务没有被回滚?
分配了值为 1、2、3 的 ID,尽管回滚,但 H2 数据库只是拒绝重用它们。
这里有一个讨论(根据 MySQL 但类似的行为)MySQL AUTO_INCREMENT does not ROLLBACK。
您可以在测试之间重置 auto-increment 值:
Resetting autoincrement in h2
或者您可以简单地更新您的代码以手动设置标识符:
CustomerEntity veronicaCustomer = new CustomerEntity();
veronicaCustomer.setid(1L);
veronicaCustomer.setName("Veronica");
根据这个问题 (H2 equivalent to SET IDENTITY_INSERT ),应该可以在 H2 中正常工作,没有任何问题。对于其他数据库(例如 SQLServer),您可能需要显式启用标识插入以在标识列上手动设置值。
我有测试 class 来测试我的 DAO class。理论上应该运行一个事务中每一个before → test → after的链条,然后回滚,但貌似不是。每次创建一个新的 id(123->456 而不是 123->123)。我猜内存数据库(我使用 H2)就是这样工作的,我没有看错。使用 Postgres 设置,它工作得很好。
我检查过:
- 配置、注释和传播级别
- 我尝试使用
hibernate.connection.autocommit = false
- HSQLDB
但我没有发现错误。
TransactionSynchronizationManager.isActualTransactionActive()
returns true
.
持久性配置:
@Configuration
@ComponentScan("com.beginnercourse.softcomputer")
@PropertySource({"classpath:persistence-postgres.properties"})
@PropertySource({"classpath:persistence-h2.properties"})
@EnableTransactionManagement
public class PersistenceConfig {
@Autowired
private Environment environment;
@Bean
@Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory);
return transactionManager;
}
@Bean
@Profile("postgres")
public DataSource postgresDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(requireNonNull(environment.getProperty("jdbc.postgres.driverClassName")));
dataSource.setUrl(requireNonNull(environment.getProperty("jdbc.postgres.connection_url")));
dataSource.setUsername(requireNonNull(environment.getProperty("jdbc.postgres.username")));
dataSource.setPassword(requireNonNull(environment.getProperty("jdbc.postgres.password")));
return dataSource;
}
@Bean
@Profile("postgres")
public LocalSessionFactoryBean postgresSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(postgresDataSource());
sessionFactory.setPackagesToScan(
new String[]{"com.beginnercourse.softcomputer"});
sessionFactory.setHibernateProperties(postgresAdditionalProperties());
return sessionFactory;
}
private Properties postgresAdditionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", requireNonNull(environment.getProperty("hibernate.postgres.hbm2ddl.auto")));
hibernateProperties.setProperty("hibernate.dialect", requireNonNull(environment.getProperty("hibernate.postgres.dialect")));
hibernateProperties.setProperty("hibernate.show_sql", requireNonNull(environment.getProperty("hibernate.postgres.show_sql")));
hibernateProperties.setProperty("hibernate.default_schema", requireNonNull(environment.getProperty("hibernate.postgres.default_schema")));
// hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", requireNonNull(environment.getProperty("hibernate.cache.use_second_level_cache")));
// hibernateProperties.setProperty("hibernate.cache.use_query_cache", requireNonNull(environment.getProperty("hibernate.cache.use_query_cache")));
return hibernateProperties;
}
@Bean
@Profile("oracle")
public DataSource oracleDataSource() throws NamingException {
return (DataSource) new JndiTemplate().lookup(requireNonNull(environment.getProperty("jdbc.url")));
}
@Bean
@Profile("test")
public LocalSessionFactoryBean testSessionFactory(DataSource dataSource ) {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
// postgresSessionFactory.setDataSource(postgresDataSource());
sessionFactory.setDataSource(dataSource);
sessionFactory.setPackagesToScan(
new String[]{"com.beginnercourse.softcomputer"});
sessionFactory.setHibernateProperties(testAdditionalProperties());
return sessionFactory;
}
@Bean
@Profile("test")
public DataSource h2DataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(requireNonNull(environment.getProperty("jdbc.h2.driverClassName")));
dataSource.setUrl(requireNonNull(environment.getProperty("jdbc.h2.connection_url")));
dataSource.setUsername(requireNonNull(environment.getProperty("jdbc.h2.username")));
dataSource.setPassword(requireNonNull(environment.getProperty("jdbc.h2.password")));
return dataSource;
}
private Properties testAdditionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", requireNonNull(environment.getProperty("hibernate.h2.hbm2ddl.auto")));
hibernateProperties.setProperty("hibernate.dialect", requireNonNull(environment.getProperty("hibernate.h2.dialect")));
hibernateProperties.setProperty("hibernate.show_sql", requireNonNull(environment.getProperty("hibernate.h2.show_sql")));
hibernateProperties.setProperty("hibernate.globally_quoted_identifiers", requireNonNull(environment.getProperty("hibernate.h2.globally_quoted_identifiers")));
hibernateProperties.setProperty("hibernate.connection.autocommit", requireNonNull(environment.getProperty("hibernate.h2.connection.autocommit")));
return hibernateProperties;
}
}
H2 属性
jdbc.h2.driverClassName=org.h2.Driver
jdbc.h2.connection_url=jdbc:h2:mem:e-commerce
jdbc.h2.username=sa
jdbc.h2.password=sa
hibernate.h2.dialect=org.hibernate.dialect.H2Dialect
hibernate.h2.show_sql=false
hibernate.h2.hbm2ddl.auto=update
hibernate.h2.globally_quoted_identifiers=true
hibernate.h2.connection.autocommit = false
TestDaoImpl
@ActiveProfiles(profiles = "test")
//@ActiveProfiles(profiles = "postgres")
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
WebConfig.class,
PersistenceConfig.class,
})
@WebAppConfiguration
@Transactional
@Rollback
public class CustomerDaoImplTest {
@Autowired
private CustomerDao customerDao;
@Before
public void setUp() throws Exception {
CustomerEntity veronicaCustomer = new CustomerEntity();
veronicaCustomer.setName("Veronica");
customerDao.create(veronicaCustomer);
CustomerEntity hannaCustomer = new CustomerEntity();
hannaCustomer.setName("Hanna");
customerDao.create(hannaCustomer);
CustomerEntity ericCustomer = new CustomerEntity();
ericCustomer.setName("Eric");
customerDao.create(ericCustomer);
}
@After
public void tearDown() throws Exception {
customerDao.remove((long) 1);
customerDao.remove((long) 2);
customerDao.remove((long) 3);
}
@Test
public void find_must_return_an_object_by_id() throws NoCustomerWithSuchParametersException {
CustomerEntity customer = new CustomerEntity();
customer.setName("Veronica");
assertEquals(customerDao.find((long) 1).get().getName(), customer.getName());
}
@Test(expected = EntityNotFoundException.class)
public void should_optional_empty() {
assertEquals(customerDao.find((long) 55), Optional.empty());
}
}
有没有其他人遇到过类似的事情?
是什么让您认为事务没有被回滚?
分配了值为 1、2、3 的 ID,尽管回滚,但 H2 数据库只是拒绝重用它们。
这里有一个讨论(根据 MySQL 但类似的行为)MySQL AUTO_INCREMENT does not ROLLBACK。
您可以在测试之间重置 auto-increment 值:
Resetting autoincrement in h2
或者您可以简单地更新您的代码以手动设置标识符:
CustomerEntity veronicaCustomer = new CustomerEntity();
veronicaCustomer.setid(1L);
veronicaCustomer.setName("Veronica");
根据这个问题 (H2 equivalent to SET IDENTITY_INSERT ),应该可以在 H2 中正常工作,没有任何问题。对于其他数据库(例如 SQLServer),您可能需要显式启用标识插入以在标识列上手动设置值。