为啥我不能在内存数据库上进行回滚?

Posted

技术标签:

【中文标题】为啥我不能在内存数据库上进行回滚?【英文标题】:Why I can't make rollback on in-memory DB?为什么我不能在内存数据库上进行回滚? 【发布时间】:2019-07-16 12:43:20 【问题描述】:

我有一个测试类来测试我的 DAO 类。理论上,它应该在一个事务中运行每个 before → test → after 的链并在此之后进行回滚,但似乎不是。每次都会创建一个新的 id(123->456 而不是 123->123)。我猜内存数据库(我使用 H2)以这种方式工作,我没有弄错。使用 Postgres 设置,它运行良好。

我已经检查过:

    配置、注释和传播级别 我尝试使用hibernate.connection.autocommit = false HSQLDB

但我没有发现那里有错误。

TransactionSynchronizationManager.isActualTransactionActive() 返回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】:

是什么让您认为事务没有被回滚?

分配了值为 1、2、3 的 ID,尽管已回滚,但 H2 数据库拒绝重用它们。

这里有一个讨论(在 mysql 但类似的行为方面)MySQL AUTO_INCREMENT does not ROLLBACK。

您可以在测试之间重置自动增量值:

Resetting autoincrement in h2

或者您可以简单地更新您的代码以手动设置标识符:

CustomerEntity veronicaCustomer = new CustomerEntity();
veronicaCustomer.setid(1L);
veronicaCustomer.setName("Veronica");

根据这个问题(H2 equivalent to SET IDENTITY_INSERT),应该可以在 H2 中正常工作,没有任何问题。对于其他数据库(例如 SQLServer),您可能需要显式启用身份插入以手动设置身份列上的值。

【讨论】:

以上是关于为啥我不能在内存数据库上进行回滚?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们不能在堆栈上分配动态内存?

ios开发h5页面有一个加载框为啥我在wkwebview上不能弹出来

为啥手机内存够却不能安装,总是安装失败

oracle 提交事务后为啥不能撤销或回滚,不是有撤销段吗

为啥我不能动态分配这个结构字符串的内存?

PHP exec():为啥我可以访问共享内存,但不能访问命名信号量? (错误号 = 13)