jueves, 4 de diciembre de 2014

Acceso a datos con Spring y ORM (parte 3)

 El acceso a datos a través de ORM y Spring

Hibernate

Trataremos la versión Hibernate 3 dentro de Spring, para mostrar la integración de ORM. Esta sección cubre en detalle y muestra distintas formas de implementación de DAO y configuración de transacciones. La mayoría de los patrones que utilizaremos pueden aplicarse al resto de ORMs soportados. .

1 Configuración de SessionFactory en el contenedor Spring

Para evitar que los objetos de la aplicación sean instanciados a través de resource lookups codificados en duro, es posible declarar recursos como un DataSource JDBC o SessionFactory de Hibernate como beans dentro de un contenedor Spring. Los objetos de la aplicación que necesitan acceder a dichos recursos, obtienen referencias a estas instancias predefinidas a través de referencias a beans, como muestra la declaración de DAOs de la siguiente sección.

Una parte de las declaraciones dentro de application context, nos muestra cómo configurar un DataSource JDBC y una SessionFactory de Hibernate:

<!-- -->
<beans>
    <!-- DataSource JDBC -->
    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>
    <!--SessionFactory -->
    <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="myDataSource"/>
        <property name="mappingResources">
            <list>
                <value>product.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
             <value>
                hibernate.dialect=org.hibernate.dialect.HSQLDialect
            </value>
        </property>
    </bean>

</beans>

El intercambio entre Jakarta Commons DBCP BasicDataSource y un DataSource referenciado a través de JNDI (este último generalmente gestionado por un AS), se realiza a través de una simple configuración:

<beans>
    <jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
</beans>

Es posible, tambien acceder a una SessionFactory a través de nombre JNDI con JndiObjectFactoryBean / <jee:jndi-lookup> de Spring, para recuperar la referencia. Sin embargo, esta no suele ser la forma usual fuera de un contexto de EJBs.

2 Implementación de DAOs directamente sobre la API Hibernate 3

En Hibernate 3, existe la posibilidad de trabajar con "contextual sessions", en las que el propio Hibernate gestiona una Session actual por cada transacción. Esta aproximación es similar a la sincronización que realiza el contenedor Spring sobre Session Hibernate para cada transacción. La correspondencia, se detallará a partir del siguiente código basado en la API Hibernate:

public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Collection loadProductsByCategory(String category) {
        return this.sessionFactory.getCurrentSession()
                .createQuery("from test.Product product where product.category=?")
                .setParameter(0, category)
                .list();
    }
}

Vemos que el código es semejante al que podemos encontrar en la documentación de Hibernate, salvo que incluye SessionFactory como una variable de instancia. Lo cual es recomendado, frente a la antigua referencia a la clase static HibernateUtil que encontramos en otros ejemplos. (como norma, es recomendable declarar un recurso dentro de una variable de tipo static, y trabjar con el a traves de una clase manejadora.)

El DAO declarado más arriba, nos permite aplicar la inyección de dependencias: ya que encaja perfectamente en un contenedor IoC Spring, como si hubiese sido codificado a partir de una HibernateTemplate de Spring (versiones anteriores), o bien poder ser configurado como Java plano (un uso sería para la realización de test unitarios). Para lo cual, sólo seria necesario instanciarlo y llamar al método setSessionFactory(..) con la referencia adecuada. La configuración del bean en Spring, seria la siguiente:

<beans>
<!--configuracion del DAO al que podemos intercambiar la sessionFactory de manera sencilla -->
    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="sessionFactory" ref="mySessionFactory"/>
    </bean>

</beans>

La principal ventaja de esta declaración de DAO es que depende únicamente del API Hibernate; no necesita ninguna clase Spring. Esto es interesante desde el punto de vista del desacoplamiento y facilita el desarrollo para DAOs en puro Hibernate.

Sin embargo, la implementación del DAO lanza una HibernateException (que es unchecked, así que podemos haber obviado manejarla), esto implica que los manejadores de excepciones sólo pueden capturar excepciones de tipo Fatal...- a menos que hallamos incluido la jerarquia de excepciones de Hibernate. Capturando casos concretos, como un error de "locks", olvidamos un close(), realizamos múltiples update(),..no es posible sin entrar en la implementación de del llamador. Este asunto, puede darse en aplicaciones que se basan en un entorno Hibernate y no sea necesario un tratamiento especial de excepciones.

Afortunadamente, en la LocalSessionFactoryBean de Spring, soporta el método SessionFactory.getCurrentSession() de Hibernate, en cualquier estratégia elegida de transacciones, devolviendo la Session transaccional gestionada por Spring, incluso con HibernateTransactionManager. Claro está, el comportamiento normal de éste método sigue devolviendo la current Session asociada con la transacción JTA en curso (si hay alguna). Este comportamiento ocurre ya sea cuando usamos JtaTransactionManager, de Spring, o transacciones gestionadas por un contenedor EJB (ContainerManagedTransactions).

Para concluir: es posible implementar DAOs directamente en la API de Hibernate 3, y al mismo tiempo que las transacciones puedan ser gestionadas por el contenedor Spring.

3 Configuración declarativa de transacciones

Es recomendable usar la configuración de transacciones que ofrece Spring, esto nos permite separar entre el API de transacciones y sus invocaciones en nuestro código Java, gracias a un interceptor de transacciones AOP. El interceptor se puede configurar en el contenedor Spring ya sea a través de anotaciones o en XML. Esta posibilidad de configuración declarativa nos permite separar los servicios de negocio del código propio de la gestión de transacciones y centrarnos en la lógica de negocio, lo que aporta un valor añadido a la aplicación.

 

Para profundizar en la gestión de transacciones es recomendable ver la sección “Declarative transaction management”.

Además, la semántica que usamos en la gestión de transacciones, como "propagation", "isolation", pueden tambien configurarse sin que esto afecte a la implementación de los servicios.

Veamos el siguiente ejemplo, que muestra como configurar un interceptor AOP de transacción, a través deXML, para un servicio:

<?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:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- SessionFactory, DataSource, etc. omitted -->

    <bean id="transactionManager"
            class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
<!--AOP: advisor, advice, pointcut    -->
    <aop:config>
        <aop:pointcut id="productServiceMethods"
                expression="execution(* product.ProductService.(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/>
    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="myTxManager">
        <tx:attributes>
            <tx:method name="increasePrice" propagation="REQUIRED"/>
            <tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/>
            <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>
<!--Business Service -->
    <bean id="myProductService" class="product.SimpleProductService">
        <property name="productDao" ref="myProductDao"/>
    </bean>

</beans>

Veamos la implementación del servico al que añadimos el aspecto:

public class ProductServiceImpl implements ProductService {

    private ProductDao productDao;

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    // Vemos que no hay código AOP dentro del método
    // ya que es gestionado a través de la declaración hecha en Spring
    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDao.loadProductsByCategory(category);
        // ...
    }
}

Mostraremos a continuación la configuración de transacción sobre atributos en el siguiente ejemplo. Para ello, hemos de añadir la anotación @Transactional en la capa de servicio, esto indica al contenedor Spring que al encontrar esta anotación, añadir la semántica de transacción para los métodos indicados.

public class ProductServiceImpl implements ProductService {

    private ProductDao productDao;

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }
/*   */
    @Transactional
    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDao.loadProductsByCategory(category);
        // ...
    }
/*   */
    @Transactional(readOnly = true)
    public List<Product> findAllProducts() {
        return this.productDao.findAllProducts();
    }

}

Como podemos ver configuración, se simplifica mucho, respecto a la configuración XML, aún aportando la mísma funcionalidad en el código de la capa de servicio. Lo único que necesitamos es incluir las entradas de la implementación de TransactionManager y "<tx:annotation-driven/>".

<?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:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- SessionFactory, DataSource, etc. omitted -->
<!-- -->
<bean id="transactionManager"
            class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
<!-- -->
    <tx:annotation-driven/>

    <bean id="myProductService" class="product.SimpleProductService">
        <property name="productDao" ref="myProductDao"/>
    </bean>

</beans>

No hay comentarios :

Publicar un comentario