Steve Chambers Steve Chambers - 1 month ago 13
Java Question

How to create non-transactional JUnit integration tests in Spring?

An integration test class is annotated with:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = IntegrationTestConfig.class)


It's not supposed to run in a transaction so isn't marked as
@Transactional
but I'm getting errors when trying to perform persist, merge etc. operations on the
EntityManager
, which is injected using
@PersistenceContext
:


No transactional EntityManager available


How can this be resolved?

EDIT:
As requested in the comments, the Spring version is 4.1.0.RELEASE and
IntegrationTestConfig
is below:

@EnableAspectJAutoProxy
@EnableAsync
@EnableScheduling
@EnableTransactionManagement
@Configuration
public class IntegrationTestConfig {
/**
* Override the existing JPA data source bean with a test data source.
* @return test data source
*/
@Bean
public DataSource dataSource() {
final SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(org.h2.Driver.class);
dataSource.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS mydb");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
}

Answer

If you are sure that you are never going to call entityManager.flush(), obtain the PersistenceContext as follows:

@PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;

Why is this needed? Spring Data JPA hands out what is called a shared EntityManager when the @PersistenceContext annotation is used (without any attributes). Full details for this are available in the JavaDocs for org.springframework.orm.jpa.SharedEntityManagerCreator. This class maintains a lookup table where the EntityManager methods flush, merge, persist, refresh and remove are required to be run inside a transaction. So, any time it encounters a method call that is not inside a transaction, it bails out.

The annotation @PersistenceContext has a type attribute that can be set to one of PersistenceContextType.EXTENDED or PersistenceContextType.TRANSACTION, with the later being the default. Therefore, the default @PersistenceContext causes SharedEntityManagerCreator to look for a transaction and bail out if none is found.

Using PersistenceContextType.EXTENDED bypasses the need to check for a transaction when obtaining the EntityManager and therefore the code should work.


flush still cannot be called without a transaction because the JPA providers require it to be called only within a transactional context.

Comments