0

I have the test class which testing my DAO class. In theory, it should run a chain of each before → test → after in one transaction and make rollback after that, but seemingly it is not. Every time creates a new id (123->456 instead of 123->123). I guess that in-memory DBs (I use H2) works this way, and I was not mistaken. With Postgres setup, it works good enough.

I've checked:

  1. configurations, annotations, and propagation levels
  2. I tried to use hibernate.connection.autocommit = false
  3. HSQLDB

But I didn't find a mistake there.

TransactionSynchronizationManager.isActualTransactionActive() returns true.

PersistenceConfig:

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

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

Has anyone else come across something similar?

Dmitriy Popov
  • 2,150
  • 3
  • 25
  • 34
Woods
  • 119
  • 1
  • 12

1 Answers1

1

What makes you think the transaction is not being rolled back?

Ids with values 1,2,3 were allocated and, despite the rollback, the H2 database has simply declined to reuse them.

There's a discussion on that here (in terms of MySQL but similar behaviour) MySQL AUTO_INCREMENT does not ROLLBACK.

You could reset the auto-increment value between tests:

Resetting autoincrement in h2

or you could simply update your code to manually set the identifiers:

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

According to this question (H2 equivalent to SET IDENTITY_INSERT ) that should work in H2 without any issues. With other databases (SQLServer for example ) you may need to explicitly enable identity inserts to manually set a value on an identity column.

Alan Hay
  • 22,665
  • 4
  • 56
  • 110