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

1 comentario:

Es bueno comunicarnos, comenta!!.