Mostrando las entradas con la etiqueta transactional. Mostrar todas las entradas
Mostrando las entradas con la etiqueta transactional. Mostrar todas las entradas

miércoles, 24 de febrero de 2010

Implementando Transacciones distribuidas con Spring y Atomikos

Una transacción distribuida debe entenderse como aquella transacción(con sus propiedades ACID) que llega a ejecutarse a través de multiples recursos y por recurso debe entenderse una base de datos, una cola de mensajes(JMS), etc..., en distintos hosts, por ejemplo:
Como aseguro que una transacción interbancaria se realice con exito, es decir retirar dinero de un banco y depositarlo en otro, teniendo en cuenta que el gestor de bases de datos del banco origen y destino son diferentes(por ejemplo mysql y postgresql) y que están en distintos hosts.

Definamos entonces de una manera más simple la transacción distribuida de ejemplo:
begin Tx.
retirar dinero del banco origen, por ejemplo MYSQL
depositar dinero al banco destino por ejemplo POSTGRESQL.
si(no excepciones)
commit Tx.
sino
rollback Tx
Una transacción distribuida asegura que se realicen todos los pasos dentro de la transacción de forma correcta o no se realice ninguno.
Por ejemplo si es que a ocurrido una excepción al depositar el dinero en el banco destino entonces la transacción distribuida debe de realizar un rollback y deshacer la acción de retiro de dinero del banco origen.

Lo anterior se realiza mediante un mecanismo llamado commit en dos fases(two-phase commit) el cual es usado para coordinar multiples recursos durante una transacción distribuida, este mecanismo como su nombre lo indica consiste en dos fases:
Fase 1: La fase de preparación.
Fase 2: La fase de compromiso(commit).

Cuando solicitamos un commit hacia una transacción distribuida el administrador transaccional inicia el mecanismo del commit en dos fases de la siguiente manera:

En la fase uno a todos los recursos involucrados en la transacción(bases de datos, colas) se les pregunta por su estado, cada recurso puede responder con cualquiera de los 3 siguientes estados: READY, READ_ONLY y NOT_READY. Si cualquiera de los recursos responde con NOT_READY entonces la transacción entera es deshecha(se realiza un rollback). Si todos los recursos responden con READY entonces estos son comprometidos(commit) en la segunda fase. Los recursos que responden con READ_ONLY no participan de la segunda fase.

En el ejemplo que vamos a ver a continuación usamos el administrador transaccional Atomikos ya que no requiere el uso de un servidor de aplicaciones J2EE, hay que recordar que la administración transaccional(sea local o distribuida) la proporciona el servidor de aplicaciones(jboss, weblogic, websphere, etc).

El ejemplo consiste en realizar una transacción interbancaria simple:

1. Retiro dinero del banco BBVA estando sus cuentas en una base de datos mysql5.0
2. El dinero obtenido del punto 1 lo deposito al banco Scotiabank en una base de datos postgres 8.4

El código fuente es el siguiente:

1. Script de base de datos tanto para mysql como para postgres:
CREATE TABLE cuenta
(
co_cuenta char(10) NOT NULL,
va_monto decimal(10,2),
PRIMARY KEY (co_cuenta)
);

insert into cuenta values('0000000001',10000);
insert into cuenta values('0000000002',10000);
insert into cuenta values('0000000003',10000);
insert into cuenta values('0000000004',10000);
insert into cuenta values('0000000005',10000);

2. Modificar el archivo de configuración del postgresql(postgresql.conf) para habilitar las transacciones distribuidas:
max_prepared_transactions = 20 # zero disables the feature

3. Crear el bean de configuración de spring(application_xa.xml):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!-- 1. Inicialización del log4j: -->
<bean id="log4jInitialization" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="org.springframework.util.Log4jConfigurer" />
<property name="targetMethod" value="initLogging" />
<property name="arguments">
<list><value>C:\\logs\spring.config</value></list>
</property>
</bean>
<!-- 2. Declaración del dataSource para conexiones al BBVA -->
<bean id="dataSourceBBVA" class="com.atomikos.jdbc.AtomikosDataSourceBean">
<property name="uniqueResourceName" value="MYSQLDS"/>
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
<property name="xaProperties">
<props>
<prop key="serverName">localhost</prop>
<prop key="databaseName">bancoBBVA</prop>
<prop key="user">root</prop>
<prop key="password">jdeveloper007</prop>
</props>
</property>
<property name="minPoolSize" value="2"/>
<property name="maxPoolSize" value="10"/>
</bean>
<!-- 3. Declaración del dataSource para conexiones al Scotiabank -->
<bean id="dataSourceScotiabank" class="com.atomikos.jdbc.AtomikosDataSourceBean">
<property name="uniqueResourceName" value="POSTGRESDS"/>
<property name="xaDataSourceClassName" value="org.postgresql.xa.PGXADataSource"/>
<property name="xaProperties">
<props>
<prop key="serverName">localhost</prop>
<prop key="databaseName">bancoScotiabank</prop>
<prop key="user">postgres</prop>
<prop key="password">Jdeveloper007</prop>
</props>
</property>
<property name="minPoolSize" value="2"/>
<property name="maxPoolSize" value="10"/>
</bean>
<!-- 4. Declaración del administrador transaccional que implementa todo el trabajo
transaccional, proporcionado por 'Atomikos' : -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
<property name="forceShutdown" value="true"/>
<property name="startupTransactionService" value="true"/>
</bean>

<!-- 5. Declaración del user transaction de bajo nivel -->
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"></bean>
<!-- 6. Al administrador transaccional JTA de spring le pasamos las referencias
implementadas por 'Atomikos' y no por un servidor de aplicaciones -->
<bean id="administradorTransaccional" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="atomikosTransactionManager"/>
<property name="userTransaction" ref="atomikosUserTransaction"/>
</bean>
<!-- 7. Habilitamos la gestión de transacciones mediante anotaciones: -->
<tx:annotation-driven transaction-manager="administradorTransaccional" />
<!-- 8. Declaración del mapeo sql para la capa de base de datos con iBatis -->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="sql-map-config.xml"/>
</bean>
<!-- 9. Definición de los daos de acceso a datos: -->
<bean id="movimientoBBVADAO" class="pe.com.slcsccy.testspring.model.dao.MovimientoBBVADAOImpl">
<property name="dataSource" ref="dataSourceBBVA"/>
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<bean id="movimientoScotiabankDAO" class="pe.com.slcsccy.testspring.model.dao.MovimientoScotiabankDAOImpl">
<property name="dataSource" ref="dataSourceScotiabank"/>
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<!-- 10. Scaneo de todos los stereotipos @Service -->
<context:component-scan base-package="pe.com.slcsccy.testspring.service"/>
</beans>

4. Creamos los daos y la configuración de ibatis:
4.1 MovimientoBBVADAO.java:
package pe.com.slcsccy.testspring.model.dao;
import pe.com.slcsccy.testspring.dominio.beans.Movimiento;
public interface MovimientoBBVADAO {
public void retirarBBVA(Movimiento cuenta);
}

4.2 MovimientoBBVADAOImpl.java:
package pe.com.slcsccy.testspring.model.dao;
import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;
import pe.com.slcsccy.testspring.dominio.beans.Movimiento;
public class MovimientoBBVADAOImpl extends SqlMapClientDaoSupport implements MovimientoBBVADAO{
@Override
public void retirarBBVA(Movimiento movimiento) {
this.getSqlMapClientTemplate().update("CUENTAS.retirarBBVA", movimiento);
}
}

4.3 MovimientoScotiabankDAO.java:
package pe.com.slcsccy.testspring.model.dao;
import pe.com.slcsccy.testspring.dominio.beans.Movimiento;
public interface MovimientoScotiabankDAO {
public void depositarScotiabank(Movimiento movimiento)throws Exception;
}

4.4 MovimientoScotiabankDAOImpl.java:
package pe.com.slcsccy.testspring.model.dao;
import java.math.BigDecimal;
import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;
import pe.com.slcsccy.testspring.dominio.beans.Movimiento;
public class MovimientoScotiabankDAOImpl extends SqlMapClientDaoSupport implements MovimientoScotiabankDAO{
private static final BigDecimal vaLimiteTransferencia = BigDecimal.valueOf(2000);
@Override
public void depositarScotiabank(Movimiento movimiento) throws Exception {
if(movimiento.getVaMonto().compareTo(vaLimiteTransferencia)>0){
throw new Exception("El monto a transferir supera el límite");
}
this.getSqlMapClientTemplate().update("CUENTAS.depositarScotiabank", movimiento);
}
}

4.5 CUENTAS_SqlMap.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd" >
<sqlMap namespace="CUENTAS">
<update id="retirarBBVA" parameterClass="pe.com.slcsccy.testspring.dominio.beans.Movimiento" >
UPDATE cuenta SET va_monto = va_monto - #vaMonto#
WHERE co_cuenta = #coCuentaInterbancario#
</update>
<update id="depositarScotiabank" parameterClass="pe.com.slcsccy.testspring.dominio.beans.Movimiento" >
UPDATE cuenta SET va_monto = va_monto + #vaMonto#
WHERE co_cuenta = #coCuentaInterbancario#
</update>
</sqlMap>


5. Definimos las clases que representan al servicio:

5.1 FacadeService.java
package pe.com.slcsccy.testspring.service;
import pe.com.slcsccy.testspring.dominio.beans.Movimiento;
public interface FacadeService {
public void transferenciaInterbancaria(Movimiento movimiento) throws Exception;
}

5.2 FacadeServiceImpl.java
package pe.com.slcsccy.testspring.service;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import pe.com.slcsccy.testspring.dominio.beans.Movimiento;
import pe.com.slcsccy.testspring.model.dao.MovimientoBBVADAO;
import pe.com.slcsccy.testspring.model.dao.MovimientoScotiabankDAO;

@Service("facadeService")
@Transactional
public class FacadeServiceImpl implements FacadeService {
private final Logger logger = Logger.getLogger(getClass());
@Autowired
private MovimientoBBVADAO movimientoBBVADAO;
@Autowired
private MovimientoScotiabankDAO movimientoScotiabankDAO;
@Override
@Transactional(rollbackFor=Exception.class,readOnly=false)
public void transferenciaInterbancaria(Movimiento movimiento) throws Exception {
logger.info("INICIANDO TRANSFERENCIA...");
movimientoBBVADAO.retirarBBVA(movimiento);
movimientoScotiabankDAO.depositarScotiabank(movimiento);
logger.info("TRANSFERENCIA FINALIZADA...");
}
}

6. Por ultimo la invocación al servicio anterior
6.1 Inicio2.java:
package pe.com.slcsccy.testspring;
import java.math.BigDecimal;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pe.com.slcsccy.testspring.dominio.beans.Movimiento;
import pe.com.slcsccy.testspring.service.FacadeService;

public class Inicio2 {

public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application_xa.xml");
FacadeService servicioTx = (FacadeService)context.getBean("facadeService");
Movimiento movimiento = new Movimiento();
movimiento.setCoCuentaInterbancario("0000000004");
movimiento.setVaMonto(BigDecimal.valueOf(1500));
try {
servicioTx.transferenciaInterbancaria(movimiento);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Si quieres que se realice un rollback al movimiento colocale un monto mayor a 2000 y verás que hace un rollback a la base del BBVA tambien.

martes, 23 de febrero de 2010

Implementando Transacciones con Spring y Anotaciones @Transactional Parte 3

En esta oportunidad vamos a ver como podemos propagar las transacciones mediante spring. Se debe de entender por propagación de la transacción al mecanismo que extiende la transacción hacia otros métodos también transaccionales. En los ejemplos anteriores habíamos visto transacciones encapsuladas en un solo método, por ejemplo:

@Transactional
public class ClaseTransaccionalImpl implements ClaseTransaccional{
public void metodoTransaccional01(){
dao01.ejecutar();
}
public void metodoTransaccional02(){
dao02.ejecutar();
}
}

En el código anterior cada método(invocado independientemente) inicia y cierra una transacción, pero que pasa si cualquiera de los dos métodos llama al otro como a continuación:

@Transactional
public class ClaseTransaccionalImpl implements ClaseTransaccional{
public void metodoTransaccional01(){
dao01.ejecutar();
metodoTransaccional02();
}
public void metodoTransaccional02(){
dao02.ejecutar();
}
}

Ahora vemos que el método metodoTransaccional01() invoca al método metodoTransaccional02(), pero que es lo que pasa con la transacción iniciada por la invocación de metodoTransaccional01()?.
se realiza un commit antes de la ejecución del siguiente método?
la transacción se suspende y se crea otra transacción por la invocación del nuevo método?
la transacción se extiende(PROPAGA) y la ejecución de los dos métodos se ejecutan en una sola transacción?
Recordando del post anterior los atributos transaccionales por default a nivel de método son los siguientes:
propagation: REQUIRED.
isolation: DEFAULT(READ_COMMITED para Oracle)
timeout: -1
readOnly: false

Entonces la clase anterior definida explicitamente quedaría como:
@Transactional
public class ClaseTransaccionalImpl implements ClaseTransaccional{

@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED,readOnly=false)
public void metodoTransaccional01(){
dao01.ejecutar();
metodoTransaccional02();
}

@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED,readOnly=false)
public void metodoTransaccional02(){
dao02.ejecutar();
}
}

Entonces que se puede deducir?, pues que la transacción se propaga hacia el método metodoTransaccional02(), ya que el atributo de propagación de la transacción es REQUIRED. Veamoslo más despacio:

1. Supongamos que desde una clase cualquiera ejecutamos lo siguiente:
ClaseTransaccional servicio = (ClaseTransaccional)context.getBean("claseTransaccional");
servicio.metodoTransaccional01();

2. Lo que hará spring es detectar que el método a sido marcado como transaccional e iniciará una transacción.

3. La ejecución del método se realiza e invoca al DAO: dao01.ejecutar();

4. La ejecución del método continua y encuentra el método metodoTransaccional02() antes de ejecutar este método spring identifica que es transaccional y que su tipo de propagación es REQUIRED, es decir que si ya existe un contexto transaccional entonces comparte dicho contexto, y si es que no existe el contexto transaccional entonces debe de crear una nueva transacción, para el ejemplo el método transaccional ha sido invocado en un contexto transaccional definido por metodoTransaccional01() por lo que la transacción se propaga a este nuevo método.

5. Si el método metodoTransaccional02() termina exitosamente, es decir no lanza alguna excepción entonces el método metodoTransaccional01() continua su ejecucion y si termina satisfactoriamente realiza un commit de la transacción, dando la posta al hilo que lo invocó.
6. Si en el punto 5 al metodo metodoTransaccional01() le hemos aplicado un aspecto afterReturning entonces la transacción se propagará a dicho aspecto.

domingo, 7 de febrero de 2010

Implementando Transacciones con Spring y Anotaciones @Transactional Parte 1

Este post va a ser el primero de una serie el cuál estará enfocado a la implementación de transacciones mediante spring, estas implementaciones las realizaré mediantes anotaciones, con la anotación @Transactional, y en forma declarativa mediante xml y aspectos.
El proyecto desarrollado hace uso del esquema de ejemplo HR que viene con Oracle 10G, la capa de acceso a datos la he implementado con ibatis y el generador de clases de acceso a datos y beans a sido abator, el objetivo del proyecto es ver como spring implementa métodos transaccionales mediante anotaciones, en este primer post vamos a ver de forma simple como antes de la ejecución de un método java spring inicia una nueva transacción y como al finalizar exitosamente dicho método realiza el commit correspondiente para persistir los objetos en forma permanente en la base de datos oracle, es decir y para recordar oracle copia los datos a sus redologs y de allí cuando crea conveniente a sus datafiles, el proyecto tambien hace uso del log4j para poder visualizar la clase de spring que crea la transacción y la clase que la compromete(commit).
Los pasos para el desarrollo del proyecto han sido los siguientes:

1. Generación de daos y beans mediante abator(ahora ibator) , el archivo de configuración es el siguiente:
configuradorhr.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE abatorConfiguration
PUBLIC "-//Apache Software Foundation//DTD Abator for iBATIS Configuration 1.0//EN"
"http://ibatis.apache.org/dtd/abator-config_1_0.dtd">

<abatorConfiguration>
<abatorContext id="DB2Tables" generatorSet="Java5">
<jdbcConnection driverClass="oracle.jdbc.driver.OracleDriver" connectionURL="jdbc:oracle:thin:@192.168.1.11:1521:optimus"
userId="hr" password="jdeveloper">
<classPathEntry location="ojdbc6.jar" />
</jdbcConnection>

<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>

<javaModelGenerator targetPackage="pe.com.slcsccy.testspring.dominio.beans" targetProject=".">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>

<sqlMapGenerator targetPackage="pe.com.slcsccy.testspring.model.dao.ibatis" targetProject=".">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>

<daoGenerator type="SPRING" targetPackage="pe.com.slcsccy.testspring.model.dao" targetProject=".">
<property name="enableSubPackages" value="true" />
</daoGenerator>

<table tableName="countries"/>
<table tableName="departments"/>
<table tableName="employees"/>
<table tableName="job_history"/>
<table tableName="jobs"/>
<table tableName="locations"/>
<table tableName="regions"/>

</abatorContext>
</abatorConfiguration>

Ahora lo único que necesitamos para crear los beans y daos es ejecutar lo siguiente:
java -jar abator.jar configuradorhr.xml true
Debes recordar también que debes de tener el driver del oracle en tu classpath para que abator te genere todas las clases.

2. El segundo paso a sido desarrollar la capa de servicios, el servicio lo he implementado mediante una interfaz y una clase con un solo método, que es el siguiente:

FacadeService.java
package pe.com.slcsccy.testspring.service;
import java.util.List;
import pe.com.slcsccy.testspring.dominio.beans.Employees;
public interface FacadeService {
public void registrarEmpleados(List empleados);
}

FacadeServiceImpl.java
package pe.com.slcsccy.testspring.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import pe.com.slcsccy.testspring.dominio.beans.Employees;
import pe.com.slcsccy.testspring.model.dao.EmployeesDAO;

@Service("facadeService")
@Transactional
public class FacadeServiceImpl implements FacadeService {
@Autowired
private EmployeesDAO empleadoDAO;
@Override
public void registrarEmpleados(List empleados) {
for(Employees e:empleados){
empleadoDAO.insert(e);
}
}
}

La clase FacadeServiceImpl.java primero la anotamos como un servicio estereotipado(@Service), es decir spring cuando encuentre esta anotación cargará el bean en memoria, la otra anotación @Transactional es la más importante para este post y nos indica que todos los métodos definidos en esta clase van a ser transaccionales, si la anotación @Transactional no le agregamos sus atributos, entonces los atributos que adquiere por defecto son los siguientes:

propagation: REQUIRED, es la conducta de propagación de la transacción, esto quiere decir que si el método no se ejecuta en un contexto transaccional, entonces spring crea una nueva transacción y si ya está en un contexto transaccional(por algún otro lado se inició una transacción), entonces el método se acopla a la transacción.
isolation: DEFAULT, el nivel de aislamiento de la transacción.
timeout: -1, el tiempo máximo en segundos que debe de ejecutarse la transacción.
read-only: false, es la transacción de solo lectura?.

Algunos definiciones personalizadas de @Transactional podrían ser la siguientes:
@Transactional(readOnly=false, propagation=Propagation.REQUIRES_NEW)
Esta definición a nivel de clase nos indica que todos los métodos no son de solo lectura(pueden hacer update insert a la base), y su nivel de propagación requiere una nueva transacción, es decir cuando la aplicación realice una invocación a un método de esta clase se deberá crear una nueva transacción.

@Transactional(readOnly=true)
Esta definición a nivel de clase nos indica que todos los métodos son de solo lectura, es decir nuestros métodos no puede realizar insert o update, si lo hacen entonces spring lanza una excepción.

3. El ultimo paso es la integración de los componentes en un contenedor de beans de spring:
application.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!-- Inicialización del log4j: -->
<bean id="log4jInitialization" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="org.springframework.util.Log4jConfigurer" />
<property name="targetMethod" value="initLogging" />
<property name="arguments">
<list><value>C:\\logs\spring.config</value></list>
</property>
</bean>
<!-- Definición del data source o fuente de datos Oracle -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
p:driverClassName="oracle.jdbc.driver.OracleDriver"
p:url="jdbc:oracle:thin:@192.168.1.11:1521:optimus"
p:username="hr" p:password="jdeveloper"/>
<!-- Administrador transaccional para los objetos de acceso a datos con iBatis : -->
<bean id="administradorTransaccional" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<!-- Gestor de transacciones mediante anotaciones: -->
<tx:annotation-driven transaction-manager="administradorTransaccional" />
<!-- Declaración del mapeo sql para la capa de base de datos con iBatis -->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"
p:configLocation="sql-map-config.xml"/>
<!-- Definiciòn de los daos de acceso a datos: -->
<bean id="abstractDao" abstract="true" p:sqlMapClient-ref="sqlMapClient"
p:dataSource-ref="dataSource"/>
<bean id="countriesDAO" class="pe.com.slcsccy.testspring.model.dao.CountriesDAOImpl" parent="abstractDao"/>
<bean id="departmentsDAO" class="pe.com.slcsccy.testspring.model.dao.DepartmentsDAOImpl" parent="abstractDao"/>
<bean id="employeesDAO" class="pe.com.slcsccy.testspring.model.dao.EmployeesDAOImpl" parent="abstractDao"/>
<bean id="jobHistoryDAO" class="pe.com.slcsccy.testspring.model.dao.JobHistoryDAOImpl" parent="abstractDao"/>
<bean id="jobsDAO" class="pe.com.slcsccy.testspring.model.dao.JobsDAOImpl" parent="abstractDao"/>
<bean id="locationsDAO" class="pe.com.slcsccy.testspring.model.dao.LocationsDAOImpl" parent="abstractDao"/>
<bean id="regionsDAO" class="pe.com.slcsccy.testspring.model.dao.RegionsDAOImpl" parent="abstractDao"/>
<!-- Scaneo de todos los stereotipos @Service -->
<context:component-scan base-package="pe.com.slcsccy.testspring.service"/>
</beans>

La ejecución del servicio que registra 3 empleados es la siguiente:

Inicio.java
package pe.com.slcsccy.testspring;
import java.util.ArrayList;
import java.util.Date;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pe.com.slcsccy.testspring.dominio.beans.Employees;
import pe.com.slcsccy.testspring.service.FacadeService;
public class Inicio {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
FacadeService servicioTx = (FacadeService)context.getBean("facadeService");
ArrayList empleados = new ArrayList() ;
//Empleado 1.
Employees empleado1 = new Employees();
empleado1.setEmployeeId(100001);
empleado1.setLastName("ccacique1");
empleado1.setEmail("carlos.cacique1@gmail.com");
empleado1.setHireDate(new Date(System.nanoTime()));
empleado1.setJobId("IT_PROG");
//Empleado 2.
Employees empleado2 = new Employees();
empleado2.setEmployeeId(100002);
empleado2.setLastName("ccacique2");
empleado2.setEmail("carlos.cacique2@gmail.com");
empleado2.setHireDate(new Date(System.nanoTime()));
empleado2.setJobId("IT_PROG");
//Empleado 3.
Employees empleado3 = new Employees();
empleado3.setEmployeeId(100003);
empleado3.setLastName("ccacique3");
empleado3.setEmail("carlos.cacique3@gmail.com");
empleado3.setHireDate(new Date(System.nanoTime()));
empleado3.setJobId("IT_PROG");
//Colocamos a todos los empleados en una lista:
empleados.add(empleado1);
empleados.add(empleado2);
empleados.add(empleado3);
//Ejecutamos el servicio transaccional.
servicioTx.registrarEmpleados(empleados);
}
}

El log generado es el siguiente, vean como spring crea y compromete(commit) la transacción:
2010-02-07 11:28:10 DEBUG [main] (AbstractFallbackTransactionAttributeSource.java:107) - Adding transactional method [registrarEmpleados] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
2010-02-07 11:28:10 DEBUG [main] (AbstractPlatformTransactionManager.java:371) - Creating new transaction with name [pe.com.slcsccy.testspring.service.FacadeService.registrarEmpleados]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2010-02-07 11:28:11 DEBUG [main] (DataSourceTransactionManager.java:202) - Acquired Connection [jdbc:oracle:thin:@192.168.1.11:1521:optimus, UserName=HR, Oracle JDBC driver] for JDBC transaction
2010-02-07 11:28:11 DEBUG [main] (DataSourceTransactionManager.java:219) - Switching JDBC Connection [jdbc:oracle:thin:@192.168.1.11:1521:optimus, UserName=HR, Oracle JDBC driver] to manual commit
2010-02-07 11:28:11 DEBUG [main] (SqlMapClientTemplate.java:177) - Opened SqlMapSession [com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl@11c0d60] for iBATIS operation
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {conn-100000} Connection
2010-02-07 11:28:11 DEBUG [main] (SqlMapClientTemplate.java:194) - Obtained JDBC Connection [jdbc:oracle:thin:@192.168.1.11:1521:optimus, UserName=HR, Oracle JDBC driver] for iBATIS operation
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {conn-100000} Preparing Statement: insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY, COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {pstm-100001} Executing Statement: insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY, COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {pstm-100001} Parameters: [100001, null, ccacique1, carlos.cacique1@gmail.com, null, 2293-08-14 05:29:50.376, IT_PROG, null, null, null, null]
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {pstm-100001} Types: [java.lang.Integer, null, java.lang.String, java.lang.String, null, java.sql.Timestamp, java.lang.String, null, null, null, null]
2010-02-07 11:28:11 DEBUG [main] (SqlMapClientTemplate.java:177) - Opened SqlMapSession [com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl@e4bb3c] for iBATIS operation
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {conn-100002} Connection
2010-02-07 11:28:11 DEBUG [main] (SqlMapClientTemplate.java:194) - Obtained JDBC Connection [jdbc:oracle:thin:@192.168.1.11:1521:optimus, UserName=HR, Oracle JDBC driver] for iBATIS operation
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {conn-100002} Preparing Statement: insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY, COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {pstm-100003} Executing Statement: insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY, COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {pstm-100003} Parameters: [100002, null, ccacique2, carlos.cacique2@gmail.com, null, 2293-08-14 05:30:04.039, IT_PROG, null, null, null, null]
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {pstm-100003} Types: [java.lang.Integer, null, java.lang.String, java.lang.String, null, java.sql.Timestamp, java.lang.String, null, null, null, null]
2010-02-07 11:28:11 DEBUG [main] (SqlMapClientTemplate.java:177) - Opened SqlMapSession [com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl@cffc79] for iBATIS operation
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {conn-100004} Connection
2010-02-07 11:28:11 DEBUG [main] (SqlMapClientTemplate.java:194) - Obtained JDBC Connection [jdbc:oracle:thin:@192.168.1.11:1521:optimus, UserName=HR, Oracle JDBC driver] for iBATIS operation
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {conn-100004} Preparing Statement: insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY, COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {pstm-100005} Executing Statement: insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY, COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {pstm-100005} Parameters: [100003, null, ccacique3, carlos.cacique3@gmail.com, null, 2293-08-14 05:30:08.251, IT_PROG, null, null, null, null]
2010-02-07 11:28:11 DEBUG [main] (JakartaCommonsLoggingImpl.java:27) - {pstm-100005} Types: [java.lang.Integer, null, java.lang.String, java.lang.String, null, java.sql.Timestamp, java.lang.String, null, null, null, null]
2010-02-07 11:28:11 DEBUG [main] (AbstractPlatformTransactionManager.java:730) - Initiating transaction commit
2010-02-07 11:28:11 DEBUG [main] (DataSourceTransactionManager.java:259) - Committing JDBC transaction on Connection [jdbc:oracle:thin:@192.168.1.11:1521:optimus, UserName=HR, Oracle JDBC driver]
2010-02-07 11:28:11 DEBUG [main] (DataSourceTransactionManager.java:314) - Releasing JDBC Connection [jdbc:oracle:thin:@192.168.1.11:1521:optimus, UserName=HR, Oracle JDBC driver] after transaction
2010-02-07 11:28:11 DEBUG [main] (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource

El proyecto completo te lo puedes bajar desde AQUI