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