Andrés Testi Hispano

Tuesday, October 10, 2006

Propuesta para Consultas Implícitas en EJB3

Al trabajar con tecnología ORM, normalmente se sugiere dividir la lógica de negocios en dos componentes arquitectónicos. Por un lado tenemos la capa de persistencia, que agrupa a los objetos que son representados por entidades en la base de datos. Estos objetos, en principio, sirven como contenedores de información, pero tienen poca funcionalidad asociada, ya que no tienen acceso directo al API de consultas. Para realizar las consultas y el movimiento de la lógica de negocios, exíste la capa de DAO (Data Access Object), que se encarga de gestionar las transacciones, consultas, inserciones, actualizaciones, etc.. Generalmente, se sugiere que exista un DAO por cada clase persistente del modelo. El DAO puede visualizar la capa de persistencia, pero la capa de persistencia no está enterada de la existencia del DAO. Hasta aquí, no pareciera complicarse el trabajo con esta arquitectura. El problema surge con los objetos que tienen relaciones mandatorias sobre otros objetos, es decir, los objetos que mantienen relaciones de padres e hijos (en el sentido de la navegación, no de la herencia). Si tengo que actualizar un objeto hijo ¿Utilizo el DAO asociado al padre, o el DAO asociado al hijo? ¿Y si borro un objeto padre que debe borrar en cascada a sus hijos? ¿Primero invoco el DAO del hijo para borrar a cada instancia por separado?. La respuesta no es simple ni única, pero pareciera coherente, centralizar la mayor cantidad posible de lógica en la capa de persistencia, para aliviar a la capa de DAO y así minimizar las inconsistencias generadas por este patrón de diseño. En este sentido, EJB3 nos ha brindado algunas soluciones, de la mano de los denominados Entity Listeners. Los Entity Listeners nos permiten acceder de manera ortogonal al ciclo de vida de las entidades (CRUD), de una manera centralizada, quitando muchas responsabilidades de la capa de DAO. Por otro lado, la API de Hibernate Validator, también contribuye en este sentido, al agregar capacidades de validación al modelo persistente. Por último, la lógica de consultas que realiza el DAO, se ve también alivianada por las relaciones de las Entidades, que hacen consultas de manera transparente para descongelar los objetos relacionados con el objeto de interés. Pero hasta el momento, sólo existe esa única contribución al mundo de las consultas transparentes, no hay nada más que lo inherente a las relaciones. Todo el tiempo se nos presentan problemas. Por ejemplo, si el sueldo de un vendedor se calcula sumando el total de las comisiones de sus ventas, debemos realizar una consulta explícita. Para colaborar con la centralización de responsabilidades, podríamos definir la consulta dentro de un Named Query asociado a la clase Vendedor. De todos modos, la única capa de la arquitectura con la responsabilidad y capacidad de ejecutar esa consulta es el DAO:

@Entity
@NamedQuery(
name=”Vendedor.calcularSueldo”,
query=

SELECT SUM(venta.comision) “ +
FROM Vendedor v JOIN v.ventas venta ” +
WHERE v = :vendedor “
)

public class Empleado{
....
@OneToMany(fetch=LAZY, cascade={ALL}, mappedBy=”vendedor”)
private List ventas;
.....
}

public class Venta{
.....
@ManyToOne(fetch=LAZY, optional=false)
@JoinColumn(name=”VENDEDOR_ID”)
private Vendedor vendedor;

private Integer comision;
....
}

@Stateless
public class DaoVendedorBean implements DaoVendedorLocal{

@PersistenceContext
private EntityManager em;

public Long calcularSueldo(Vendedor v){
return (Long)em.createNamedQuery(“Vendedor.calcularSueldo”)
.setParameter(“vendedor”, v)
.getSingleResult();
}

}

Aquí algo parece no estar funcionando del todo bien. Supuestamente, los objetos en OOP tienen métodos propios que nos informan de su estado ¿Por qué debe ser otro objeto el que nos informe del sueldo del vendedor? Podríamos entonces, pensar en un nuevo tipo de inyección denominado Implicit Query. Supongamos que contamos con la siguiente annotation:

@Retention(RUNTIME)
@Target(METHOD)
public @interface ImplicitQuery{

String value();
String parameter() default “this”;
FetchType fetch() default EAGER;

}


En donde

value: es el nombre de alguna NamedQuery de la entidad.
parameter: es el nombre del único parametro por el que se inyecta la entidad analizada. Por default es “this”
fetch: determina en qué momento se realiza la consulta.

Entonces podríamos agregar el siguiente campo en la clase vendedor:

@ImplicitQuery(
name=”Vendedor.calcularSaldo”,
parameter=”vendedor”
)

private Long saldo;

De esta manera podríamos eliminar el método calcularSueldo del DaoVendedor, aliviando aún más la responsabilidad del Dao!!!


Adicionalmente, las Implicit Queries traerían otras ventajas:

  • Anidamiento de consultas transparente:

Supongamos que un vendedor pide adelantos de sueldo, y queremos conocer qué adelantos de sueldo han superado el total de comisiones por ventas que tiene. Esto, en la implementación actual, se haría de la manera siguiente:


SELECT v.adelanto
FROM Vendedor v
WHERE v = :vendedor AND v.adelanto > ALL(
SELECT SUM(venta.comision)
FROM Ventas venta
WHERE venta.vendedor = :vendedor
)


Si tuvieramos Implicit Query, sólo tendríamos que hacer esto:

SELECT v.adelanto
FROM Vendedor v
WHERE v = :vendedor AND v.adelanto > v.sueldo

  • Typesafe:

Si asociamos una Implicit Query a un campo o getter, el contenedor puede detectar en fase de deployment, si la propiedad soporta el tipo de dato retornado por la consulta. Esto ayudaría a detectar tempranamente los errores de tipo, reduciendo la posibilidad de que ocurran en tiempo de ejecución.

  • Polimorfismo:

Si además de Vendedores, nuestra empresa cuenta con Administrativos que ganan sus sueldos en función de las horas trabajadas, sería interesante generalizar ambas clases en una superclase Empleado. Las declaraciones quedarían asi:

public class Empleado{...}

@Entity
@NamedQuery(
name=”Administrativo.calcularSueldo”,
query=
”SELECT SUM(ht.cantidad) ” +
“FROM Administrativo a JOIN a.horasTrabajadas ht“ +
“WHERE a = :administrativo”
)

public class Administrativo extends Empleado{...}

@Entity
@NamedQuery(
name=”Vendedor.calcularSueldo”,
query=
”SELECT SUM(venta.comision) “ +
“FROM Vendedor v JOIN v.ventas venta ” +
“WHERE v = :vendedor “
)

public class Vendedor extends Empleado{....}

Entonces en la especificación actual, el DaoEmpleado, se deberia hacer esto para conocer el sueldo de un empleado:

public Long calcularSueldo(Empleado e){
if(e instanceof Administrativo){

return(Long)em
.createNamedQuery(“Administrativo.calcularSueldo”)
.setParameter(“administrativo”, e)
.getSingleResult();

}else{
return(Long)em
.createNamedQuery(“Vendedor.calcularSueldo”)
.setParameter(“vendedor”, e)
.getSingleResult();
}
}

Nada más lejos de la OOP. Además, el diseño se complicaría mucho más si hay más subclases de Empleado. Con implicit queries, éste metodo no sería necesario, simplemente definiríamos una implicit query por cada subclase de empleado, y la obtención del resultado sería de la siguiente manera:

empleado.getSueldo();

Si trasladamos esta característica al ámbito de EJBQL, la cantidad de consultas anidadas que nos ahorramos es enorme. Por ejemplo, si quisiéramos hallar todos los empleados que tengan un sueldo menor a 3000, tendríamos que anidar una consulta por cada subclase de empleado:

SELECT DISTINCT(e)
FROM Administrativo a, Vendedores v
WHERE (
a = :empleado AND 3000 > (
SELECT SUM(ht.cantidad)
FROM Administrativo a JOIN a.horasTrabajadas ht
WHERE a = :empleado
)
)OR(
v = :empleado AND 3000 > (
SELECT SUM(venta.comision)
FROM Vendedor v JOIN v.ventas venta
WHERE v = :vendedor
)

)

Utilizando Implicit Query, lo anterior se reduce a:

SELECT e
FROM Empleado e
WHERE e.sueldo < 3000

  • Simplificación de expresiones en contextos de Bean:

Las implicit queries, al evitar depender de otro objeto para su obtención, simplificarían el acceso a consultas dentro del contexto del Expression Language, porque se podría acceder al resultado navegando por el propio bean.

11 Comments:

Post a Comment

<< Home