miércoles, 11 de enero de 2012

Configuracion Hibernate en Spring

Para configurar hibernate en Spring he tenido que hacer los siguiente:

Crear mi propio filtro, que lo he llamado CustomHibernateSessionFilter. Este filtro es necesario para establecer el FlushMode a AUTO y sobreescribir el de defecto (FlushMode.NEVER) que me ha dado problemas como la excepción que he puesto más abajo. 

package com.manager.filters;

import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate3.support.OpenSessionInViewFilter;

public class CustomHibernateSessionFilter extends OpenSessionInViewFilter {
      
    protected Session getSession(SessionFactory sessionFactory)
                        throws DataAccessResourceFailureException {
        Session session = super.getSession(sessionFactory);
        session.setFlushMode(FlushMode.AUTO);  
        return session;
    }
  
    protected void closeSession(Session session, SessionFactory factory) {
        session.flush();
        super.closeSession(session, factory);
    }
}

Después, en el archivo web.xml he he creado también el filtro:

   <filter>
      <filter-name>hibernateFilter</filter-name>
      <filter-class>com.manager.filters.CustomHibernateSessionFilter</filter-class>
      <init-param>
        <param-name>sessionFactoryBeanName</param-name>
        <param-value>sessionFactory</param-value>
      </init-param>
   </filter>

   <filter-mapping>
       <filter-name>hibernateFilter</filter-name>
       <url-pattern>/*</url-pattern>
   </filter-mapping>          


Tiene que haber una forma más fácil pero por el momento crear mi propio filtro extendiendio del que nos proporciona Spring me ha resuelto algunos problemas con la gestión de la sesión, en concreto me daba esta  excepción:"Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition." y la fuente de la solución la encontre en este link: https://forum.hibernate.org/viewtopic.php?f=1&t=1007587&view=next.  Si averiguo una forma más fácil lo añadiré al final de la entrada.


Esta es la implementación del dao que utilizo para obtener las entidades de la base de datos:

package com.manager.dao;

import java.io.Serializable;
import java.util.List;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;

@Repository
public class SpringHibernateDAO extends HibernateDaoSupport implements IGenericDAO {

    @Autowired
    public SpringHibernateDAO(SessionFactory sessionFactory) {
        super.setSessionFactory(sessionFactory);
    }
   
    public void persist(Object entity) {
        getHibernateTemplate().saveOrUpdate(entity);
    }
   
    public void persist(Object[] entities) {
        for (int i = 0; i < entities.length; i++) {
            persist(entities[i]);
        }
    }

    public <T> List<T> find(Class<T> entityClass) {
        final List<T> entities = getHibernateTemplate().loadAll(entityClass);
        return entities;
    }
   
    public <T> T load(Class<T> entityClass, Serializable id) {
        final T entity = (T) getHibernateTemplate().load(entityClass, id);
        return entity;
    }
   
    public <T> List<T> find(String hql) {
        final List<T> entities = getHibernateTemplate().find(hql);
        return entities;
    }
}

 
Y esta la interfaz:

package com.manager.dao;

import java.io.Serializable;
import java.util.List;

public interface IGenericDAO {       
    public void persist(Object entity); 
    public void persist(Object[] entities); 
    public <T> List<T> find(Class<T> entityClass); 
    public <T> T load(Class<T> entityClass, Serializable id); 
    public <T> List<T> find(String hql); 
}

Y el servicio que utiliza el repositorio es el siguiente:

package com.manager.services;

import java.util.List;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.manager.dao.IGenericDAO;
import com.manager.entities.Module;

@Service
public class ModuleService {
    @Resource
    private IGenericDAO dao;

    public Module newModule() {
        return new Module();
    }

    public void persist(Object obj) {
        dao.persist(obj);
    }

    public List<Module> getModules() {
        final List<Module> list = dao.find(Module.class);
        return list;
    }
   
    public Module getModule(int moduleId){
        return dao.load(Module.class, moduleId);
    }
}

El fichero con la configuración de hibernate es el siguiente:

<?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:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:security="http://www.springframework.org/schema/security"
    xmlns:context="http://www.springframework.org/schema/context"
    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-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

    <aop:aspectj-autoproxy /> 
    <tx:annotation-driven transaction-manager="transacctionManager"/>

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:database.properties</value>
            </list>
        </property>
    </bean>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="initialSize" value="15" />
        <property name="maxActive" value="30" />
        <property name="maxIdle" value="10" />
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <!--<property name="configLocation" value="classpath:hibernate.cfg.xml" /> -->
        <property name="dataSource" ref="dataSource"/>
        <property name="exposeTransactionAwareSessionFactory"><value>false</value></property>       
        <property name="mappingResources">
            <list>
                <value>news.hbm.xml</value>
                <value>module.hbm.xml</value>
                <value>newspaper.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">update</prop>               
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.query.substitutions">true 'T', false 'F'</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.c3p0.minPoolSize">5</prop>
                <prop key="hibernate.c3p0.maxPoolSize">20</prop>
                <prop key="hibernate.c3p0.timeout">600</prop>
                <prop key="hibernate.c3p0.max_statement">50</prop>
                <prop key="hibernate.c3p0.testConnectionOnCheckout">false</prop>
                <prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate3.SpringSessionContext</prop> 
                  <prop key="hibernate.transaction.factory_class">org.springframework.orm.hibernate3.SpringTransactionFactory</prop>
                <prop key="hibernate.current_session_context_class">thread</prop>               
                <prop key="show_sql">true</prop>                           
            </props>
        </property>
    </bean>
    <!--
    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">   
      <property name="interceptors">
        <list>
          <ref bean="openSessionInViewInterceptor"/>
        </list>
      </property>
    </bean>
    -->
   
<!--
    <bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor"> 
        <property name="sessionFactory"> 
            <ref local="sessionFactory"/> 
        </property> 
        <property name="flushModeName"> 
            <value>FLUSH_AUTO</value> 
        </property> 
    </bean>
-->
   
    <!--
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
      <tx:attributes>   
        <tx:method name="get*" read-only="true"/>      
        <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
      -->

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="hibInterceptor" class="org.springframework.orm.hibernate3.HibernateInterceptor">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

Un fichero de hibernate que he añadido es el module.hbm.xml, cuyo código es el siguiente:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">


<hibernate-mapping package="com.manager.entities">
    <class name="Module" table="modules" schema="manager">
        <id name="moduleId" column="module_id" type="integer" unsaved-value="null">
            <generator class="increment" />
        </id>            
        <property name="name" column="modules_name" type="string"/>
    </class>
</hibernate-mapping>

Es muy simple. Lo que hacemos es decirle el paquete donde esta creada la entidad, la clase que utilizamos (Módule) y que se mapeará con la columna de la tabla de base de datos "modules". Esta clase tiene un atributo "moduleId" que se corresponde con la columna "module_id" de la base de datos de tipo "integer" y no puede ser nula y cuyo identificador al crearse se debe autoincrementar, y también tiene otra propiedad "name".

Una vez tenemos todo lo anterior. Podemos incluir el servicio en un controlador y crear las entidades correspondientes en la base de datos. Por ejemplo yo me he creado el siguiente controlador.

package com.manager.controllers;

import java.util.List;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.manager.entities.Module;
import com.manager.services.ModuleService;

@Controller
@RequestMapping("/module")
public class ModuleController {
      protected static final Logger logger = Logger.getLogger(ModuleController.class);
   
      @Resource
      private ModuleService mModule;
   
      @ModelAttribute("module")
      public Module populateForm() {
          return new Module(); // creamos el bean para que se pueda popular
      }
     
      @RequestMapping(value="/form", method=RequestMethod.GET)
      public String formView() {
          return "fmodule";
      }        
     
      @RequestMapping(value="/form/join", method=RequestMethod.GET)
      public String formJoinView() {
          return "fmoduleJoin";
      }   
         
      @RequestMapping(value="/{moduleId}", method=RequestMethod.GET)
      public @ResponseBody Object findModule(@PathVariable String moduleId, Model model) {       
          return mModule.getModule(Integer.valueOf(moduleId));
      }
     
      @RequestMapping(value="/list", method=RequestMethod.POST)
      public @ResponseBody List<Module> getAllModules(Model model) {   
          return mModule.getModules();
      }
     
      @RequestMapping(value="/new", method=RequestMethod.POST)
      public @ResponseBody Module createModule(@RequestBody Module module, Model model) {       
          mModule.persist(module);
          return module;
      }
}


 

7 comentarios:

  1. Como manejas los erroes que ocurren al momento de realizar alguna operacion de guardado o eliminacion?
    Saludos.

    ResponderEliminar
  2. Buenas. Supongo que un ejemplo sería:

    Transaction tx = null;
    try {
    Session session = sessionFactory.getCurrentSession();
    tx = session.beginTransaction();
    session.delete(news);
    tx.commit();
    session.close();
    } catch (RuntimeException e) {
    logger.error("Error al eliminar la noticia:"+e.getMessage());
    if (tx != null && tx.isActive()) {
    try {
    tx.rollback();
    } catch (HibernateException e1) {
    logger.debug("Error rolling back transaction");
    }
    throw e;
    }
    }

    aunque con la nueva versión de java se ha mejorado.

    También si se utiliza json para codificar todas las respuesta he utilizado la anotación @ExceptionHandler y entre parentesis la excepción que quiero que se capture.

    @ExceptionHandler(Exception.class)
    public @ResponseBody String handlerException(Exception e, HttpServletResponse response){ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    return e.getMessage();
    }

    ResponderEliminar
  3. Yo lo hice de la siguiente manera.
    @Transactional
    public boolean deleteIds(List ids){
    boolean exito=false;
    try {
    for (Long id : ids)
    configModuleDao.deleteById(id);
    exito=true;
    } catch (Exception e) {
    logger.debug(e.getMessage());
    }
    return exito;
    }

    Pero no hay una forma para verificar las restricciones y evitar el error?
    saludos.

    ResponderEliminar
  4. Exactamente, ¿qué es lo que quieres hacer? Si se produce un error tiene que capturarse. Aunque se cumplan las restricciones sigue siendo posible que se produzca alguna otra excepción. En el ejemplo si se elimina un registro ya se da como exitosa la operación, sin embargo, es posible que alguna operación haya producido alguna excepción. Quizá lo que necesites sea cargar el objeto con load y despues eliminarlo con delete así te aseguras que el objeto es persistente y que se debe poder eliminar correctamente aunque siempre pueden haber claves ajenas que salten alguna excepción.

    public Modulo findModuloById(Short id) {
    return (Modulo)getHibernateTemplate().load(Modulo.class, id);
    }

    public void deleteModuloById(Short id){
    Modulo modulo = findCalendarById(id);
    getHibernateTemplate().delete(modulo);
    getHibernateTemplate().flush() ;
    }

    ResponderEliminar
  5. Ya me di cuenta que el codigo de la parte de arriba que puse:
    el de @Transactional
    No funciona correctamente, al momento de realizar el borrado de dos registros no realiza el rollback y se elimina un registro y se supone que no debe de realizar ningun borrado.
    Ya probe la otra opcion y me da el mismo resultado.
    Alquin seba como corregir el problema;
    saludos.

    ResponderEliminar
  6. Encontre la solucion de mi problema, es un problema comun si no sabes utilizar las anotaciones de forma adecuada. resulta que las anotaciones deben de estar en todas las clases que utilices, en mi caso es la anotacion la tengo en la capa de servicio pero no la anotacion no la tenia en la capa DAO, le puse la anotacion y listo funciono de forma correcta.
    Saludos.

    ResponderEliminar
  7. Menos mal. Cuando haga algún módulo con transacciones dejaré anotado el uso de esta anotación para que quede claro. Gracias por comentarlo. Saludos.

    ResponderEliminar