Buenas Prácticas JPA y Taylor

Uso de Taylor

Esta sección busca dar algunos lineamientos o recomendaciones en el uso de Taylor.
Core5 propone usar la herramienta Taylor para modelar las entidades y sus relaciones. Taylor permite modelar las entidades y agregarles los estereotipos o anotaciones de JPA y Hibernate, para luego generar el código de estas entidades. Por esta razón se considera que Taylor es una herramienta util para agilizar el desarrollo de las aplicaciones.
El documento [TAYLOR] explica el uso de esta herramienta.
A continuación se exponen una serie de recomendaciones o buenas prácticas que se deben aplicar en los proyectos.

Manejo del modelo de entidades

Debido a que Taylor no permite el trabajo colaborativo, es decir que varias personas puedan modificar al mismo tiempo el modelo de entidades, se recomienda que una sola persona sea la encargada de realizar modificaciones en el modelo de entidades.
Es buena práctica mantener el modelo de entidades actualizado, de tal forma que en cualquier momento se puedan volver a generar las entidades para un proyecto. Para esto es necesario que el código Java de las entidades no sea modificado directamente, sino que cualquier modificación a las entidades se haga a través del modelo de entidades.
El modelo de entidades en Taylor debe tener definida la estructura de paquetes. La estructura de paquetes propuesta por Core5 consiste en realizar agrupaciones de entidades por módulos de la aplicación. Así el paquete para las entidades es com.heinsohn.{proyecto}.{modulo}. Por ejemplo para el proyecto demo, las entidades del modulo de seguridad quedan en el directorio src/main y el paquete com.heinsohn.demo.security.

Enumeraciones

En el modelo de entidades se deben usar enumeraciones siempre que se tengan campos en las entidades donde el valor de estos esté entre un conjunto de valores que no requieren ser parametrizables, es decir que son valores fijos y no van a cambiar en el tiempo. Como por ejemplo un campo estado con posibles valores: iniciado, aprobado, rechazado.
Las enumeraciones pueden crearse en la misma entidad donde se usan o pueden crearse de forma separada. La recomendación en este caso es que cuando la enumeración se usa sólo en una entidad se debe crear en la misma entidad, pero si la enumeración se va a utilizar en varias entidades se debe crear de forma separada.

Cuando un atributo de una entidad corresponde a una enumeración es buena practica usar la anotación @Enumerated(value = EnumType.STRING) para que en la base de datos no quede guardado el código del literal de la enumeración, sino que quede guardado el nombre del literal de la enumeración.

Uso de NamedQuery

Un namedQuery es una consulta que esta previamente definida y que puede ser usada a través de un nombre.
Siempre que se pueda se deben usar namedQueries para realizar consultas a la base de datos. Se considera mala practica escribir las consultas directamente en los EJBs.
Un NamedQuery debe definirse en la entidad a la cual hace referencia la consulta. Los nombres de los namedQueries deben tener un estándar definido. El estándar propuesto es:
{nombreEntidad}.{nombreConsulta}
Se debe usar el nombre de la entidad como prefijo del nombre de la consulta para evitar conflicto de nombres de consulta en diferentes entidades.
El nombre de la consulta debe indicar lo que retorna y los parámetros que recibe, de tal forma que sea fácil saber que hace una consulta sólo con su nombre, por ejemplo:

  • Usuario.usuariosPorPais: Retorna los usuarios que pertenecen a un pais especifico. Entidad: Usuario. Parametros: pais
  • Persona.personasPorSexoYEdad: Retorna las personas que tienen un sexo y edad especificas. Entidad: Persona. Parametros: sexo y edad

La definición de namedQueries se debe hacer en el modelo de Taylor. Definiendo los namedQueries desde los diagramas de Taylor, se asegura que ante cualquier modificación que sufran las entidades, por nuevos atributos, validaciones, relaciones, etc., la generación del código mantendrá las consultas que se están utilizando en los EJBs o clases utilitarias del proyecto.

Herencia

Estrategias para manejar herencia en las entidades

Estrategia Descripción Ventajas Desventajas
SINGLE_TABLE Crea una sola tabla en la BD para almacenar los registros de toda la jerarquía No requiere joins para hacer consultas Tabla no normalizada, desperdicio de espacio por columnas que van a estar nulas, tablas con muchas columnas (atributos de todas las clases de la jerarquía)
JOINED Crea una tabla por cada entidad de la jerarquía Tablas normalizadas, no hay desperdicio de espacio Las consultas causan joins, consultas lentas cuando hay jerarquías profundas
TABLE_PER_CLASS Crea una tabla por cada clase hoja de la jerarquía Consultas sobre la misma entidad no causan joins Tablas no normalizadas, desperdicio de espacio, consultas polimorficas causan queries con UNION

Concurrencia

Estrategias para manejar concurrencia en el acceso a datos

Estrategia Ventajas Desventajas Recomendación
Optimista no se mantienen locks en la base de datos requiere un atributo version en la entidad, el usuario o la aplicación deben refrescar o intentar de nuevo las actualizaciones de datos fallidas Usar en entidades que tienen poca probabilidad de modificación concurrente
Pesimista No hay que hacer algo especial en la aplicación crea locks en la base de datos (SELECT FOR UPDATE), afecta la escalabilidad, puede causar deadlocks Usar cuando hay alta probabilidad de modificación concurrente para una misma entidad

Recomendaciones

  • Usar namedQueries para evitar el uso de consultas JPQL escritas en los EJBs.
  • Las consultas deben hacer referencia a los objetos, y no a los identificadores de las entidades: En los casos que se requiera comparar con algún atributo de la entidad que no sea de tipo simple (como numéricos, cadenas, etc.) sino que sea una referencia a otra entidad, debe procurarse el uso de las comparaciones a nivel de entidades, pues Hibernate se encarga de resolver el JOIN entre tablas buscando los atributos que se anotan con @Id. A continuación se muestran dos casos para ilustrar la recomendación, donde el Tipo de Documento es una referencia a una entidad.

No recomendado: select p from persona p where p.tipoDocumento.id = :idTipoDocumento and p.numeroDocumento = :numeroDocumento
Recomendado: select p from persona p where p.tipoDocumento = :tipoDocumento and p.numeroDocumento = :numeroDocumento

  • Consultas que hacen referencia a atributos de tipo Enumeración:Cuando se requiera hacer una consulta con una referencia explícita al valor de una enumeración, debe utilizarse el nombre completo de la enumeración (incluyendo el paquete) y el valor al que se desea hacer referencia. A continuación se muestra un ejemplo de una consulta que hace referencia al valor concreto de una enumeración. Nótese en negrilla el nombre completo de la enumeración y el valor.

select t from TipoComision t where t.estado = com.heinsohn.factoring.common.EnumEstadoEntidad.HABILITADO

  • Si se necesitan construir consultas dinamicas se debe usar parametros en la consulta y no concatenar los valores en el string de la consulta, para evitar SQL injection. Por ejemplo:

No recomendado:
em.createQuery("select p from persona p where p.tipoDocumento.id = " + tipoDocId + " and p.numeroDocumento = " + numDoc);
Recomendado:
em.createQuery("select p from persona p where p.tipoDocumento.id = :param1 and p.numeroDocumento = :param2")
.setParameter("param1", tipoDocId ).setParameter("param2", numDoc);

  • Evitar el uso de queries para realizar actualizaciones de datos. Si se usa para hacer actualizaciones masivas tener en cuenta que los cambios no quedarán en el contexto de persistencia que se esté usando y que se pierde el manejo de concurrencia que provee JPA. Por ejemplo:
//consulta un empleado
Empleado empleado = em.find(id, Empleado.class);

//incrementa al doble el salario de todos los empleados
em.createQuery("update empleado set salario = salario * 2").executeUpdate();

if ( empleado.getSalario() < 10 ) {
   empleado.setSalario(15);
}

Suponiendo que el anterior código se ejecuta en una transacción. Si se consulta un empleado especifico que tiene salario igual a 9, al final de la transacción ese empleado tendrá salario 15

  • Evitar usar relaciones uno a muchos, o muchos a muchos con tipo de fetch EAGER. Si se necesita consultar entidades incluyendo las entidades hijas es preferible usar un query con JOIN FETCH. Ejemplo:
@Entity
public class Departamento {

   @OneToMany(mappedBy="departamento" fetch="EAGER")
   Collection<Empleado> empleados; 

}

Evitar la relación EAGER anterior cambiandola a LAZY y realizar consultas como la siguiente:

@Entity
@NamedQueries({
   @NamedQuery(name="departamentosConEmpleados", query="select d from Departamento d join fetch d.empleados")
})
public class Departamento {

   @OneToMany(mappedBy="departamento" fetch="LAZY")
   Collection<Empleado> empleados; 

}

En el caso anterior si la entidad Departamento no es manejada por el contexto de persistencia y se intenta acceder a la colecciòn de empleados (departamento.getEmpleados()), causa que se lance una excepción. Por eso se recomienda hacer departamento.getEmpleados().size() cuando la entidad aun esta siendo manejada por el contexto de persistencia.

  • Evitar el uso de Cascade=ALL o MERGE cuando hay jerarquias profundas. Si se desea utilizar uno de estos tipos de Cascade se debe limitar el alcance de los mismos. Los posibles tipos son: ALL, PERSIST, MERGE, REMOVE, REFRESH, la opción por defecto no hace ninguna operación en Cascade.
  • Evitar usar queries nativos, porque se pierde la portabilidad a diferentes bases de datos.
  • No usar transaccionalidad en métodos que son sólo de consulta. Se debe usar @TransactionAttribute(SUPPORTS)

Optimización

Optimización en diseño

  1. Usar los tipos de datos mas adecuados en tamaño
  2. En lo posible usar llaves primarias simples
  3. La normalización reduce la redundancia de datos, por lo que los updates serán más rápidos. Sin embargo la normalización causa que se tengan que hacer joins en las consultas, haciéndolas más lentas.
  4. Pensar en realizar partición vertical en entidades que tengan muchos atributos y no todos se necesiten en todos los casos. Por ejemplo:

Persona (Cedula, nombre, apellido, dirección, RH, edad, cantidad de hijos, estado civil, sexo, nacionalidad)
Puede cambiarse a las siguientes dos entidades:
Persona (Cedula, nombre, apellido, infoDetallePersona)
InfoDetallePersona(dirección, RH, edad, cantidad de hijos, estado civil, sexo, nacionalidad)

La optimización consiste en:

  1. Monitorear las consultas que se generan cuando se ejecutan casos de uso criticos
  2. Identificar consultas: que se demoren mucho tiempo, que se ejecuten a menudo, que carguen más datos de los necesarios
  3. Analizar las consultas que se generan: Es posible que dividir una consulta en dos o más pasos dejando a java que realice pasos intermedios, es más eficiente que ejecutar una sola consulta que traiga todos los datos de una vez.
  4. Analizar las consultas para evitar consultar datos que no se necesitan: Cuando se utiliza em.find(..) o una consulta "select d from Departamento", se cargan todos los atributos de la entidad Departamento. En ese caso si la entidad tiene atributos tipo BLOB que no se necesitan puede hacerse una consulta que excluya estos campos.
  5. Crear indices sobre columnas que se utilicen frecuentemente en la parte where de las consultas. Tener en cuenta que los indices mejoran la velocidad de las consultas pero reducen la velocidad de los updates
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License