openxava / documentación / Lección 15: Cálculo de valor por defecto multiusuario

Curso: 1. Primeros pasos | 2. Modelo básico del dominio (1) | 3. Modelo básico del dominio (2) | 4. Refinar la interfaz de usuario | 5. Desarrollo ágil | 6. Herencia de superclases mapeadas | 7. Herencia de entidades | 8. Herencia de vistas | 9. Propiedades Java |10. Propiedades calculadas | 11. @DefaultValueCalculator en colecciones |12. @Calculation y totales de colección |13. @DefaultValueCalculator desde archivo |14. Evolución del esquema manual | 15. Cálculo de valor por defecto multiusuario |16. Sincronizar propiedades persistentes y calculadas |17. Lógica desde la base de datos |18. Validación avanzada | 19. Refinar el comportamiento predefinido | 20. Comportamiento y lógica de negocio | 21. Referencias y colecciones | A. Arquitectura y filosofía | B. Java Persistence API | C. Anotaciones | D. Pruebas automáticas

Tabla de contenidos

Lección 15: Cálculo de valor por defecto multiusuario
Métodos de retrollamadas JPA
Cálculo de valor por defecto multiusuario
Resumen
En la lección anterior, aprendimos a modificar el esquema de nuestra base de datos de manera manual con el fin de modificar los datos sin tener que borrar las tablas. Ahora veremos cómo definir cálculos por defecto para aplicar en ambientes multiusuario.
Si no te gustan los videos sigue las instrucciones a continuación.

Métodos de retrollamadas JPA

Otra forma práctica de añadir lógica de negocio a tu modelo es mediante los métodos de retrollamada JPA. Un método de retrollamada se llama en un momento específico del ciclo de vida de la entidad como objeto persistente. Es decir, puedes especificar cierta lógica a ejecutar al grabar, leer, borrar o modificar una entidad.
En esta sección veremos algunas aplicaciones prácticas de los métodos de retrollamada JPA.

Cálculo de valor por defecto multiusuario

Hasta ahora estamos calculando el número para Factura y Pedido usando @DefaultValueCalculator. Éste calcula el valor por defecto en el momento que el usuario pulsa para crear una nueva Factura o Pedido. Por tanto, si varios usuarios pulsan en el botón Nuevo al mismo tiempo todos ellos obtendrán el mismo número. Esto no es apto para aplicaciones multiusuario. La forma correcta de generar un número único es generándolo justo en el momento de grabar.
Vamos a implementar la generación del número usando métodos de retrollamada JPA. JPA permite marcar cualquier método de tu clase para ser ejecutado en cualquier momento de su ciclo de vida. Indicaremos que justo antes de grabar un DocumentoComercial calcule su número. De paso mejoraremos el cálculo para tener una numeración diferente para Pedido y Factura.
Edita la entidad DocumentoComercial y añade el método calcularNumero(). Veamos el código:
@PrePersist // Ejecutado justo antes de grabar el objeto por primera vez
private void calcularNumero() {
    Query query = XPersistence.getManager().createQuery(
        "select max(f.numero) from " +
        getClass().getSimpleName() + // De esta forma es válido para Factura y Pedido
        " f where f.anyo = :anyo");
    query.setParameter("anyo", anyo);
    Integer ultimoNumero = (Integer) query.getSingleResult();
    this.numero = ultimoNumero == null ? 1 : ultimoNumero + 1;
}
Este código es el mismo que el de CalculadorSiguienteNumeroParaAnyo pero usando getClass().getSimpleName() en lugar de "DocumentoComercial". El método getSimpleName() devuelve el nombre de la clase sin paquete, es decir, precisamente el nombre de la entidad. Será "Pedido" para Pedido y "Factura" para Factura. Así podremos obtener una numeración diferente para Factura y Pedido.
La especificación JPA establece que no puedes usar el API JPA dentro de un método de retrollamada. Por tanto, el método de arriba no es legal desde un punto de vista estricto. Pero, Hibernate (la implementación de JPA que OpenXava usa por defecto) te permite usarla en @PrePersist. Y dado que usar JPA es la forma más fácil de hacer este cálculo, nosotros lo usamos.
Ahora borra la clase CalculadorSiguienteNumeroParaAnyo de tu proyecto y modifica la propiedad numero de DocumentoComercial para que no la use:
@Column(length = 6)
//  @DefaultValueCalculator(value=CalculadorSiguienteNumeroParaAnyo.class, // Quita esto
//      properties=@PropertyValue(name="anyo")
//  )
@ReadOnly // El usuario no puede modificar el valor
int numero;
Fíjate que además de quitar @DefaultValueCalculator, hemos añadido la anotación @ReadOnly. Esto significa que el usuario no puede introducir ni modificar este número. Esta es la forma correcta de hacerlo ahora dado que el número es generado al grabar el objeto, por lo que el valor que tecleara el usuario sería sobrescrito siempre.
Prueba ahora el módulo de Factura o Pedido, verás como el número está vacío y no es editable, y cuando grabes el documento, el número se calcula y se muestra un mensaje con el año y el número recién calculado para esa factura o pedido.

Resumen

En esta lección vimos cómo definir propiedades y valores aplicables por defecto, algo muy útil si nuestra aplicación será utilizada en ambientes multiusuario o de acceso masivo. En próxima lecciones veremos otras formas de añadir lógica de negocio, y cómo sincronizar propiedades.

¿Problemas con la lección? Pregunta en el foro ¿Ha ido bien? Ve a la lección 16