Merge pull request #7947 from maryarm/BAEL-3228

#BAEL-3228: Unit tests for Transaction Propagation and Isolation in Spring
This commit is contained in:
Eric Martin
2019-10-20 10:35:18 -05:00
committed by GitHub
2 changed files with 398 additions and 0 deletions

View File

@@ -0,0 +1,250 @@
package com.baeldung.persistence.service.transactional;
import com.baeldung.persistence.model.Foo;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { PersistenceTransactionalTestConfig.class }, loader = AnnotationConfigContextLoader.class)
@DirtiesContext
public class FooTransactionalUnitTest {
static abstract class BasicFooDao {
@PersistenceContext private EntityManager entityManager;
public Foo findOne(final long id) {
return entityManager.find(Foo.class, id);
}
public Foo create(final Foo entity) {
entityManager.persist(entity);
return entity;
}
}
@Repository
static class RequiredTransactionalFooDao extends BasicFooDao {
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Foo create(Foo entity) {
return super.create(entity);
}
}
@Repository
static class RequiresNewTransactionalFooDao extends BasicFooDao {
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Foo create(Foo entity) {
return super.create(entity);
}
}
@Repository
static class SupportTransactionalFooDao extends BasicFooDao {
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public Foo create(Foo entity) {
return super.create(entity);
}
}
@Repository
static class MandatoryTransactionalFooDao extends BasicFooDao {
@Override
@Transactional(propagation = Propagation.MANDATORY)
public Foo create(Foo entity) {
return super.create(entity);
}
}
@Repository
static class SupportTransactionalFooService {
@Transactional(propagation = Propagation.SUPPORTS)
public Foo identity(Foo entity) {
return entity;
}
}
@Service
static class MandatoryTransactionalFooService {
@Transactional(propagation = Propagation.MANDATORY)
public Foo identity(Foo entity) {
return entity;
}
}
@Service
static class NotSupportedTransactionalFooService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public Foo identity(Foo entity) {
return entity;
}
}
@Service
static class NeverTransactionalFooService {
@Transactional(propagation = Propagation.NEVER)
public Foo identity(Foo entity) {
return entity;
}
}
@Autowired private TransactionTemplate transactionTemplate;
@Autowired private RequiredTransactionalFooDao requiredTransactionalFooDao;
@Autowired private RequiresNewTransactionalFooDao requiresNewTransactionalFooDao;
@Autowired private SupportTransactionalFooDao supportTransactionalFooDao;
@Autowired private MandatoryTransactionalFooDao mandatoryTransactionalFooDao;
@Autowired private MandatoryTransactionalFooService mandatoryTransactionalFooService;
@Autowired private NeverTransactionalFooService neverTransactionalFooService;
@Autowired private NotSupportedTransactionalFooService notSupportedTransactionalFooService;
@Autowired private SupportTransactionalFooService supportTransactionalFooService;
@After
public void tearDown(){
PersistenceTransactionalTestConfig.clearSpy();
}
@Test
public void givenRequiredWithNoActiveTransaction_whenCallCreate_thenExpect1NewAnd0Suspend() {
requiredTransactionalFooDao.create(new Foo("baeldung"));
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(0, transactionSpy.getSuspend());
Assert.assertEquals(1, transactionSpy.getCreate());
}
@Test
public void givenRequiresNewWithNoActiveTransaction_whenCallCreate_thenExpect1NewAnd0Suspend() {
requiresNewTransactionalFooDao.create(new Foo("baeldung"));
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(0, transactionSpy.getSuspend());
Assert.assertEquals(1, transactionSpy.getCreate());
}
@Test
public void givenSupportWithNoActiveTransaction_whenCallService_thenExpect0NewAnd0Suspend() {
supportTransactionalFooService.identity(new Foo("baeldung"));
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(0, transactionSpy.getSuspend());
Assert.assertEquals(0, transactionSpy.getCreate());
}
@Test(expected = IllegalTransactionStateException.class)
public void givenMandatoryWithNoActiveTransaction_whenCallService_thenExpectIllegalTransactionStateExceptionWith0NewAnd0Suspend() {
mandatoryTransactionalFooService.identity(new Foo("baeldung"));
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(0, transactionSpy.getSuspend());
Assert.assertEquals(0, transactionSpy.getCreate());
}
@Test
public void givenNotSupportWithNoActiveTransaction_whenCallService_thenExpect0NewAnd0Suspend() {
notSupportedTransactionalFooService.identity(new Foo("baeldung"));
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(0, transactionSpy.getSuspend());
Assert.assertEquals(0, transactionSpy.getCreate());
}
@Test
public void givenNeverWithNoActiveTransaction_whenCallService_thenExpect0NewAnd0Suspend() {
neverTransactionalFooService.identity(new Foo("baeldung"));
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(0, transactionSpy.getSuspend());
Assert.assertEquals(0, transactionSpy.getCreate());
}
@Test
public void givenRequiredWithActiveTransaction_whenCallCreate_thenExpect0NewAnd0Suspend() {
transactionTemplate.execute(status -> {
Foo foo = new Foo("baeldung");
return requiredTransactionalFooDao.create(foo);
});
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(0, transactionSpy.getSuspend());
Assert.assertEquals(1, transactionSpy.getCreate());
}
@Test
public void givenRequiresNewWithActiveTransaction_whenCallCreate_thenExpect1NewAnd1Suspend() {
transactionTemplate.execute(status -> {
Foo foo = new Foo("baeldung");
return requiresNewTransactionalFooDao.create(foo);
});
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(1, transactionSpy.getSuspend());
Assert.assertEquals(2, transactionSpy.getCreate());
}
@Test
public void givenSupportWithActiveTransaction_whenCallCreate_thenExpect0NewAnd0Suspend() {
transactionTemplate.execute(status -> {
Foo foo = new Foo("baeldung");
return supportTransactionalFooDao.create(foo);
});
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(0, transactionSpy.getSuspend());
Assert.assertEquals(1, transactionSpy.getCreate());
}
@Test
public void givenMandatoryWithActiveTransaction_whenCallCreate_thenExpect0NewAnd0Suspend() {
transactionTemplate.execute(status -> {
Foo foo = new Foo("baeldung");
return mandatoryTransactionalFooDao.create(foo);
});
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(0, transactionSpy.getSuspend());
Assert.assertEquals(1, transactionSpy.getCreate());
}
@Test
public void givenNotSupportWithActiveTransaction_whenCallCreate_thenExpect0NewAnd1Suspend() {
transactionTemplate.execute(status -> {
Foo foo = new Foo("baeldung");
return notSupportedTransactionalFooService.identity(foo);
});
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(1, transactionSpy.getSuspend());
Assert.assertEquals(1, transactionSpy.getCreate());
}
@Test(expected = IllegalTransactionStateException.class)
public void givenNeverWithActiveTransaction_whenCallCreate_thenExpectIllegalTransactionStateExceptionWith0NewAnd0Suspend() {
transactionTemplate.execute(status -> {
Foo foo = new Foo("baeldung");
return neverTransactionalFooService.identity(foo);
});
PersistenceTransactionalTestConfig.TransactionSynchronizationAdapterSpy transactionSpy = PersistenceTransactionalTestConfig.getSpy();
Assert.assertEquals(0, transactionSpy.getSuspend());
Assert.assertEquals(1, transactionSpy.getCreate());
}
}

View File

@@ -0,0 +1,148 @@
package com.baeldung.persistence.service.transactional;
import com.google.common.base.Preconditions;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
@Configuration
@EnableTransactionManagement
@PropertySource({ "classpath:persistence-h2.properties" })
@ComponentScan({ "com.baeldung.persistence","com.baeldung.jpa.dao" })
@EnableJpaRepositories(basePackages = "com.baeldung.jpa.dao")
public class PersistenceTransactionalTestConfig {
public static class TransactionSynchronizationAdapterSpy extends TransactionSynchronizationAdapter {
private int create, suspend;
public int getSuspend() {
return suspend;
}
public int getCreate() {
return create;
}
public void create() {
create++;
}
@Override
public void suspend() {
suspend++;
super.suspend();
}
}
public static class JpaTransactionManagerSpy extends JpaTransactionManager {
@Override
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
super.prepareSynchronization(status, definition);
if (status.isNewTransaction()) {
if ( adapterSpyThreadLocal.get() == null ){
TransactionSynchronizationAdapterSpy spy = new TransactionSynchronizationAdapterSpy();
TransactionSynchronizationManager.registerSynchronization(spy);
adapterSpyThreadLocal.set(spy);
}
adapterSpyThreadLocal.get().create();
}
}
}
private static ThreadLocal<TransactionSynchronizationAdapterSpy> adapterSpyThreadLocal = new ThreadLocal<>();
@Autowired
private Environment env;
public PersistenceTransactionalTestConfig() {
super();
}
public static TransactionSynchronizationAdapterSpy getSpy(){
if ( adapterSpyThreadLocal.get() == null )
return new TransactionSynchronizationAdapterSpy();
return adapterSpyThreadLocal.get();
}
public static void clearSpy(){
adapterSpyThreadLocal.set(null);
}
// beans
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "com.baeldung.persistence.model" });
final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
@Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(Preconditions.checkNotNull(env.getProperty("jdbc.driverClassName")));
dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("jdbc.url")));
dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("jdbc.user")));
dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("jdbc.pass")));
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
final JpaTransactionManagerSpy transactionManager = new JpaTransactionManagerSpy();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
final Properties additionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", "false");
return hibernateProperties;
}
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager){
TransactionTemplate template = new TransactionTemplate(transactionManager);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return template;
}
}