Spring tests inpact on service transactions
I am using @Transactional for my JUnit tests (main advantage is rollback of changes within one test) but I have small problem that this influence my service transactions. So for example this is
my service :
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class ServiceImpl
my unit test :
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/test-context.xml" })
@Transactional
public class TestService
@Test
public void testNumberTransaction() {
Entity a = new Entity();
Entity b = new Entity();
service.add(a);
service.add(b);
}
So naively I expected to have two separate transactions for service.a开发者_StackOverflow社区dd() but unless I use @nontransactional on the test method it runs inside one transaction (but then it does not roll back after test).
Is this expected?Can I alter it with some configuration?
Thanks
Yes, it's expected. Propagation.REQUIRED
(which is the default) means: execute in the existing transaction if it exists. Else, create a transaction and commit it at the end of the method.
So yes, if the whole test method is transactional, both service calls will execute in the context of the test transaction.
Note that since the service is annotated with REQUIRED
, it's supposed to work if a transaction already exists. This makes the test valid: it tests your service in the context of an existing transaction. If you want a service to execute in its own dedicated transaction, it should be annotated with REQUIRES_NEW
. But of course, if it's the case, you won't be able to rollback the service transaction by executing the test in a transaction.
By default (REQUIRES
is the default propagation behavior) if the transaction already exists, your service method will not create new one but rather join to the one that exists. In your case it is the transaction created by the Spring test framework (the one that will be rolled back).
You can alter this behavior by setting propagation to REQUIRES_NEW
. However because now you business add
method runs within a new transaction, once you leave this method the transaction will be committed rather than rolled back. By default, since all transactions are joined in the JUnit test transaction, all changes made to the database are rolled back.
The @Transactional
defines the demarcation line. Annotating the class is equivalent with annotating each public method of the class, the line of demarcation is still the method (in your case, the testNumberTransaction()
). The commit/rollback decision will be taken at the demarcation point, that is when returning from your test method. Regardless of whether you specify REQUIRES
or REQUIRES_NEW
propagation, the actual transaction unit is the same, your testNumberTransaction()
method so the two service.add(...)
calls will always be executed in the same transaction.
If your goal is to always roll back your transactions after the test, then simply removing the @Transactional
annotation (or putting @nontransactional as you mentioned) should do.
If, on the other hand, you want to FORCE a new transaction for each service.add(...)
invocation, you could either create a wrapper for your service class where you have an add(...)
method that is annotated with @Transactional(propagation = Propagation.REQUIRES_NEW) and call the wrapped service
instance add(...)
method from there. OR you could add some declarative transaction management in your spring test context that adds transaction advise for your service.add(...)
method. Read more in the spring documentation on how to add declarative transaction support using the <tx:XXX>
tags.
精彩评论