Programmatic Transaction Management
The Spring Framework provides two means of programmatic transaction management, by using:
-
The
TransactionTemplate. -
A
PlatformTransactionManagerimplementation directly.
The Spring team generally recommends the TransactionTemplate for programmatic
transaction management. The second approach is similar to using the JTA
UserTransaction API, although exception handling is less cumbersome.
Using the TransactionTemplate
The TransactionTemplate adopts the same approach as other Spring templates, such as
the JdbcTemplate. It uses a callback approach (to free application code from having to
do the boilerplate acquisition and release transactional resources) and results in
code that is intention driven, in that your code focuses solely on what
you want to do.
As the examples that follow show, using the TransactionTemplate absolutely
couples you to Spring’s transaction infrastructure and APIs. Whether or not programmatic
transaction management is suitable for your development needs is a decision that you
have to make yourself.
|
Application code that must execute in a transactional context and that explicitly uses the
TransactionTemplate resembles the next example. You, as an application
developer, can write a TransactionCallback implementation (typically expressed as an
anonymous inner class) that contains the code that you need to execute in the context of
a transaction. You can then pass an instance of your custom TransactionCallback to the
execute(..) method exposed on the TransactionTemplate. The following example shows how to do so:
public class SimpleService implements Service {
// single TransactionTemplate shared amongst all methods in this instance
private final TransactionTemplate transactionTemplate;
// use constructor-injection to supply the PlatformTransactionManager
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public Object someServiceMethod() {
return transactionTemplate.execute(new TransactionCallback() {
// the code in this method executes in a transactional context
public Object doInTransaction(TransactionStatus status) {
updateOperation1();
return resultOfUpdateOperation2();
}
});
}
}
// use constructor-injection to supply the PlatformTransactionManager
class SimpleService(transactionManager: PlatformTransactionManager) : Service {
// single TransactionTemplate shared amongst all methods in this instance
private val transactionTemplate = TransactionTemplate(transactionManager)
fun someServiceMethod() = transactionTemplate.execute<Any?> {
updateOperation1()
resultOfUpdateOperation2()
}
}
If there is no return value, you can use the convenient TransactionCallbackWithoutResult class
with an anonymous class, as follows:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
override fun doInTransactionWithoutResult(status: TransactionStatus) {
updateOperation1()
updateOperation2()
}
})
Code within the callback can roll the transaction back by calling the
setRollbackOnly() method on the supplied TransactionStatus object, as follows:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
} catch (SomeBusinessException ex) {
status.setRollbackOnly();
}
}
});
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
override fun doInTransactionWithoutResult(status: TransactionStatus) {
try {
updateOperation1()
updateOperation2()
} catch (ex: SomeBusinessException) {
status.setRollbackOnly()
}
}
})
Specifying Transaction Settings
You can specify transaction settings (such as the propagation mode, the isolation level,
the timeout, and so forth) on the TransactionTemplate either programmatically or in
configuration. By default, TransactionTemplate instances have the
default transactional settings. The
following example shows the programmatic customization of the transactional settings for
a specific TransactionTemplate:
public class SimpleService implements Service {
private final TransactionTemplate transactionTemplate;
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
// the transaction settings can be set here explicitly if so desired
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
this.transactionTemplate.setTimeout(30); // 30 seconds
// and so forth...
}
}
class SimpleService(transactionManager: PlatformTransactionManager) : Service {
private val transactionTemplate = TransactionTemplate(transactionManager).apply {
// the transaction settings can be set here explicitly if so desired
isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
timeout = 30 // 30 seconds
// and so forth...
}
}
The following example defines a TransactionTemplate with some custom transactional
settings by using Spring XML configuration:
<bean id="sharedTransactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
<property name="timeout" value="30"/>
</bean>
You can then inject the sharedTransactionTemplate
into as many services as are required.
Finally, instances of the TransactionTemplate class are thread-safe, in that instances
do not maintain any conversational state. TransactionTemplate instances do, however,
maintain configuration state. So, while a number of classes may share a single instance
of a TransactionTemplate, if a class needs to use a TransactionTemplate with
different settings (for example, a different isolation level), you need to create
two distinct TransactionTemplate instances.
Using the PlatformTransactionManager
You can also use the org.springframework.transaction.PlatformTransactionManager
directly to manage your transaction. To do so, pass the implementation of the
PlatformTransactionManager you use to your bean through a bean reference. Then,
by using the TransactionDefinition and TransactionStatus objects, you can initiate
transactions, roll back, and commit. The following example shows how to do so:
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic here
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
val def = DefaultTransactionDefinition()
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName")
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED
val status = txManager.getTransaction(def)
try {
// execute your business logic here
} catch (ex: MyException) {
txManager.rollback(status)
throw ex
}
txManager.commit(status)