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:
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
Tutorial anterior: http://jmagm.blogspot.com/2013/04/mejorando-el-uso-de-hibernate-en.html
Próximo tutorial: http://jmagm.blogspot.com/2013/04/reimplementar-administracion-de.html
Saludos
Mariano
7 comentarios:
Gracias por estos tutoriales me ayudan mucho.
Por nada Jonathan, la intención es esa. Si tienes alguna pregunta o inquietud no dudes en postearla.
Saludos
Mariano
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.
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
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!
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}"
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
Publicar un comentario