martes, abril 09, 2013

Migrando la lista de ventas a Hibernate en nuestra aplicación PrimeFaces/JSF/Spring/Hibernate

Hola gente,

seguimos con nuestra aplicación, en esta ocasión migraremos la obtención de la lista de ventas para que sea manejada por Hibernate.

Objetivo

Utilizar la capacidad de trabajar con consultas sql nativas parametrizadas de Hibernate.

Desarrollo

En esta ocasión no es mucho el trabajo, comenzaremos configurando:

ar.com.magm.web.primefaces.VentasBean

...
...

SessionFactory sessionFactory; 

public VentasBean() {
  jsfCtx = FacesContext.getCurrentInstance();
  bundle = jsfCtx.getApplication().getResourceBundle(jsfCtx, "msg");
  sessionFactory = HibernateUtil.getSessionFactory(); 
  // processList(null);
}

...
...


Solo agregamos una instancia de SessionFactory para poder trabajar con Hibernate en las instancias de esta clase.
Luego debemos modificar el método que genera la lista de ventas:


private void processList(Object args[]) {
  meses = bundle.getString("lbl.months").split(",");
  ventas = new ArrayList<Venta>();
  zonas = new ArrayList<String>();
  Session session = sessionFactory.getCurrentSession();
  Query query = session.createSQLQuery(sql);
  if (args != null) {
    for (int t = 0; t < args.length; t++) {
      query.setParameter(0, args[t]);
    }
  }
  List<Object[]> vtaTmp = query.list();
  for (Object[] vo : vtaTmp) {
    Venta venta = new Venta(vo[2].toString(), 
                            vo[3].toString(),
                            Integer.parseInt(vo[0].toString()),
                            Integer.parseInt(vo[1].toString()),
                            meses[Integer.parseInt(vo[1].toString()) - 1],
                            Double.parseDouble(vo[4].toString()));
    ventas.add(venta);
    if (!zonas.contains(venta.getZona()))
      zonas.add(venta.getZona());
  }

  /*
  ServletContext sc = (ServletContext) FacesContext.getCurrentInstance()
                                        .getExternalContext().getContext();
  Connection cn = (Connection) sc.getAttribute("datasource");
  try {
    PreparedStatement pst = cn.prepareStatement(sql);
    if (args != null) {
      for (int t = 0; t < args.length; t++) {
        pst.setObject(t + 1, args[t]);
      }
    }
    ResultSet rs = pst.executeQuery();
    while (rs.next()) {
      String zona = rs.getString("zona");
      Venta venta = new Venta(zona, rs.getString("cliente"), rs.getInt("anio"),          
                              rs.getInt("mes"), meses[rs.getInt("mes") - 1], 
                              rs.getDouble("ventas"));
      ventas.add(venta);
      if (!zonas.contains(zona))
        zonas.add(zona);
    }
  } catch (SQLException e) {
    e.printStackTrace();
  }
  */
}


Lo primero que haremos en este método es inhabilitar todo el código antiguo, en aquel en que utilizábamos JDBC. Se puede ver que hemos utilizado comentarios de bloque: /* .. */
Luego en la línea
Query query = session.createSQLQuery(sql);
Obtenemos un objeto Query, el cual nos permite trabajar con sentencias SQL nativas, el método recibe como parámetro la consulta, que no es ni más ni menos que la que ya estábamos utilizando:
SELECT 
  year(fecha) as anio, 
  month(fecha) as mes, 
  zona, 
  cliente, 
  sum(importe*cantidad) as ventas 
FROM 
  dw_ventasfact v 
    INNER JOIN clientes c ON v.idCliente=c.idCliente 
    INNER JOIN zonas z ON z.idZona=c.idZona 
WHERE 
  cliente like ? 
GROUP BY 
  zona, cliente, anio, mes 
ORDER BY 
  anio,mes,zona,cliente
En el siguiente fragmento establecemos los valores para los parámetros, la diferencia más importante con el método anterior es que los parámetros comienzan desde 0, en JDBC es a partir de 1.
if (args != null) {
  for (int t = 0; t < args.length; t++) {
    query.setParameter(0, args[t]);
  }
}
En la líneaList<Object[]> vtaTmp = query.list();
obtenemos una lista que en cada elemento contiene un arreglo de objetos que representa una fila, en nuestro caso una entidad de ventas. El orden de los elementos del array coinciden con lo definido en la consulta SQL, en nuestro caso: 
[0]=año
[1]=mes
[2]=zona
[3]=cliente
[4]=ventas
El código que sirve para poblar la lista (el ciclo while) casi no ha cambiado, solo se reemplazó la línea que instancia la venta, ahora se utilizan los elementos del array y también se reutiliza la zona ya obtenida, por ello se quitó la primera línea en la cual se obtenía.

Esta es toda la modificación que hay que realizar en cuanto a código, ahora solo falta crear sesiones para que puedan ser utilizadas en este componente para lo cual modificaremos HibernateContextListenerAndFilter como ya lo hemos hecho antes:

...
@WebFilter(urlPatterns = { "/TestHibernateConSpring", "*.xhtml" }) 
...

Solo agregamos el patrón "*.xhtml" para que se creen sesiones ante peticiones de componentes JSF.

Bien, ya podemos probar y no notaremos ninguna diferencia, todo seguirá funcionando como antes del lado del cliente, solo que ahora  Hibernate maneja algo más. En la consola podemos ver las sentencias SQL que Hibernate envía al motor.

Espero les sea de utilidad



Como siempre el proyecto completo y el WAR en el repo de GITHUB: https://github.com/magm3333/workspace-pftuto



Saludos

Mariano

7 comentarios:

Unknown dijo...

Gracias por estos tutoriales me ayudan mucho.

Unknown dijo...

Por nada Jonathan, la intención es esa. Si tienes alguna pregunta o inquietud no dudes en postearla.
Saludos
Mariano

Jorge dijo...

Mariano, muy buen material! ahora quiero sumar mi granito de arena! Teniendo en cuenta este post y el anterior.
Gracias por tu tiempo y dedicacion.

1- Lo primero que me llamo la atencion es que llevas la Entidad JPA a la vista, hacer esto me genera varias dudas:
a) El tan famoso problema del "Detached Entity" que se da en la vista cuando intentamos acceder a una propiedad de una Entidad-JPA que ya no se encuentra en una Session de transaccion.
Imagino que por tal motivo utilizas el Open Session In View?
b) Me parece que la vista habria que abstraerla del acceso a datos( clases jpa, implementacion de hibernate), propongo
utilizar una clase DT replica de un JPA (mismas propiedades, metodos, etc) Ej: Ciente con su ClienteDT, de está forma
si en la vista accedemos a una propiedad que no fue inicializada en la Session, no habria inconveniente, porque simplemente
no se mostraria el valor de dicha propiedad, el sistema no fallaria y obligaria al dev a inicializar su valor.
Crear un DT por cada JPA implica un trabajo mas pero lo vale, si cambiamos por ejemplo, el acceso a datos de hibernate por JDBC a la vieja usansa
la vista no se enteraria. Y siempre es mas facil borrar una clase que eliminar Anotaciones, por lo menos para mi :)
Ahora como hacemos para pasar la info de un JPA a un DT y viceversa, existe una libreria llamada Dozer, que hace magia, mientras
los get/set se llamen igual mapea cada atributo de la clase A -> B o B->A.

2- Muy bueno el Generic de DAO, pero que pasa si necesito obtener un listado aplicandole filtros?
En uno de los proyectos que trabajo utilizamos Criteria de Hibernate, te olvidas de hacer un SQL a pata, te devuelve una entidad JPA
derecho, o un listado de JPA.

Criteria criteria = entityManager.getDelegate().createCriteria(Cliente.class);
criteria.add(Restrictions.eq("zona.id", 1L ));
List lst =criteria.list();

Eso te devolveria, todos los clientes de la zona 1 por ejemplo.

3- Me parece que si utilizas la anotacion de spring @Repository en las implementaciones de los DAO,
no hace falta declarar en el applicationContext.xml las clases implementadas.

Unknown dijo...

Hola Jorge,

ante todo gracias por tu comentario.
Voy a tratar de contestarte casi todos los puntos diciéndote que mi idea es ir agregando (mejorando) la aplicación en cada post, como verás comenzamos con JDBC,JSF y PrimeFaces, luego se han agregado frameworks paso a paso y explicando brevemente el porqué, de esa manera se pone de manifiesto lo bueno que es usar ciertos frameworks, aplicar ciertos patrones, etc.
En el futuro iré reimplementando componentes y aplicando patrones, por ejemplo en breve publicaré la migración de las cuentas de usuario a Hibernate y luego utilizaremos Spring MVC.
La verdad que no tengo planificado más que eso, ya que lo hago en tiempos que me sobran sin otro animo que no sea compartir experiencias y algún mínimo conocimiento.
También podrás notar que los posts tienen una orientación muy práctica, pero trato de ser detallista en cuanto a descripción de configuraciones y despliegue de componentes.
Por otro lado estoy totalmente abierto a comentarios y sobre todo aportes.
Se me había pasado por la cabeza el tema de listados multicriterios, pero aplicando patrones, ahora que comentas lo de Criteria de Hibernate sería interesante, si se te ocurre algún ejemplo, bienvenido sería.
Mi intención más fuerte es crear ejemplos del uso de PrimeFaces, pero en el medio del camino decidí agregar algunas cosillas más. Por ahora estoy armando una buena base, pero llegará un momento en el cual solo mostraré componentes de PrimeFaces.

Saludos
Mariano

Jorge dijo...

Hola Mariano, te paso un ejemplo asumiendo que contamos con una clase DT y su homonimo en JPA con sus respectivos get/set de sus propiedades-
ClienteDT
---> Date fechaNacimiento;
---> String razonSocial;
---> ZonaDT zonaId; // ZonaDT={id,descripcion}

En la vista: se podria tener una seccion de Filtro y el Listado con el resultado de cada busqueda.

------VISTA-------












------CONTROLADOR-------
Backing
@Autowired ClientesDAO oClientesDAO;
---> filtrar(){
resultados =oClientesDAO.buscar(filtroDT);
}
------IMPLEMENTACION DAO-------
ClientesDAOImpl //Asumimos que trabajamos con JPA y Hibernate.
---> List buscar(ClienteDT filtro){
//Se crea un consulta sobre una clase persistente.
Criteria criteria= ((Session)entityManager.getDelegate()).createCriteria(ClientesJPA.class);
if(filtro!=null){
if(!"".equals(filtro.getRazonSocial()))
criteria.add(Restrictions.like("razonSocial", filtro.getRazonSocial() )); // se traduce en un like %%
if(filtro.getFechaNacimiento()!=null)
criteria.add(Restrictions.ge("fechaNacimiento", filtro.getFechaNacimiento() )); // los mayores a tal fecha de nacimiento
if(filtro.getZona()!=null)
criteria.add(Restrictions.eq("zonaId.id", filtro.getZona().getId() )); // clientes de tal Zona, con propiedades anidadas.
//Mirando un poco la API de criteria, se puden hacer joins, determinar q campos interesan en el Select y lo mas practico es que te devuelve
//un lista de objetos concretos. Tambien se puede utilzar criteria.uniqueResult() si estamos buscando un solo objeto en particular.
}
//Luego de aplicar los criterios ejecutamos la consulta.-
List lst= criteria.list();

return lstDT; // convertir las clases JPA a DT o simplemente devolver JPA si lo utilizamos en la vista.
}
Este ejemplo es muy somero, espero sirva como puntapie!

Jorge dijo...

La vista, sin formato xhtml-

h:outputText value="#{msg.lbl_fecha}"
rich:calendar value="#{backing.filtroDT.fechaNacimiento}"
h:outputText value="#{msg.lbl_razon_social}"
h:inputText value="#{backing.filtroDT.razonSocial}"
h:outputText value="#{msg.lbl_zona}"
h:selectOneMenu value="#{backing.filtroDT.zonaId.id}"

Unknown dijo...

Gracias Jorge,
ya lo veré con detenimiento, actualmente estoy trabajando en un proyecto que me absorbe todo el tiempo. Te pido que al código me lo envíes a mi mail (magm3333@gmail.com) parta que no queden tan caóticos .los comentarios.
Saludos
Mariano

Etiquetas

pentaho (45) java (35) eclipse (23) jdbc (14) curso (13) tomcat (13) primefaces (12) db2 (11) mondrian (10) review (10) jsf (9) openI (9) pdi (9) prd (9) libro (8) plugin (8) musql (7) struts (7) javascript (6) spring (6) cdf (5) ctools (5) instalar (5) linux (5) mysql (5) data studio (4) hibernate (4) ireport (4) jasper (4) meteor (4) videocurso (4) eglu (3) eglubi (3) elearning (3) graphite (3) grupo eglu (3) jboos tools (3) mexico (3) openbits (3) packt (3) python (3) undec (3) websphere (3) applet (2) cde (2) dao (2) db2university (2) exelearning (2) flexigrid (2) hadoop (2) iua (2) kettle (2) moodle (2) node (2) olap (2) osbi (2) pivot4j (2) scorm (2) sql (2) stpivot (2) actionscript (1) amazon (1) autenticacion (1) avanzado (1) base de datos (1) big data (1) bigdata (1) bodoc (1) cambiar (1) ccc (1) cdc (1) chat (1) cloud (1) coffeescript (1) control de acceso (1) corti (1) csv (1) cuba (1) curso meteor undec (1) dashboard (1) datamart (1) dataptix.tv (1) datasource (1) datatable (1) db2 ExpressC (1) demonio (1) distancia (1) driver (1) driver jdbc (1) eglufiltertwolist (1) encapsulamiento (1) especialización (1) etl (1) excepciones (1) export (1) faces (1) federación (1) filas afectadas (1) filtertwolist (1) filtrado (1) flegrid (1) flex (1) google (1) google viz (1) hostname (1) html (1) i18n (1) ibm (1) identidad (1) indignación (1) instancias (1) inteligencia de negocios (1) jee (1) jpivot (1) l10n (1) la azada (1) la zaga de los confines (1) layout (1) liberado (1) libre (1) libro promoción (1) lob (1) marktplace (1) menu (1) meteor node javascript google oauth autenticacion (1) mobile (1) mongoDB (1) node.js (1) oauth (1) olap4j (1) open source (1) orm (1) persistencia (1) personalizada (1) prd5 (1) psw (1) publicidad (1) rad6 (1) recursividad (1) reporting (1) rock (1) saiku (1) script (1) servicio (1) sessiontimeout (1) sourceforge (1) spinneta (1) sqlserver (1) ssl (1) taller (1) troyanx (1) ubuntu (1) ucc (1) ui (1) web (1) web console (1) xampp (1) xml (1) xpath (1)

Seguidores