Merge pull request #7947 from maryarm/BAEL-3228
#BAEL-3228: Unit tests for Transaction Propagation and Isolation in Spring
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user