ItEr47S06DocumentacionFormacionItEr46S06: Fix rst format problems in compilation

Modified format related to indentation and emphasis
This commit is contained in:
Jose Maria Casanova Crespo 2010-02-09 13:51:36 +01:00
parent c089051fdd
commit 81f5e7e588
4 changed files with 113 additions and 86 deletions

View file

@ -5,4 +5,4 @@ NavalPlan: Guía de desenvolvemento
.. image:: images/logo.png .. image:: images/logo.png
:align: left :align: left
TODO: Esta é a guia de desenvolvemnto de NavalPlan Esta é a guia de desenvolvemento da aplicación NavalPlan

View file

@ -169,17 +169,23 @@ Timeplot
TODO TODO
---- ----
* Ferramentas
* Ferramentas externas: Cutycap * Ferramentas externas: Cutycap
* JFreeChart. * JFreeChart.
* YUI. Libreria Javascript para compoñentes. * YUI. Libreria Javascript para compoñentes.
* JasperReports para informes. * JasperReports para informes.
* Jgrapht. Grafo de tarefas. * Jgrapht. Grafo de tarefas.
* Servicios web REST. * Servicios web REST.
* JAX-RS API. * JAX-RS API.
* Apache CXF. * Apache CXF.
* Framework Spring security. Para autenticación. * Framework Spring security. Para autenticación.
* Hibernate Validator para validacións. * Hibernate Validator para validacións.
* Framework auxiliares: * Framework auxiliares:
* JODA * JODA
* Gettext para internacionalizacion. * Gettext para internacionalizacion.
* Apache Math. * Apache Math.

View file

@ -191,6 +191,6 @@ Logo o integrador deberá responder a lista confirmando a aplicación do parche.
Trucos con GIT Trucos con GIT
============== ==============
Cando estase resolvendo conflictos podense ver os distintos pais do merge. Empregar =git show :1:filename= para ver o ancestro comun, =git show :2:filename= para ver a version local, =git show :3:filename= para ver a version remota. Para escoller unha das versions empregar =git checkout --ours file= e =git checkout --theirs file=. (Solo en git 1.6.1 e posteriores) Cando estase resolvendo conflictos podense ver os distintos pais do merge. Empregar *git show :1:filename* para ver o ancestro comun, *git show :2:filename* para ver a version local, *git show :3:filename* para ver a version remota. Para escoller unha das versions empregar *git checkout --ours file* e *git checkout --theirs file*. (Solo en git 1.6.1 e posteriores)
Podese usar un =git mergetool= para resolver os conflictos empregando unha ferramenta visual. Podese usar un *git mergetool* para resolver os conflictos empregando unha ferramenta visual.

View file

@ -26,7 +26,7 @@ Conversacións
Conceptos xerais Conceptos xerais
---------------- ----------------
Unha **conversación** representa un caso de uso no que o usuario emprega unha serie de **pasos* para editar/crear un ou varios obxectos persistentes, e finalmente executa un paso para confirmar ou cancela-los cambios. Como exemplo de conversación, **podemos** considera-la edición/creación dos datos dun traballador, na que o usuario emprega unha serie de pasos para modifica-la información básica do traballador (e.g. nome) e modifica/elimina/engade datos da súa historia laboral (e.g. data de contratación) ou das súas localizacións de traballo. Unha **conversación** representa un caso de uso no que o usuario emprega unha serie de **pasos** para editar/crear un ou varios obxectos persistentes, e finalmente executa un paso para confirmar ou cancela-los cambios. Como exemplo de conversación, **podemos** considera-la edición/creación dos datos dun traballador, na que o usuario emprega unha serie de pasos para modifica-la información básica do traballador (e.g. nome) e modifica/elimina/engade datos da súa historia laboral (e.g. data de contratación) ou das súas localizacións de traballo.
Dende o punto de vista dun usuario final, unha conversación ten que cumprir dúas propiedades: Dende o punto de vista dun usuario final, unha conversación ten que cumprir dúas propiedades:
@ -106,39 +106,57 @@ Nesta sección preséntase un exemplo do caso máis sinxelo de realización dunh
_`Figura 3` : Listado de traballadores. _`Figura 3` : Listado de traballadores.
Neste exemplo, para crear ou edita-los datos dun traballador, o usuario accede a unha pantalla que lle amosa un listado cós traballadores existentes (`figura 3`_). A continuación, utiliza o botón Create para crear un novo traballador, ou o botón Edit dun traballador existente para edita-los seus datos. Neste exemplo, para crear ou edita-los datos dun traballador, o usuario accede a unha pantalla que lle amosa un listado cós traballadores existentes (`figura 3`_). A continuación, utiliza o botón Create para crear un novo traballador, ou o botón *Edit* dun traballador existente para edita-los seus datos.
Ambos botóns inician a conversación de creación ou edición do datos dun traballador. En ambos casos, o usuario accede a un wizard que contén tres pestanas para edita-los datos do traballador, un botón Save para confirmar tódolos cambios e un botón Cancel para cancela-los cambios. A primeira das pestanas (`figura 4`_) permite edita-los datos básicos do traballador. A segunda pestana (`figura 5`_) dispón de botóns para engadir ou eliminar localizacións do traballador. A terceira pestana (`figura 6`_) dispón de botóns para engadir, eliminar ou editar items da historia laboral (data de contratación, baixas laborais, etc.) do traballador. A `figura 7`_ amosa a edición/creación dun item da historia laboral do traballador. Unha vez que o usuario completa a edición dos datos, remata a conversación pulsando en Save (para materializa-los datos na base de datos) ou en Cancel (para descartalos). En ambos casos, vólvese ó listado xeral de traballadores (`figura 3`_). Ambos botóns inician a conversación de creación ou edición do datos dun traballador. En ambos casos, o usuario accede a un *wizard* que contén tres pestanas para edita-los datos do traballador, un botón *Save* para confirmar tódolos cambios e un botón *Cancel* para cancela-los cambios. A primeira das pestanas (`figura 4`_) permite edita-los datos básicos do traballador. A segunda pestana (`figura 5`_) dispón de botóns para engadir ou eliminar localizacións do traballador. A terceira pestana (`figura 6`_) dispón de botóns para engadir, eliminar ou editar items da historia laboral (data de contratación, baixas laborais, etc.) do traballador. A `figura 7`_ amosa a edición/creación dun item da historia laboral do traballador. Unha vez que o usuario completa a edición dos datos, remata a conversación pulsando en *Save* (para materializa-los datos na base de datos) ou en *Cancel* (para descartalos). En ambos casos, vólvese ó listado xeral de traballadores (`figura 3`_).
.. image:: images/02-ediciontraballador.png
:alt: Edición dos datos básicos dun traballador
:width: 200
_`Figura 4`: Edición dos datos básicos dun traballador. _`Figura 4`: Edición dos datos básicos dun traballador.
.. image:: images/03-edicionlocalizacions.png
:alt: Edición das localizacións dun traballador
:width: 200
_`Figura 5`: Edición das localizacións dun traballador. _`Figura 5`: Edición das localizacións dun traballador.
.. image:: images/04-edicionhistoriatraballador.png
:alt: Edición da historia laboral dun traballador.
:width: 200
_`Figura 6`: Edición da historia laboral dun traballador. _`Figura 6`: Edición da historia laboral dun traballador.
.. image:: images/05-edicionitemhistoriatraballador.png
:alt: Edición/creación dun item da historia laboral dun traballador
:width: 200
_`Figura 7`: Edición/creación dun item da historia laboral dun traballador. _`Figura 7`: Edición/creación dun item da historia laboral dun traballador.
A `figura 8`_ amosa as entidades que modelan os datos dun traballador. Un traballador é un tipo de recurso (Resource). A clase Worker contén os datos básicos do traballador. Cada traballador ten un conxunto de CriterionSatisfaction asociados (relación bidireccional 1:N). Unha instancia de CriterionSatisfaction modela de maneira xenérica algún tipo de situación laboral dun recurso nun intervalo temporal. En particular, cada localización dun traballador e cada item da súa historia laboral modélase coma una instancia de CriterionSatisfaction. En consecuencia, os CriterionSatisfaction dun recurso representan a súa situación laboral ó longo do tempo. Cada CriterionSatisfaction está asociado cunha instancia dun Criterion. Un Criterion representan unha clase de situación laboral, coma por exemplo, contratación ou baixa médica, no caso de relacións laborais, ou Coruña ou Pontevedra no caso de localizacións. Finalmente, é importante entender que os datos dun traballador veñen dados por unha instancia da clase Worker e os seus CriterionSatisfaction asociados. As instancias de Criterion son globais a tódolos traballadores, dado que representan clases de situacións laborais. A `figura 8`_ amosa as entidades que modelan os datos dun traballador. Un traballador é un tipo de recurso (*Resource*). A clase *Worker* contén os datos básicos do traballador. Cada traballador ten un conxunto de *CriterionSatisfaction* asociados (relación bidireccional 1:N). Unha instancia de *CriterionSatisfaction* modela de maneira xenérica algún tipo de situación laboral dun recurso nun intervalo temporal. En particular, cada localización dun traballador e cada item da súa historia laboral modélase coma una instancia de *CriterionSatisfaction*. En consecuencia, os *CriterionSatisfaction* dun recurso representan a súa situación laboral ó longo do tempo. Cada *CriterionSatisfaction* está asociado cunha instancia dun *Criterion*. Un *Criterion* representan unha clase de situación laboral, coma por exemplo, contratación ou baixa médica, no caso de relacións laborais, ou Coruña ou Pontevedra no caso de localizacións. Finalmente, é importante entender que os datos dun traballador veñen dados por unha instancia da clase *Worker* e os seus *CriterionSatisfaction* asociados. As instancias de *Criterion* son globais a tódolos traballadores, dado que representan clases de situacións laborais.
.. image:: images/06-modelotraballador.png
:alt: Entidades que modelan os datos dun traballador.
:width: 200
_`Figura 8`: Entidades que modelan os datos dun traballador. _`Figura 8`: Entidades que modelan os datos dun traballador.
A `figura 9`_ amosa a estrutura do modelo (sección 4) que representa a interacción do usuario para crear/edita-los datos dun traballador. A interface do modelo ven dada por IWorkerModel. WorkerModelImpl realiza a interface IWorkerModel. A clase WorkerModelImpl modélase como un servizo de Spring con estado, que entre outras cousas, contén o traballador en creación/edición. A `figura 9`_ amosa a estrutura do modelo (sección 4) que representa a interacción do usuario para crear/edita-los datos dun traballador. A interface do modelo ven dada por *IWorkerModel*. *WorkerModelImpl* realiza a interface *IWorkerModel*. A clase *WorkerModelImpl* modélase como un servizo de Spring con estado, que entre outras cousas, contén o traballador en creación/edición.
.. image:: images/07-modeloediciontraballador.png
:alt: Modelo para a edición dos datos dun traballador.
:width: 200
_`Figura 9`: Modelo para a edición dos datos dun traballador. _`Figura 9`: Modelo para a edición dos datos dun traballador.
A continuación coméntase o protocolo da conversación. Cando o usuario desexa visualiza-lo listado de traballadores (`figura 3`_), o controlador invoca a operación IWorkerModel::getWorkers. Se o usuario pulsa o botón Create ou o botón Edit dun traballador, o controlador invoca o método IWorkerModel::initCreate ou IWorkerModel::initEdit, respectivamente, iniciándose así a conversación para crear ou edita-los datos dun traballador. No primeiro caso, inicialízase o atributo worker de WorkerModelImpl creándose un Worker en memoria que non ten ningún CriterionSatisfaction asociado. No segundo caso, inicialízase o atributo worker recuperándose o Worker da base de datos mediante o DAO IWorkerDao. Sempre que o controlador precisa mostralos datos (e.g. datos básicos, localizacións e items da historia laboral) do worker en edición/creación, invoca IWorkerModel::getWorker, que devolve o worker almacenado en WorkerModelImpl. Resource dispón de métodos para devolve-los conxuntos de localizacións actuais e pasadas (`figura 5`_), e os items da historia laboral (`figura 6`_). Estes métodos son simples operacións de conveniencia que retornan o subconxunto [#]_ apropiado dos CriterionSatisfaction do recurso (traballador). A continuación coméntase o protocolo da conversación. Cando o usuario desexa visualiza-lo listado de traballadores (`figura 3`_), o controlador invoca a operación *IWorkerModel::getWorkers*. Se o usuario pulsa o botón Create ou o botón Edit dun traballador, o controlador invoca o método *IWorkerModel::initCreate* ou *IWorkerModel::initEdit*, respectivamente, iniciándose así a conversación para crear ou edita-los datos dun traballador. No primeiro caso, inicialízase o atributo worker de *WorkerModelImpl* creándose un *Worker* en memoria que non ten ningún *CriterionSatisfaction* asociado. No segundo caso, inicialízase o atributo worker recuperándose o *Worker* da base de datos mediante o DAO *IWorkerDao*. Sempre que o controlador precisa mostralos datos (e.g. datos básicos, localizacións e items da historia laboral) do worker en edición/creación, invoca *IWorkerModel::getWorker*, que devolve o worker almacenado en *WorkerModelImpl*. Resource dispón de métodos para devolve-los conxuntos de localizacións actuais e pasadas (`figura 5`_), e os items da historia laboral (`figura 6`_). Estes métodos son simples operacións de conveniencia que retornan o subconxunto [#]_ apropiado dos *CriterionSatisfaction* do recurso (traballador).
.. [#] O controlador pode ordenar (e.g. por data de inicio) este subconxunto empregando un java.util.TreeSet. .. [#] O controlador pode ordenar (e.g. por data de inicio) este subconxunto empregando un java.util.TreeSet.
Para mostra-la lista ordenada de localizacións non asignadas actualmente ó traballador (`figura 5`_), o controlador invoca a IWorkerModel:getNonAssignedLocationCriteria. Cada vez que o usuario selecciona un conxunto de localizacións para asignar ou des-asignar, o controlador invoca a IWorkerModel::assignLocations ou IWorkerModel::unassignLocations. Cada vez que o usuario engade, modifica ou elimina un item da historia laboral (`figura 6`_), o controlador invoca IWorkerModel::addLaboralHistoryItem, IWorkerModel::updateLaboralHistoryItem ou IWorkerModel::removeLaboralHistoryItem. Para poder mostra-la lista desplegable (ordenada) Para mostra-la lista ordenada de localizacións non asignadas actualmente ó traballador (`figura 5`_), o controlador invoca a *IWorkerModel:getNonAssignedLocationCriteria*. Cada vez que o usuario selecciona un conxunto de localizacións para asignar ou des-asignar, o controlador invoca a *IWorkerModel::assignLocations* ou *IWorkerModel::unassignLocations*. Cada vez que o usuario engade, modifica ou elimina un item da historia laboral (`figura 6`_), o controlador invoca *IWorkerModel::addLaboralHistoryItem*, *IWorkerModel::updateLaboralHistoryItem* ou *IWorkerModel::removeLaboralHistoryItem*. Para poder mostra-la lista desplegable (ordenada) do formulario da `figura 7`_, o controlador invoca *IWorkerModel::getLaboralCriteria*. A edición dos datos básicos (`figura 4`_) non supón ningunha interacción especial con *IWorkerModel* (os datos básicos non se validan ata o paso de confirmación). Finalmente, cando o usuario remata o proceso de edición de datos, se pulsa o botón Save, o controlador invoca a operación *IWorkerModel::confirm*. Esta operación materializa o atributo worker de *WorkerModelImpl* na base de datos. Se decide pulsa-lo botón Cancel, o controlador invoca a operación *IWorkerModel::cancel* para resetea-lo estado. En ambos casos (confirmación ou cancelación), o controlador invoca posteriormente a operación *IWorkerModel::getWorkers* para amosa-lo listado de traballadores.
do formulario da `figura 7`_, o controlador invoca IWorkerModel::getLaboralCriteria. A edición dos datos básicos (`figura 4`_) non supón ningunha interacción especial con IWorkerModel (os datos básicos non se validan ata o paso de confirmación). Finalmente, cando o usuario remata o proceso de edición de datos, se pulsa o botón Save, o controlador invoca a operación IWorkerModel::confirm. Esta operación materializa o atributo worker de WorkerModelImpl na base de datos. Se decide pulsa-lo botón Cancel, o controlador invoca a operación IWorkerModel::cancel para resetea-lo estado. En ambos casos (confirmación ou cancelación), o controlador invoca posteriormente a operación IWorkerModel::getWorkers para amosa-lo listado de traballadores.
A continuación coméntase a realización dalgunhas operacións de IWorkerModel na clase WorkerModelImpl. A `figura 10`_ amosa a realización da operación getWorkers. Esta operación simplemente delega no método homólogo do DAO IWorkerDao. Obsérvese que se empregou a anotación @Transactional có atributo readOnly = true. Tódalas operacións de WorkerModelImpl correspondentes a un paso inicial ou intermedio, e que usen directa ou indirectamente DAOs, empregarán esta mesma anotación, dado que: (1) os métodos dos DAOs só se poden empregar no contexto dunha transacción (@Transactional), e (2) as operacións correspondes a un paso inicial ou intermedio non modifican a base de datos (readOnly = true). A continuación coméntase a realización dalgunhas operacións de *IWorkerModel* na clase *WorkerModelImpl*. A `figura 10`_ amosa a realización da operación *getWorkers*. Esta operación simplemente delega no método homólogo do DAO *IWorkerDao*. Obsérvese que se empregou a anotación *@Transactional* có atributo *readOnly = true*. Tódalas operacións de *WorkerModelImpl* correspondentes a un paso inicial ou intermedio, e que usen directa ou indirectamente DAOs, empregarán esta mesma anotación, dado que: (1) os métodos dos DAOs só se poden empregar no contexto dunha transacción (*@Transactional*), e (2) as operacións correspondes a un paso inicial ou intermedio non modifican a base de datos (*readOnly = true*).
:: ::
@ -147,9 +165,9 @@ A continuación coméntase a realización dalgunhas operacións de IWorkerModel
return workerDao.getWorkers(); return workerDao.getWorkers();
} }
_`Figura 10`: WorkerModelImpl::getWorkers. _`Figura 10`: *WorkerModelImpl::getWorkers*.
A `figura 11`_ amosa a operación WorkerModelImpl::initEdit. A operación inicializa o atributo worker cargando a instancia de Worker a partir do identificador do traballador. Dado que as relacións en Hibernate por defecto son lazy (independentemente da cardinalidade), o worker non terá inicializada a colección (java.util.Set) de CriterionSatisfaction. Sen embargo, as operacións correspondentes ós pasos intermedios precisan traballar cós CriterionSatisfaction do traballador, e cós Criterion asociados a cada CriterionSatisfaction. Do mesmo, xeito, a capa vista tamén asume que pode navegar polos CriterionSatisfaction (e Criterion) do traballador que devolve o método WorkerModelImpl::getWorker. Por este motivo, o código recorre a lista de CriterionSatifaction (a primeira iteración causa que a lista se inicialice) e invoca un método que non sexa get<<Clave>> sobre cada Criterion asociado, para inicializa-lo proxy do Criterion asociado ó CriterionSatisfaction desa iteración. O método get<<Clave>> é o único método que non causa a inicalización dun proxy, suxeito a que se empregue o tipo de acceso property para a clave (que é o tipo de acceso por defecto para tódalas propiedadespersistentes). É importante observar que esta incialización faise dende WorkerModelImpl no canto de facela dende un método de negocio da entidade Resource, dado que son as clase modelo as que coñecen as relacións que é preciso inicializar para levar a cabo a conversación. A `figura 11`_ amosa a operación *WorkerModelImpl::initEdit*. A operación inicializa o atributo worker cargando a instancia de *Worker* a partir do identificador do traballador. Dado que as relacións en Hibernate por defecto son *lazy* (independentemente da cardinalidade), o *worker* non terá inicializada a colección (*java.util.Set*) de *CriterionSatisfaction*. Sen embargo, as operacións correspondentes ós pasos intermedios precisan traballar cós *CriterionSatisfaction* do traballador, e cós *Criterion* asociados a cada *CriterionSatisfaction*. Do mesmo, xeito, a capa vista tamén asume que pode navegar polos *CriterionSatisfaction* (e *Criterion*) do traballador que devolve o método *WorkerModelImpl::getWorker*. Por este motivo, o código recorre a lista de *CriterionSatifaction* (a primeira iteración causa que a lista se inicialice) e invoca un método que non sexa get<<Clave>> sobre cada *Criterion* asociado, para inicializa-lo proxy do *Criterion* asociado ó *CriterionSatisfaction* desa iteración. O método *get<<Clave>>* é o único método que non causa a inicalización dun proxy, suxeito a que se empregue o tipo de acceso property para a clave (que é o tipo de acceso por defecto para tódalas propiedadespersistentes). É importante observar que esta incialización faise dende *WorkerModelImpl* no canto de facela dende un método de negocio da entidade Resource, dado que son as clase modelo as que coñecen as relacións que é preciso inicializar para levar a cabo a conversación.
:: ::
@ -163,9 +181,9 @@ A `figura 11`_ amosa a operación WorkerModelImpl::initEdit. A operación inicia
_`Figura 11`: WorkerModelImpl::initEdit. _`Figura 11`: WorkerModelImpl::initEdit.
O método getWorker (`figura 12`_) simplemente devolve o atributo worker. A `figura 13`_ amosa a realización do método updateLaboralHistoryItem. O método recibe un CriterionSatisfaction que actúa como Data Transfer Object (DTO) [#]_ , é dicir contén os datos do formulario de edición (`figura 7`_) así como o identificador do CriterionSatisfaction (o valor do atributo version é irrelevante). Obsérvese que non sería correcto que a operación recibise o CriterionSatisfaction destino modificado polo controlador, dado que se a modificación non é correcta e o usuario na corrixe, o estado do worker quedaría inconsistente e podería afectar a validacións de interaccións posteriores. A operación Resource::updateCriterionSatisfactioncomproba se é posible modifica-lo CriterionSatisfaction do worker cós datos recibidos no DTO. En caso de selo, copia os atributos do DTO no CriterionSatisfaction do worker, excepto os atributos id e version. Neste exemplo, a comprobación que realiza o método updateCriterionSatisfaction só se reduce a iterar por tódolos CriterionSatisfaction do worker (excepto o CriterionSatisfaction obxectivo) e comprobar que non haxa ningunha incompatibilidade cós datos recibidos no DTO (e.g. o rango temporal do DTO non debe solaparse con outros CriterionSatisfaction). En consecuencia, non se require facer un reattachment do worker na sesión de Hibernate, dado que a lóxica do método updateCriterionSatisfaction só itera polo conxunto CriterionSatisfaction asociados, que xa están cargados en memoria (no caso da edición do traballador, WorkerModelImpl::initEdit provoca a inicialización do conxunto de CriterionSatisfaction inicial, e tanto na edición coma na creación, os items da historia laboral vanse engadindo ó conxunto CriterionSatisfaction con WorkerModelImpl::addLaboralHistoryItem). Finalmente, tamén e importante darse conta que o método Resource::updateCriterionSatisfaction levanta a excepción ResourceConstraintException en caso de que non sexa posible actualiza-lo item da historia laboral (a capas vista/controlador deben notificar este error ó usuario final). O método *getWorker* (`figura 12`_) simplemente devolve o atributo *worker*. A `figura 13`_ amosa a realización do método *updateLaboralHistoryItem*. O método recibe un *CriterionSatisfaction* que actúa como Data Transfer Object (DTO) [#]_ , é dicir contén os datos do formulario de edición (`figura 7`_) así como o identificador do *CriterionSatisfaction* (o valor do atributo version é irrelevante). Obsérvese que non sería correcto que a operación recibise o *CriterionSatisfaction* destino modificado polo controlador, dado que se a modificación non é correcta e o usuario na corrixe, o estado do worker quedaría inconsistente e podería afectar a validacións de interaccións posteriores. A operación *Resource::updateCriterionSatisfaction*comproba se é posible modifica-lo *CriterionSatisfaction* do worker cós datos recibidos no DTO. En caso de selo, copia os atributos do DTO no *CriterionSatisfaction* do worker, excepto os atributos *id* e *version*. Neste exemplo, a comprobación que realiza o método *updateCriterionSatisfaction* só se reduce a iterar por tódolos *CriterionSatisfaction* do worker (excepto o *CriterionSatisfaction* obxectivo) e comprobar que non haxa ningunha incompatibilidade cós datos recibidos no DTO (e.g. o rango temporal do DTO non debe solaparse con outros *CriterionSatisfaction*). En consecuencia, non se require facer un reattachment do worker na sesión de Hibernate, dado que a lóxica do método *updateCriterionSatisfaction* só itera polo conxunto *CriterionSatisfaction* asociados, que xa están cargados en memoria (no caso da edición do traballador, *WorkerModelImpl::initEdit* provoca a inicialización do conxunto de *CriterionSatisfaction* inicial, e tanto na edición coma na creación, os items da historia laboral vanse engadindo ó conxunto *CriterionSatisfaction* con *WorkerModelImpl::addLaboralHistoryItem*). Finalmente, tamén e importante darse conta que o método *Resource::updateCriterionSatisfaction* levanta a excepción *ResourceConstraintException* en caso de que non sexa posible actualiza-lo item da historia laboral (a capas vista/controlador deben notificar este error ó usuario final).
.. [#] Neste caso é posible reusa-la entidade CriterionSatisfaction coma un DTO, dado que esta entidade contén tódolos datos necesarios. Noutro caso, sería necesario definir unha clase pura DTO, é dicir, unha clase non persistente que contén os datos necesarios. .. [#] Neste caso é posible reusa-la entidade *CriterionSatisfaction* coma un DTO, dado que esta entidade contén tódolos datos necesarios. Noutro caso, sería necesario definir unha clase pura DTO, é dicir, unha clase non persistente que contén os datos necesarios.
:: ::
@ -173,7 +191,7 @@ O método getWorker (`figura 12`_) simplemente devolve o atributo worker. A `fig
return worker; return worker;
} }
_`Figura 12`: WorkerModelImpl::getWorker. _`Figura 12`: *WorkerModelImpl::getWorker*
:: ::
@ -183,13 +201,11 @@ _`Figura 12`: WorkerModelImpl::getWorker.
worker.updateCriterionSatisfaction(newDataDto); worker.updateCriterionSatisfaction(newDataDto);
} }
_`Figura 13`: WorkerModelImpl::updateLaboralHistoryItem. _`Figura 13`: *WorkerModelImpl::updateLaboralHistoryItem.*
A `figura 14`_ mostra o código da operación WorkerModelImpl::confirm. O código encárgase de tres aspectos: (1) valida-los datos básicos do traballador mediante Worker::validateBasicData (que pode devolver ResourceConstraintException), (2) actualiza-lo worker e os seus CriterionSatisfaction asociados en base de datos e (3) resetea-lo estado. Con respecto ó primeiro aspecto, é importante recordar que a edición dos datos básicos do traballador non supónningunha interacción con IWorkerModel. Por contra, durante a edición dos datos básicos do A `figura 14`_ mostra o código da operación *WorkerModelImpl::confirm*. O código encárgase de tres aspectos: (1) valida-los datos básicos do traballador mediante *Worker::validateBasicData* (que pode devolver *ResourceConstraintException*), (2) actualiza-lo worker e os seus *CriterionSatisfaction* asociados en base de datos e (3) resetea-lo estado. Con respecto ó primeiro aspecto, é importante recordar que a edición dos datos básicos do traballador non supónningunha interacción con *IWorkerModel*. Por contra, durante a edición dos datos básicos do traballador, o controlador modifica directamente o obxecto retornado por *IWorkerModel::getWorker* (o estado da conversación). En consecuencia, no paso de confirmacióné preciso valida-los datos básicos. É importante observar que se os valores dos datos básicos influíran no resto de validacións (e.g. nos items da historia laboral), o controlador non debería traballar directamente có obxecto retornado por *IWorkerModel::getWorker*, senón empregar un DTO cós datos básicos do traballador e pasalo como parámetro en *IWorkerModel::confirm* (dado que valores erróneos nos datos básicos poderían influír negativamente noutras validacións mentres o usuario non os corrixa), de maneira similar a como se fixo con *IWorkerModel::updateLaboralHistoryItem* (`figura 13`_). Así mesmo, tamén sería preciso facer unha validación de tódolos datos do traballador (datos básicos e conxunto de *CriterionSatisfaction*) en *WorkerModelImpl::confirm*.
traballador, o controlador modifica directamente o obxecto retornado por
IWorkerModel::getWorker (o estado da conversación). En consecuencia, no paso de confirmacióné preciso valida-los datos básicos. É importante observar que se os valores dos datos básicos influíran no resto de validacións (e.g. nos items da historia laboral), o controlador non debería traballar directamente có obxecto retornado por IWorkerModel::getWorker, senón empregar un DTO cós datos básicos do traballador e pasalo como parámetro en IWorkerModel::confirm (dado que valores erróneos nos datos básicos poderían influír negativamente noutras validacións mentres o usuario non os corrixa), de maneira similar a como se fixo con IWorkerModel::updateLaboralHistoryItem (`figura 13`_). Así mesmo, tamén sería preciso facer unha validación de tódolos datos do traballador (datos básicos e conxunto de CriterionSatisfaction) en WorkerModelImpl::confirm.
Con respecto ó segundo aspecto, o código invoca a operación IGenericDao::save, que como se explicou anteriormente (apartado `Conceptos específicos a Hibernate`_), causa un reattachment do worker na sesión de Hibernate, de maneira que cando se faga o commit da transacción, Hibernate engadirá ou actualizará o worker na base de datos. Dado que se pretende que tamén se actualicen en base de datos (actualizándose/engadíndose/elimándose) os CriterionSatisfaction asociados ó worker, é preciso facer que o reattachment do worker se propague en cascada ós CriterionSatisfaction asociados. Para elo, basta empregar unha opción de cascada no mapping da relación entre Resource e CriterionSatisfaction que como mínimo inclúa save-update. Ademais, como cada instancia dun CriterionSatisfaction só se referenciada dende un só recurso, e dende ningunha outra entidade, para provocar que os CriterionSatisfaction eliminados (items da historia laboral) no só se desasocien do traballador en edición, senón que tamén se eliminen da base de datos automaticamente, é posible emprega-la opción delete-orphan. A `figura 15`_ amosa o uso da opción all-delete-orphan, que é sinónima de tódalas opcións de cascada (concretamente é sinónimo de “all, delete-orphan”). Obsérvese tamén que o paso de confirmación non emprega o elemento *readOnly = true* na anotación *@Transactional*. Con respecto ó segundo aspecto, o código invoca a operación *IGenericDao::save*, que como se explicou anteriormente (apartado `Conceptos específicos a Hibernate`_), causa un *reattachment* do *worker* na sesión de Hibernate, de maneira que cando se faga o commit da transacción, Hibernate engadirá ou actualizará o *worker* na base de datos. Dado que se pretende que tamén se actualicen en base de datos (actualizándose/engadíndose/elimándose) os *CriterionSatisfaction* asociados ó *worker*, é preciso facer que o reattachment do worker se propague en cascada ós *CriterionSatisfaction* asociados. Para elo, basta empregar unha opción de cascada no mapping da relación entre Resource e *CriterionSatisfaction* que como mínimo inclúa *save-update*. Ademais, como cada instancia dun *CriterionSatisfaction* só se referenciada dende un só recurso, e dende ningunha outra entidade, para provocar que os *CriterionSatisfaction* eliminados (items da historia laboral) no só se desasocien do traballador en edición, senón que tamén se eliminen da base de datos automaticamente, é posible emprega-la opción *delete-orphan*. A `figura 15`_ amosa o uso da opción *all-delete-orphan*, que é sinónima de tódalas opcións de cascada (concretamente é sinónimo de “*all*, *delete-orphan*”). Obsérvese tamén que o paso de confirmación non emprega o elemento *readOnly = true* na anotación *@Transactional*.
:: ::
@ -219,9 +235,9 @@ _`Figura 14`: WorkerModelImpl::confirm.
... ...
</class> </class>
_`Figura 15`: Uso de opcións de cascada na relación entre Resource e CriterionSatisfaction. _`Figura 15`: Uso de opcións de cascada na relación entre *Resource* e *CriterionSatisfaction*.
A `figura 16`_ amosa a operación WorkerModelImpl::cancel, que simplemente resetea o estado de WorkerModelImpl, descartando os cambios feitos. A `figura 16`_ amosa a operación *WorkerModelImpl::cancel*, que simplemente resetea o estado de *WorkerModelImpl*, descartando os cambios feitos.
:: ::
@ -229,9 +245,9 @@ A `figura 16`_ amosa a operación WorkerModelImpl::cancel, que simplemente reset
resetState(); resetState();
} }
_`Figura 16`: WorkerModelImpl::cancel. _`Figura 16`: *WorkerModelImpl::cancel*
Coma se explicou no apartado `Conceptos específicos a Hibernate`_, se dous usuarios están editando os datos dun mesmo traballador concorrentemente, un confirmará (IWorkerModel::confirm) os datos antes que o outro. O paso de confirmación (`figura 14`_), provoca un reattachment do worker en edición, que se propaga en cascada ós CriterionSatisfaction asociados debido ó uso da opción de cascada ilustrada na `figura 15`_ (en particular, debido a semántica asociada a save-update). É importante entender que Hibernate sempre considera como dirty os obxectos dos que se fai un reattachment, tanto se se modificaron coma se non (cando se fai un reattachment dun obxecto, Hibernate non sabe se foi modificando antes de facelo reattachment, polo que sempre o considera dirty). Neste caso, isto quere dicir que o paso de confirmación sempre provoca unha actualización dos datos básicos do traballador e a actualización/inserción/eliminación dos CriterionSatisfaction asociados, cós conseguintes incrementos de versión no worker e nos CriterionSatisfaction que xa existían, tanto se se modificaron coma senón. En consecuencia, o usuario que execute o paso de confirmación en segundo lugar, recibirá a excepción OptimisticLockingFailureException, dado que o número de versión do seu worker e menor que o actual en base de datos. Esta excepción debe capturarse dende a capa Web (seguramente de maneira xenérica) para informar ó usuario de que outro usuario estaba a actualiza-los datos do mesmo traballador e confirmou os cambios antes que ela/el. Coma se explicou no apartado `Conceptos específicos a Hibernate`_, se dous usuarios están editando os datos dun mesmo traballador concorrentemente, un confirmará (*IWorkerModel::confirm*) os datos antes que o outro. O paso de confirmación (`figura 14`_), provoca un *reattachment* do *worker* en edición, que se propaga en cascada ós *CriterionSatisfaction* asociados debido ó uso da opción de cascada ilustrada na `figura 15`_ (en particular, debido a semántica asociada a *save-update*). É importante entender que Hibernate sempre considera como *dirty* os obxectos dos que se fai un reattachment, tanto se se modificaron coma se non (cando se fai un reattachment dun obxecto, Hibernate non sabe se foi modificando antes de facelo reattachment, polo que sempre o considera *dirty*). Neste caso, isto quere dicir que o paso de confirmación sempre provoca unha actualización dos datos básicos do traballador e a actualización/inserción/eliminación dos *CriterionSatisfaction* asociados, cós conseguintes incrementos de versión no *worker* e nos *CriterionSatisfaction* que xa existían, tanto se se modificaron coma senón. En consecuencia, o usuario que execute o paso de confirmación en segundo lugar, recibirá a excepción *OptimisticLockingFailureException*, dado que o número de versión do seu *worker* e menor que o actual en base de datos. Esta excepción debe capturarse dende a capa Web (seguramente de maneira xenérica) para informar ó usuario de que outro usuario estaba a actualiza-los datos do mesmo traballador e confirmou os cambios antes que ela/el.
:: ::
@ -245,13 +261,13 @@ Coma se explicou no apartado `Conceptos específicos a Hibernate`_, se dous usua
_`Figura 17`: Mellora de WorkerModelImpl::updateLaboralHistoryItem. _`Figura 17`: Mellora de WorkerModelImpl::updateLaboralHistoryItem.
IGenericDao proporciona a operación checkVersion que permite detecta-la anterior situación de concorrencia sen esperar ó paso de confirmación. A `figura 17`_ amosa unha mellora ó método WorkerModelImpl::updateLoaboralHistoryItem, que a diferencia da versión anterior (`figura 13`_), invoca primeiramente ó método IGenericDao::checkVersion. Este método lanza unha consulta á base de datos para comprobar se o número de versión do obxecto recibido como parámetro é o mesmo que en base de datos. En caso negativo, lanza a excepción OptimisticLockingFailureException. Se a clave ou a versión da entidade é null, ou a entidade non existen en base de datos, o método considera a comprobación correcta. Isto permite trata-la creación e a edición de obxectos de maneira unificada (e.g. na `figura 17`_ o traballador pasado a IGenericDao::checkVersion pode ser un traballador en creación ou un en edición). O método require que a entidade pasada como parámetro dispoña dos métodos getId (para devolve-la clave) e getVersion (para devolve-lo campo version). Esta mesma idea pódese aplicar ó resto de métodos correspondentes a pasos intermedios de modificación (e.g. addLaboralHistoryItem, removeLaboralHistoryItem, etc.), ou incluso, a calquera paso intermedio. Desta maneira, cada vez que se intenta executar un paso intermedio, compróbase se ten sentido permitir que o usuario continúe editando os datos. *IGenericDao* proporciona a operación *checkVersion* que permite detecta-la anterior situación de concorrencia sen esperar ó paso de confirmación. A `figura 17`_ amosa unha mellora ó método *WorkerModelImpl::updateLoaboralHistoryItem*, que a diferencia da versión anterior (`figura 13`_), invoca primeiramente ó método *IGenericDao::checkVersion*. Este método lanza unha consulta á base de datos para comprobar se o número de versión do obxecto recibido como parámetro é o mesmo que en base de datos. En caso negativo, lanza a excepción *OptimisticLockingFailureException*. Se a clave ou a versión da entidade é *null*, ou a entidade non existen en base de datos, o método considera a comprobación correcta. Isto permite trata-la creación e a edición de obxectos de maneira unificada (e.g. na `figura 17`_ o traballador pasado a *IGenericDao::checkVersion* pode ser un traballador en creación ou un en edición). O método require que a entidade pasada como parámetro dispoña dos métodos *getId* (para devolve-la clave) e *getVersion* (para devolve-lo campo *version*). Esta mesma idea pódese aplicar ó resto de métodos correspondentes a pasos intermedios de modificación (e.g. *addLaboralHistoryItem*, *removeLaboralHistoryItem*, etc.), ou incluso, a calquera paso intermedio. Desta maneira, cada vez que se intenta executar un paso intermedio, compróbase se ten sentido permitir que o usuario continúe editando os datos.
Finalmente queda por tratar un aspecto ó que aínda non se lle prestou atención: a asociación dunha instancia de Criterion cada vez que o controlador ten que crear unha instancia de CriterionSatisfaction, é dicir, cada vez que invoca a IWorkerModel::assignLocations, IWorkerModel::addLaboralHistoryItem ou IWorkerModel::updateLaboralHistoryItem. O encargado de facer esta asociación é o controlador, dado que é este quen se encarga de crea-las instancias de CriterionSatisfaction que se pasan ás anteriores operacións. O controlador asigna o Criterion apropiado a partir dos Criterion retornados por IWorkerModel::getLaboralCriteria e IWorkerModel::getNonAssignedLocationCriteria. A `figura 18`_ amosa a realización destes dous métodos na clase WorkerModelImpl. Os métodos fan uso dos atributos privados laboralCriteria e locationCriteria. O primeiro representa co conxunto total de Criterion que modelan clases de situacións laborais (e.g. contratación, baixa médica, etc.). O segundo representa o conxunto total de Criterion que modelan localizacións. Ambos atributos son inicializados en cada un dos pasos iniciais da conversación mediante o método privado initCriteria, que recupera os dous conxuntos de Criterion da base de datos. O método WorkerModelImpl::getLaboralCriteria simplemente devolve o atributo laboralCriteria, mentres que o método WorkerModelImpl.getNonAssignedLocationCriteria devolve o subconxunto de localizacións actualmente non asignadas. Finalmente queda por tratar un aspecto ó que aínda non se lle prestou atención: a asociación dunha instancia de *Criterion* cada vez que o controlador ten que crear unha instancia de *CriterionSatisfaction*, é dicir, cada vez que invoca a *IWorkerModel::assignLocations*, *IWorkerModel::addLaboralHistoryItem* ou *IWorkerModel::updateLaboralHistoryItem*. O encargado de facer esta asociación é o controlador, dado que é este quen se encarga de crea-las instancias de *CriterionSatisfaction* que se pasan ás anteriores operacións. O controlador asigna o *Criterion* apropiado a partir dos *Criterion* retornados por *IWorkerModel::getLaboralCriteria* e *IWorkerModel::getNonAssignedLocationCriteria*. A `figura 18`_ amosa a realización destes dous métodos na clase *WorkerModelImpl*. Os métodos fan uso dos atributos privados *laboralCriteria* e *locationCriteria*. O primeiro representa co conxunto total de *Criterion* que modelan clases de situacións laborais (e.g. contratación, baixa médica, etc.). O segundo representa o conxunto total de *Criterion* que modelan localizacións. Ambos atributos son inicializados en cada un dos pasos iniciais da conversación mediante o método privado *initCriteria*, que recupera os dous conxuntos de *Criterion* da base de datos. O método *WorkerModelImpl::getLaboralCriteria* simplemente devolve o atributo *laboralCriteria*, mentres que o método *WorkerModelImpl.getNonAssignedLocationCriteria* devolve o subconxunto de localizacións actualmente non asignadas.
É importante darse conta que o código da `figura 18`_ garante que tódolos CriterionSatisfaction do traballador usarán a mesma instancia de Criterion por cada tipo de Criterion que empreguen. Neste caso, esta garantía non é necesaria. Sen embargo, é unha boa práctica non ter obxectos duplicados en memoria, e ademais algúns mappings de Hibernate requiren esta garantía á hora de executa-lo paso de confirmación. Por exemplo, se a relación entre CriterionSatisfaction e Criterion fose 1:N e unidireccional (un CriterionSatisfaction dispón dun conxunto de Criterion) e se empregase unha táboa intermedia para mapea-la relación [#]_, o paso de confirmación produciría org.hibernate.NonUniqueObjectException (en adiante NonUniqueObjectException) se hai máis dunha instancia dun mesmo tipo de Criterion entre os CriterionSatisfaction do traballador. Existen casos máis complexos, nos que incluso é necesario garantir non só que non haxa instancias repetidas, senón tamén que estean engadidas na sesión de Hibernate. No apartado `Técnicas avanzadas`_ presentarase unha mellora á solución da `figura 18`_ para garantir adicionalmente este último aspecto. É importante darse conta que o código da `figura 18`_ garante que tódolos *CriterionSatisfaction* do traballador usarán a mesma instancia de *Criterion* por cada tipo de *Criterion* que empreguen. Neste caso, esta garantía non é necesaria. Sen embargo, é unha boa práctica non ter obxectos duplicados en memoria, e ademais algúns mappings de Hibernate requiren esta garantía á hora de executa-lo paso de confirmación. Por exemplo, se a relación entre *CriterionSatisfaction* e *Criterion* fose 1:N e unidireccional (un *CriterionSatisfaction* dispón dun conxunto de *Criterion*) e se empregase unha táboa intermedia para mapea-la relación [#]_, o paso de confirmación produciría *org.hibernate.NonUniqueObjectException* (en adiante NonUniqueObjectException) se hai máis dunha instancia dun mesmo tipo de *Criterion* entre os *CriterionSatisfaction* do traballador. Existen casos máis complexos, nos que incluso é necesario garantir non só que non haxa instancias repetidas, senón tamén que estean engadidas na sesión de Hibernate. No apartado `Técnicas avanzadas`_ presentarase unha mellora á solución da `figura 18`_ para garantir adicionalmente este último aspecto.
.. [#] Sería preciso empregar unha táboa intermedia se as instancias de Criterion puideran ter relacións exclusivas con varias entidades. .. [#] Sería preciso empregar unha táboa intermedia se as instancias de *Criterion* puideran ter relacións exclusivas con varias entidades.
:: ::
@ -299,8 +315,8 @@ Finalmente queda por tratar un aspecto ó que aínda non se lle prestou atenció
} }
_`Figura 18`: Xestión do conxunto total de Criterion da historia laboral e localizacións en _`Figura 18`: Xestión do conxunto total de *Criterion* da historia laboral e localizacións en
WorkerModelImpl. *WorkerModelImpl*.
Técnicas avanzadas Técnicas avanzadas
@ -309,14 +325,19 @@ Técnicas avanzadas
Reattachment de obxectos modificados en pasos intermedios Reattachment de obxectos modificados en pasos intermedios
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Para ilustra-la necesidade de facer reattachments en pasos intermedios, así como unha serie de problemas asociados que poder ser necesario ter en conta, a `figura 19`_ engade unha entidade con respecto ó diagrama que amosa a `figura 8`_. A nova entidade, ResourceGroup, representa un grupo de recursos e ten unha relación bidireccional 1:N con Resource (un grupo de recursos está formado por varios recursos e un recurso só pode pertencer a un grupo). Cada grupo de recursos impón unha serie de localizacións actuais obrigatorias ós seus membros en total, é dicir, o grupo debe ter “presencia” nesas localizacións. En consecuencia, o conxunto de localizacións obrigatorias do grupo debe estar contido no conxunto total das localizacións actuais dos seus recursos. As localizacións obrigatorias están modeladas inicialmente [#]_ coma un conxunto de nomes (e.g. A Coruña, Pontevedra, etc.), e corresponden ó atributo name dos Criterion que representan localizacións. Para ilustra-la necesidade de facer *reattachments* en pasos intermedios, así como unha serie de problemas asociados que poder ser necesario ter en conta, a `figura 19`_ engade unha entidade con respecto ó diagrama que amosa a `figura 8`_. A nova entidade, *ResourceGroup*, representa un grupo de recursos e ten unha relación bidireccional 1:N con *Resource* (un grupo de recursos está formado por varios recursos e un recurso só pode pertencer a un grupo). Cada grupo de recursos impón unha serie de localizacións actuais obrigatorias ós seus membros en total, é dicir, o grupo debe ter “presencia” nesas localizacións. En consecuencia, o conxunto de localizacións obrigatorias do grupo debe estar contido no conxunto total das localizacións actuais dos seus recursos. As localizacións obrigatorias están modeladas inicialmente [#]_ coma un conxunto de nomes (e.g. A Coruña, Pontevedra, etc.), e corresponden ó atributo *name* dos *Criterion* que representan localizacións.
.. [#] Máis adiante, as localizacións obrigatorias modelaranse coma un conxunto de Criterion. .. [#] Máis adiante, as localizacións obrigatorias modelaranse coma un conxunto de Criterion.
.. image:: images/08-modeloadicionentidade.png
:alt: Adición da entidade ResourceGroup o diagrama da figura 8.
:width: 200
_`Figura 19`: Adición da entidade ResourceGroup o diagrama da `figura 8`_. _`Figura 19`: Adición da entidade ResourceGroup o diagrama da `figura 8`_.
Por simplicidade e consistencia coa interface gráfica do apartado `Un exemplo inicial`_ (figuras 3-7), asumirase que a creación dos datos dun traballador non ten que encargarse da asignación ó grupo de traballo, senón que isto último faise dende outro caso de uso (e.g. xestión de grupos). En consecuencia, a restrición de localizacións obrigatorias só afecta á des-asignación de localizacións (`figura 5`_) no caso de edición dos datos dun traballador. A `figura 20`_ amosa o código relevante dun “primeiro intento” para contemplar esta nova restrición. Por unha banda, a operación WorkerModelImpl::unassignLocations (`figura 20`_), despois de comprobar se a versión do worker segue sendo a mesma que en base de datos, elimina as localizacións recibidas por parámetro do conxunto de CriterionSatisfaction do worker mediante o método Resource:removeCriterionSatisfactions. Posteriormente, no caso de edición dos datos do traballador, compróbase se o grupo ó que pertence o traballador segue cumprindo a restrición de localizacións obrigatorias, e en caso negativo, vólvense a engadi-las localizacións que se pretendían des-asignar ó traballador. A comprobación da restrición de localizacións obrigatorias faise a través do método ResourceGroup::validateLocations (`figura 21`_). Este método calcula o conxunto de localizacións actuais (aquelas con data final non especificada ou superior á data actual) como o conxunto das localizacións actuais de tódolos recursos do grupo, e comproba se contén as localizacións obrigatorias do grupo. Por simplicidade e consistencia coa interface gráfica do apartado `Un exemplo inicial`_ (figuras 3-7), asumirase que a creación dos datos dun traballador non ten que encargarse da asignación ó grupo de traballo, senón que isto último faise dende outro caso de uso (e.g. xestión de grupos). En consecuencia, a restrición de localizacións obrigatorias só afecta á des-asignación de localizacións (`figura 5`_) no caso de edición dos datos dun traballador. A `figura 20`_ amosa o código relevante dun “primeiro intento” para contemplar esta nova restrición. Por unha banda, a operación *WorkerModelImpl::unassignLocations* (`figura 20`_), despois de comprobar se a versión do *worker* segue sendo a mesma que en base de datos, elimina as localizacións recibidas por parámetro do conxunto de *CriterionSatisfaction* do *worker* mediante o método *Resource:removeCriterionSatisfactions*. Posteriormente, no caso de edición dos datos do traballador, compróbase se o grupo ó que pertence o traballador segue cumprindo a restrición de localizacións obrigatorias, e en caso negativo, vólvense a engadi-las localizacións que se pretendían des-asignar ó traballador. A comprobación da restrición de localizacións obrigatorias faise a través do método *ResourceGroup::validateLocations* (`figura 21`_). Este método calcula o conxunto de localizacións actuais (aquelas con data final non especificada ou superior á data actual) como o conxunto das localizacións actuais de tódolos recursos do grupo, e comproba se contén as localizacións obrigatorias do grupo.
:: ::
@ -337,7 +358,7 @@ Por simplicidade e consistencia coa interface gráfica do apartado `Un exemplo i
} }
_`Figura 20`: WorkerModelImpl::unassignLocations (primeiro intento). _`Figura 20`: *WorkerModelImpl::unassignLocations* (primeiro intento).
:: ::
@ -353,10 +374,10 @@ _`Figura 20`: WorkerModelImpl::unassignLocations (primeiro intento).
} }
} }
_`Figura 21`: ResourceGroup::validateLocations. _`Figura 21`: *ResourceGroup::validateLocations*.
A solución anterior non é correcta dado que cando se execute a liña worker.getResourceGroup().validateLocations() no método WorkerModelImpl::unassignLocations (`figura 20`_), produciríase a excepción org.hibernate.LazyInitializationException (en adiante LazyInitializationException), dado que, por exemplo, o método WorkerModelImpl:initEdit non inicializa a referencia ó ResourceGroup (é un proxy sen inicializar). Poderíase pensar que unha solución a este problema consiste en facer que WorkerModelImpl::initEdit inicialice tamén a referencia ó ResourceGroup. Aínda así, cando se executase a iteración sobre os recursos do grupo no método ResourceGroup::validateLocations (`figura 21`_), produciríase outra vez LazyInitializationException, dado que o ResourceGroup non ten inicializada a colección (java.util.Set) de recursos. De novo, podería pensarse en modificar WorkerModelImpl::initEdit para que tamén inicializase os recursos do grupo (e os CriterionSatisfaction/Criterion de cada un deles). No obstante, esta solución non é correcta dado que tanto o ResourceGroup coma os seus recursos (cós seus CriterionSatisfaction) acabarán en memoria, como parte do estado de WorkerModelImpl, de maneira que as validacións faranse contra as localizacións dos traballadores en memoria, que poden ser distintas ás que hai en base de datos. Para elo, basta pensar en dúas conversacións concorrentes que modifican as localizacións de dous traballadores distintos dun mesmo grupo. Se unha conversación elimina a localización A do traballador 1 e outra a mesma localización A do traballador 2, de maneira que a localización A era obrigatoria e ningún outro recurso do grupo tiña esa localización, ambas conversacións confirman os cambios (porque en memoria sempre hai un traballador que está na localización A), deixando a base de datos nun estado inconsistente (ningún traballador do grupo estará na localización A). Outra solución alternativa a forza-la carga do ResourceGroup, e os seus recursos asociados, consiste en facer un reattachment (e aplica-las técnicas que se comentan mais adiante) do worker no método WorkerModelImpl::unassignLocations antes de navegar ó ResourceGroup. No obstante, isto conduce ó mesmo problema: o ResourceGroup e os seus recursos asociados rematan formando parte do estado da conversación (o que se poría de manifesto nos seguintes pasos da conversación). Finalmente, tamén é importante darse conta de que ambas alternativas conducen a ter como estado da conversacións obxectos persistentes que non son editados pola conversación, o que conceptualmente quizais non é correcto. A solución anterior non é correcta dado que cando se execute a liña *worker.getResourceGroup().validateLocations()* no método *WorkerModelImpl::unassignLocations* (`figura 20`_), produciríase a excepción *org.hibernate.LazyInitializationException* (en adiante *LazyInitializationException*), dado que, por exemplo, o método *WorkerModelImpl:initEdit* non inicializa a referencia ó *ResourceGroup* (é un proxy sen inicializar). Poderíase pensar que unha solución a este problema consiste en facer que *WorkerModelImpl::initEdit* inicialice tamén a referencia ó *ResourceGroup*. Aínda así, cando se executase a iteración sobre os recursos do grupo no método *ResourceGroup::validateLocations* (`figura 21`_), produciríase outra vez *LazyInitializationException*, dado que o *ResourceGroup* non ten inicializada a colección (*java.util.Set*) de recursos. De novo, podería pensarse en modificar *WorkerModelImpl::initEdit* para que tamén inicializase os recursos do grupo (e os *CriterionSatisfaction*/*Criterion* de cada un deles). No obstante, esta solución non é correcta dado que tanto o *ResourceGroup* coma os seus recursos (cós seus *CriterionSatisfaction*) acabarán en memoria, como parte do estado de *WorkerModelImpl*, de maneira que as validacións faranse contra as localizacións dos traballadores en memoria, que poden ser distintas ás que hai en base de datos. Para elo, basta pensar en dúas conversacións concorrentes que modifican as localizacións de dous traballadores distintos dun mesmo grupo. Se unha conversación elimina a localización A do traballador 1 e outra a mesma localización A do traballador 2, de maneira que a localización A era obrigatoria e ningún outro recurso do grupo tiña esa localización, ambas conversacións confirman os cambios (porque en memoria sempre hai un traballador que está na localización A), deixando a base de datos nun estado inconsistente (ningún traballador do grupo estará na localización A). Outra solución alternativa a forza-la carga do *ResourceGroup*, e os seus recursos asociados, consiste en facer un *reattachment* (e aplica-las técnicas que se comentan mais adiante) do *worker* no método *WorkerModelImpl::unassignLocations* antes de navegar ó *ResourceGroup*. No obstante, isto conduce ó mesmo problema: o *ResourceGroup* e os seus recursos asociados rematan formando parte do estado da conversación (o que se poría de manifesto nos seguintes pasos da conversación). Finalmente, tamén é importante darse conta de que ambas alternativas conducen a ter como estado da conversacións obxectos persistentes que non son editados pola conversación, o que conceptualmente quizais non é correcto.
:: ::
@ -383,18 +404,18 @@ A solución anterior non é correcta dado que cando se execute a liña worker.ge
} }
_`Figura 22`: WorkerModelImpl::unassignLocations (segundo intento). _`Figura 22`: *WorkerModelImpl::unassignLocations* (segundo intento).
As `figura 22`_ presenta unha nova versión de WorkerModelImpl::unassignLocations que solventa os problemas anteriores. A diferencia da solución anterior, o ResourceGroup non se obtén por navegación a partir do worker, senón que se recupera da base de datos a partir do seu identificador. En consecuencia, a comprobación faise sempre contra os datos reais que hai na base de datos. Con respecto ó código da `figura 22`_, é importante destacar varios aspectos: As `figura 22`_ presenta unha nova versión de *WorkerModelImpl::unassignLocations* que solventa os problemas anteriores. A diferencia da solución anterior, o ResourceGroup non se obtén por navegación a partir do worker, senón que se recupera da base de datos a partir do seu identificador. En consecuencia, a comprobación faise sempre contra os datos reais que hai na base de datos. Con respecto ó código da `figura 22`_, é importante destacar varios aspectos:
* A invocación worker.getResourceGroup().getId() non provoca que o proxy do ResourceGroup asociado ó worker se inicialize, dado que o método get<<Clave>> é o único método que non provoca a inicialización dun proxy (sempre que o tipo de acceso da clave sexa property). * A invocación *worker.getResourceGroup().getId()* non provoca que o proxy do *ResourceGroup* asociado ó *worker* se inicialize, dado que o método *get<<Clave>>* é o único método que non provoca a inicialización dun proxy (sempre que o tipo de acceso da clave sexa *property*).
* Para que o método ResourceGroup::validateLocations (`figura 21`_) funcione correctamente, no caso de edición dos datos do traballador, o worker que se está a editar debe ser un dos recursos do atributo resources. Para que isto sexa posible, é preciso que cando Hibernate lance a consulta correspondente á inicialización do conxunto resources, o worker xa estea engadido na sesión de Hibernate (de maneira que Hibernate descarte o obxecto coa mesma clave retornado pola consulta), é dicir, é preciso facer un reattachment (IGenericDao::save) do worker. Por este motivo, o método WorkerModelImpl::unassignResources fai un reattachment do worker antes de face-la validación. * Para que o método *ResourceGroup::validateLocations* (`figura 21`_) funcione correctamente, no caso de edición dos datos do traballador, o *worker* que se está a editar debe ser un dos recursos do atributo resources. Para que isto sexa posible, é preciso que cando Hibernate lance a consulta correspondente á inicialización do conxunto resources, o worker xa estea engadido na sesión de Hibernate (de maneira que Hibernate descarte o obxecto coa mesma clave retornado pola consulta), é dicir, é preciso facer un *reattachment* (*IGenericDao::save*) do *worker*. Por este motivo, o método *WorkerModelImpl::unassignResources* fai un *reattachment* do *worker* antes de face-la validación.
* En xeral, o reattachment deberíase facer dende as clases modelo, dado que son elas as que definen o estado da conversación, maximizando así a reusabilidade dos métodos de negocio das entidades. * En xeral, o *reattachment* deberíase facer dende as clases modelo, dado que son elas as que definen o estado da conversación, maximizando así a reusabilidade dos métodos de negocio das entidades.
* Emprégase a anotación @Transactional có elemento readOnly = true. Por unha banda, o uso desta anotación é conceptualmente correcto dado que se accede á base de datos e non se pretende realizar ningunha modificación. Por outra banda, o uso desta anotación é necesario, dado que a operación IGenericDao::save causa a execución de Session::saveOrUpdate. Como xa se dixo, esta operación provoca un reattachment do obxecto pasado como parámetro. Como mínimo, isto provoca que Hibernate considere ó obxecto pasado como parámetro como dirty, de maneira que cando Spring faiga o commit da transacción, Hibernate, en principio, faría un flush de sesión, o que provocaría que os obxectos dirty (e os novos) se actualicen en base de datos. Dado que un paso intermedio dunha conversación non debería modifica-la base de datos, é preciso indicarlle a Hibernate que non faga o flush. Para elo, basta emprega-lo elemento readOnly = true. Este elemento provoca que Spring fixe o modo de flush da sesión de Hibernate a FlashMode.MANUAL, de maneira o flush non ocorre a menos que o desenvolvedor o provoque explicitamente (e.g. Session::flush). * Emprégase a anotación *@Transactional* có elemento *readOnly = true*. Por unha banda, o uso desta anotación é conceptualmente correcto dado que se accede á base de datos e non se pretende realizar ningunha modificación. Por outra banda, o uso desta anotación é necesario, dado que a operación *IGenericDao::save* causa a execución de *Session::saveOrUpdate*. Como xa se dixo, esta operación provoca un *reattachment* do obxecto pasado como parámetro. Como mínimo, isto provoca que Hibernate considere ó obxecto pasado como parámetro como *dirty*, de maneira que cando Spring faiga o *commit* da transacción, Hibernate, en principio, faría un *flush* de sesión, o que provocaría que os obxectos *dirty* (e os novos) se actualicen en base de datos. Dado que un paso intermedio dunha conversación non debería modifica-la base de datos, é preciso indicarlle a Hibernate que non faga o flush. Para elo, basta emprega-lo elemento *readOnly = true*. Este elemento provoca que Spring fixe o modo de *flush* da sesión de Hibernate a *FlashMode.MANUAL*, de maneira o *flush* non ocorre a menos que o desenvolvedor o provoque explicitamente (e.g. *Session::flush*).
Finalmente, na `figura 23`_ móstrase a nova versión do paso de confirmación. A diferencia da versión anterior (`figura 14`_), agora é preciso valida-las localizacións do worker, dado que aínda que se validaron en WorkerModelImpl::unassignLocations, entre a última execución deste paso e a execución do paso de confirmación, outra conversación concorrente puido altera-las localizacións doutro traballador do mesmo grupo, de maneira que a condición de validación das localizacións xa non se cumpra. Neste punto, o lector pode pensar en que, dado que é preciso facer esta comprobación no paso de confirmación, pódese simplifica-la realización de WorkerModelImpl::unassignLocations de maneira que no se faga ningunha comprobación nese paso. Desta forma, non sería preciso facer un reattachment en WorkerModelImpl::unassignLocations, e o código non tería que tratar coas complexidades asociadas ós reattachments en pasos intermedios (que se comentan a continuación). Como contrapartida, non se advertiría ó usuario de posibles erros nas localizacións ata o paso de confirmación. En calquera caso, a efectos de ilustra-los posibles problemas (e as solucións) que poden provoca-los reattachments en pasos intermedios, no resto do apartado, asumirase que é preciso face-lo reattachment en WorkerModelImpl::unassignLocations (e.g. como parte dos requisitos é preciso informar de posibles errores canto antes ó usuario final). Finalmente, na `figura 23`_ móstrase a nova versión do paso de confirmación. A diferencia da versión anterior (`figura 14`_), agora é preciso valida-las localizacións do *worker*, dado que aínda que se validaron en *WorkerModelImpl::unassignLocations*, entre a última execución deste paso e a execución do paso de confirmación, outra conversación concorrente puido altera-las localizacións doutro traballador do mesmo grupo, de maneira que a condición de validación das localizacións xa non se cumpra. Neste punto, o lector pode pensar en que, dado que é preciso facer esta comprobación no paso de confirmación, pódese simplifica-la realización de *WorkerModelImpl::unassignLocations* de maneira que no se faga ningunha comprobación nese paso. Desta forma, non sería preciso facer un reattachment en *WorkerModelImpl::unassignLocations*, e o código non tería que tratar coas complexidades asociadas ós reattachments en pasos intermedios (que se comentan a continuación). Como contrapartida, non se advertiría ó usuario de posibles erros nas localizacións ata o paso de confirmación. En calquera caso, a efectos de ilustra-los posibles problemas (e as solucións) que poden provoca-los reattachments en pasos intermedios, no resto do apartado, asumirase que é preciso face-lo reattachment en *WorkerModelImpl::unassignLocations* (e.g. como parte dos requisitos é preciso informar de posibles errores canto antes ó usuario final).
Tamén é importante observa-lo uso do elemento rollbackFor na anotación @Transactional. Por defecto, Spring fai un rollback cando un método anotado con @Transactional levanta unha excepción de runtime, pero non cando se produce unha excepción checked. Asumindo que a excepción ResourceConstraintException sexa checked, o comportamento por defecto, non é suficiente para o correcto funcionamento de WorkerModelImpl::confirm, dado que se o método ResourceGroup::validateLocations levanta a excepción ResourceConstraintException, o worker e os seus CriterionSatisfaction actualizaranse en base de datos, dado que antes fíxose un reattachment do worker (recórdese que no paso de confirmación non se emprega readOnly = true). Para provocar que Spring faga un rollback se se produce a excepción ResourceConstraintException, emprégase o elemento rollbackFor, que permite especificar unha ou varias excepcións para as que Spring debe facer un rollback (adicionalmente, Spring segue facendo rollback das excepcións de runtime, a menos que se exclúan có elemento noRollbackFor). Tamén é importante observa-lo uso do elemento *rollbackFor* na anotación *@Transactional*. Por defecto, Spring fai un rollback cando un método anotado con *@Transactional* levanta unha excepción de runtime, pero non cando se produce unha excepción checked. Asumindo que a excepción *ResourceConstraintException* sexa checked, o comportamento por defecto, non é suficiente para o correcto funcionamento de *WorkerModelImpl::confirm*, dado que se o método *ResourceGroup::validateLocations* levanta a excepción *ResourceConstraintException*, o worker e os seus *CriterionSatisfaction* actualizaranse en base de datos, dado que antes fíxose un *reattachment* do *worker* (recórdese que no paso de confirmación non se emprega *readOnly = true*). Para provocar que Spring faga un rollback se se produce a excepción *ResourceConstraintException*, emprégase o elemento *rollbackFor*, que permite especificar unha ou varias excepcións para as que Spring debe facer un rollback (adicionalmente, Spring segue facendo rollback das excepcións de runtime, a menos que se exclúan có elemento *noRollbackFor*).
:: ::
@ -414,15 +435,15 @@ Tamén é importante observa-lo uso do elemento rollbackFor na anotación @Trans
} }
_`Figura 23`: WorkerModelImpl::confirm. _`Figura 23`: *WorkerModelImpl::confirm*.
Desafortunadamente, o uso de readOnly = true para facer reattachments en pasos intermedios non é suficiente en determinados escenarios, coma o caso deste exemplo. Neste caso, o reattachment do worker ocorre en cascada ós CriterionSatisfaction, o que da lugar ós seguintes problemas: Desafortunadamente, o uso de *readOnly = true* para facer reattachments en pasos intermedios non é suficiente en determinados escenarios, coma o caso deste exemplo. Neste caso, o reattachment do *worker* ocorre en cascada ós *CriterionSatisfaction*, o que da lugar ós seguintes problemas:
* Pode haber novos CriterionSatisfaction, correspondentes a localizacións ou items da historia laboral engadidos polo usuario. Estes CriterionSatisfaction non terán o identificador xerado, polo que IGenericDao::save (Sesssion::saveOrUpdate) terá que xeralo. Asumino que as entidades empreguen a estratexia native, Hibernate usará unha secuencia ou fará unha inserción na táboa na que se mapea a entidade CriterionSatisfaction, dependendo de se a base de datos soporta secuencias ou columnas contador, respectivamente. No primeiro caso, o único efecto lateral consiste en “n” incrementos na secuencia correspondente. Este efecto lateral pódese considerar irrelevante. No segundo caso, os novos CriterionSatisfaction teríanse que engadir á base de datos. Este efecto lateral, por contra, é incompatible coa semántica asociada a un paso intermedio (ademais, provoca unha excepción, dado que a inserción de obxectos é incompatible coa semántica asociada a readOnly = true). * Pode haber novos *CriterionSatisfaction*, correspondentes a localizacións ou items da historia laboral engadidos polo usuario. Estes *CriterionSatisfaction* non terán o identificador xerado, polo que *IGenericDao::save* (*Sesssion::saveOrUpdate*) terá que xeralo. Asumino que as entidades empreguen a estratexia *native*, Hibernate usará unha secuencia ou fará unha inserción na táboa na que se mapea a entidade *CriterionSatisfaction*, dependendo de se a base de datos soporta secuencias ou columnas contador, respectivamente. No primeiro caso, o único efecto lateral consiste en “n” incrementos na secuencia correspondente. Este efecto lateral pódese considerar irrelevante. No segundo caso, os novos *CriterionSatisfaction* teríanse que engadir á base de datos. Este efecto lateral, por contra, é incompatible coa semántica asociada a un paso intermedio (ademais, provoca unha excepción, dado que a inserción de obxectos é incompatible coa semántica asociada a *readOnly = true*).
* Aínda que se consiga xera-lo identificador dos obxectos novos nos pasos intermedios que fagan reattachments (e.g. empregando unha base de datos que dispoña de secuencias), independentemente do tipo de base de datos, o paso de confirmación (`figura 23`_) provocará unha excepción. Para entende-lo motivo, é preciso recordar que cando se invoca Session::saveOrUpdate, Hibernate sincroniza os obxectos que xa existían coa base de datos mediante sentencias UPDATE ... WHERE id = ? AND version = ? (e os novos mediante INSERT), é dicir, engade a comprobación do número de versión na sentenza UPDATE. Se o obxecto que vai a sincronizar coa base de datos é un obxecto novo (e.g. un CriterionSatisfaction) có identificador xerado, Hibernate non tentará engadilo, senón actualizalo, dado que dispón de identificador, e en consecuencia, asume que é un obxecto que existe en base de datos. Consecuentemente, lanzará unha sentencia UPDATE, que fallará porque non hai ningún obxecto na base de datos con ese identificador. * Aínda que se consiga xera-lo identificador dos obxectos novos nos pasos intermedios que fagan *reattachments* (e.g. empregando unha base de datos que dispoña de secuencias), independentemente do tipo de base de datos, o paso de confirmación (`figura 23`_) provocará unha excepción. Para entende-lo motivo, é preciso recordar que cando se invoca *Session::saveOrUpdate*, Hibernate sincroniza os obxectos que xa existían coa base de datos mediante sentencias *UPDATE ... WHERE id = ? AND version = ?* (e os novos mediante *INSERT*), é dicir, engade a comprobación do número de versión na sentenza *UPDATE*. Se o obxecto que vai a sincronizar coa base de datos é un obxecto novo (e.g. un *CriterionSatisfaction*) có identificador xerado, Hibernate non tentará engadilo, senón actualizalo, dado que dispón de identificador, e en consecuencia, asume que é un obxecto que existe en base de datos. Consecuentemente, lanzará unha sentencia *UPDATE*, que fallará porque non hai ningún obxecto na base de datos con ese identificador.
As `figura 24`_, `figura 25`_ e `figura 26`_ amosan unha posible solución ós problemas anteriores. Por unha banda, no mapping de CriterionSatisfaction emprégase a estratexia hilo (`figura 24`_) para a xeración de identificadores. Conceptualmente, esta estratexia é similar a sequence, pero funciona con calquera base de datos. A realización de hilo emprega unha táboa (table) que conceptualmente emula unha secuencia, e ademais emprega un algoritmo que non necesita acceder á base de datos cada vez que é preciso xerar un identificador (usa un rango, de tamaño max_lo, de identificadores en memoria, e cando se acaba, colle outro rango da táboa subxacente). No mapping de CriterionSatisfaction tamén se define version coma java.lang.Long (no canto de long) para que poida toma-lo valor null. Hibernate considera que as entidades con clave ou version con valor null son transient. As `figura 24`_, `figura 25`_ e `figura 26`_ amosan unha posible solución ós problemas anteriores. Por unha banda, no mapping de *CriterionSatisfaction* emprégase a estratexia *hilo* (`figura 24`_) para a xeración de identificadores. Conceptualmente, esta estratexia é similar a *sequence*, pero funciona con calquera base de datos. A realización de hilo emprega unha táboa (*table*) que conceptualmente emula unha secuencia, e ademais emprega un algoritmo que non necesita acceder á base de datos cada vez que é preciso xerar un identificador (usa un rango, de tamaño *max_lo*, de identificadores en memoria, e cando se acaba, colle outro rango da táboa subxacente). No mapping de *CriterionSatisfaction* tamén se define version coma *java.lang.Long* (no canto de *long*) para que poida toma-lo valor null. Hibernate considera que as entidades con clave ou *version* con valor *null* son *transient*.
:: ::
@ -438,13 +459,13 @@ As `figura 24`_, `figura 25`_ e `figura 26`_ amosan unha posible solución ós p
... ...
</class> </class>
_`Figura 24`: Uso da estratexia hilo para a xeración de identificadores. _`Figura 24`: Uso da estratexia *hilo* para a xeración de identificadores.
A `figura 25`_ amosa os cambios feitos sobre a entidade Resource. Por unha banda, defínese o atributo booleano newObject, que especifica se o obxecto é transient ou non. Este atributo non é persistente e pódeselle dar valor mediante o método setNewObject. Os métodos getVersion e setVersion permiten acceder á propiedade version, que agora ten tipo de acceso property(`figura 24`_). O método getVersion devolve null se o atributo newObject é true, de maneira que Hibernate asumirá que o obxecto é transient [#]_ aínda que a clave non sexa null. A `figura 25`_ amosa os cambios feitos sobre a entidade *Resource*. Por unha banda, defínese o atributo booleano *newObject*, que especifica se o obxecto é *transient* ou non. Este atributo non é persistente e pódeselle dar valor mediante o método *setNewObject*. Os métodos *getVersion* e *setVersion* permiten acceder á propiedade *version*, que agora ten tipo de acceso *property* (`figura 24`_). O método *getVersion* devolve *null* se o atributo *newObject* é true, de maneira que Hibernate asumirá que o obxecto é *transient* [#]_ aínda que a clave non sexa *null*.
.. [#] Hibernate considera que unha entidade é transient se a clave ou o campo version son null. .. [#] Hibernate considera que unha entidade é transient se a clave ou o campo version son null.
A `figura 26`_ amosa a operación WorkerModelImpl::addLaboralHistoryItem. Antes de engadi-lo item da historia laboral, a operación establece o atributo newObject do item a true. Na operación WorkerModelImpl::assignLocations sería preciso face-lo mesmo para as novas localizacións. A invocación a setNewObject en WorkerModelImpl::addLaboralHistoryItem e WorkerModelImpl::assignLocations farán que as instancias novas (é dicir, transient) de CriterionSatisfaction teñan o atributo newObject a true, e en consecuencia, o método getVersion sobre elas devolverá null. A `figura 26`_ amosa a operación *WorkerModelImpl::addLaboralHistoryItem*. Antes de engadi-lo item da historia laboral, a operación establece o atributo *newObject* do item a *true*. Na operación *WorkerModelImpl::assignLocations* sería preciso face-lo mesmo para as novas localizacións. A invocación a *setNewObject* en *WorkerModelImpl::addLaboralHistoryItem* e *WorkerModelImpl::assignLocations* farán que as instancias novas (é dicir, *transient*) de *CriterionSatisfaction* teñan o atributo *newObject* a *true*, e en consecuencia, o método *getVersion* sobre elas devolverá *null*.
:: ::
@ -467,7 +488,7 @@ A `figura 26`_ amosa a operación WorkerModelImpl::addLaboralHistoryItem. Antes
this.version = version; this.version = version;
} }
_`Figura 25`: Estratexia de version == null para instancias “transient”. (CriterionSatisfaction). _`Figura 25`: Estratexia de *version* == *null* para instancias “*transient*”. (*CriterionSatisfaction*).
:: ::
@ -481,15 +502,15 @@ _`Figura 25`: Estratexia de version == null para instancias “transient”. (Cr
} }
_`Figura 26`: Nova versión de WorkerModelImpl::addLaboralHistoryItem. _`Figura 26`: Nova versión de *WorkerModelImpl::addLaboralHistoryItem*
Cada vez que se fai un reattachment do worker (recordemos que se propaga en cascada ós CriterionSatisfaction) en WorkerModelImpl::unassignLocations (`figura 22`_), xenéranse os identificadores dos CriterionSatisfaction transient, aínda que xa foran xerados como consecuencia dun reattachment anterior, dado que o método getVersion devolve null (e en consecuencia, Hibernate asume que os obxectos son transient). Tendo en conta que cada o identificador é de tipo java.lang.Long (64 bits) e que cada entidade pode usa-la súa propia táboa para a estratexia hilo, o posible desaproveitamento de identificadores non parece un problema [#]_. Cada vez que se fai un *reattachment* do *worker* (recordemos que se propaga en cascada ós *CriterionSatisfaction*) en *WorkerModelImpl::unassignLocations* (`figura 22`_), xenéranse os identificadores dos *CriterionSatisfaction* *transient*, aínda que xa foran xerados como consecuencia dun *reattachment* anterior, dado que o método *getVersion* devolve *null* (e en consecuencia, Hibernate asume que os obxectos son transient). Tendo en conta que cada o identificador é de tipo *java.lang.Long* (64 bits) e que cada entidade pode usa-la súa propia táboa para a estratexia hilo, o posible desaproveitamento de identificadores non parece un problema [#]_.
.. [#] Unha maneira de minimiza-lo desaproveitamento de identificadores consiste en engadi-lo atributo booleano beforeSave e o método setBeforeSave, de maneira que getVersion só devolva null se newObject e beforeSave son true, e que o paso de confirmación invoque a setBeforeSave sobre cada obxecto transient antes de face-lo reattachment (no exemplo, o desenvolvedor, no paso de confirmación, tería que iterar polos CriterionSatisfaction e invocar a setBeforeSave sobre tódolos obxectos con newObject a true). .. [#] Unha maneira de minimiza-lo desaproveitamento de identificadores consiste en engadi-lo atributo booleano *beforeSave* e o método *setBeforeSave*, de maneira que *getVersion* só devolva *null* se *newObject* e *beforeSave* son *true*, e que o paso de confirmación invoque a *setBeforeSave* sobre cada obxecto transient antes de face-lo *reattachment* (no exemplo, o desenvolvedor, no paso de confirmación, tería que iterar polos *CriterionSatisfaction* e invocar a *setBeforeSave* sobre tódolos obxectos con *newObject* a *true*).
No paso de confirmación (`figura 23`_), cando se fai o reattachment do worker, Hibernate volve a considera-los CriterionSatisfaction novos como transient (dado que getVersion segue devolvendo null), de maneira que xenera os identificadores destes obxectos outra vez, e planifica a inserción dos obxectos na base de datos para o momento no que se faga o commit da transacción. No paso de confirmación (`figura 23`_), cando se fai o *reattachment* do *worker*, Hibernate volve a considera-los *CriterionSatisfaction* novos como *transient* (dado que *getVersion* segue devolvendo *null*), de maneira que xenera os identificadores destes obxectos outra vez, e planifica a inserción dos obxectos na base de datos para o momento no que se faga o commit da transacción.
A estratexia hilo e a técnica de redefinición de getVersion tamén é preciso aplicalas sobre a clase Resource, dado que no caso de creación dos datos dun traballador, o worker é un obxecto transient durante os pasos intermedios. En consecuencia, a operación WorkerModel::initCreate ten que invocar a setNewObject sobre o worker creado. A `figura 27`_ amosa esta operación. A estratexia hilo e a técnica de redefinición de *getVersion* tamén é preciso aplicalas sobre a clase *Resource*, dado que no caso de creación dos datos dun traballador, o *worker* é un obxecto *transient* durante os pasos intermedios. En consecuencia, a operación *WorkerModel::initCreate* ten que invocar a *setNewObject* sobre o *worker* creado. A `figura 27`_ amosa esta operación.
:: ::
@ -501,12 +522,12 @@ A estratexia hilo e a técnica de redefinición de getVersion tamén é preciso
_`Figura 27`: WorkerModelImpl::initCreate. _`Figura 27`: WorkerModelImpl::initCreate.
Finalmente, é importante coñecer que Hibernate ofrece un alternativa a técnica de reattachment mediante o método Session::merge. O uso deste método coñécese con nome de merging. A técnica de merging é necesaria cando non é posible saber no código se o obxecto do que se vai a facer un reattachment xa está engadido na sesión de Hibernate. Se efectivamente o estivese, e se intenta facer un reattachment, Hiberante lanza a excepción NonUniqueObjectException. Nestes caso, é preciso emprega-la técnica de merging mediante a operación Session::merge. Este método recibe o obxecto detached (ou transient) e devolve a referencia que se debe de empregar a partir dese momento para actuar sobre el. O método copia o estado do obxecto pasado como parámetro á sesión de Hibernate. A referencia pasada como parámetro débese descartar e a referencia retornada refírese o obxecto que está na sesión de Hibernate. O feito de ter que traballar coa referencia retornada pode complica-lo código, polo que unha boa estratexia é intentar face-lo reattachment dende as clases modelo antes de cargar, directa (mediante un DAO) ou indirectamente (navegación a través dunha relación lazy), calquera obxecto da base de datos, como se amosou no exemplo usado nesta sección. Finalmente, é importante coñecer que Hibernate ofrece un alternativa a técnica de reattachment mediante o método *Session::merge*. O uso deste método coñécese con nome de *merging*. A técnica de merging é necesaria cando non é posible saber no código se o obxecto do que se vai a facer un *reattachment* xa está engadido na sesión de Hibernate. Se efectivamente o estivese, e se intenta facer un *reattachment*, Hiberante lanza a excepción *NonUniqueObjectException*. Nestes caso, é preciso emprega-la técnica de merging mediante a operación *Session::merge*. Este método recibe o obxecto *detached* (ou *transient* ) e devolve a referencia que se debe de empregar a partir dese momento para actuar sobre el. O método copia o estado do obxecto pasado como parámetro á sesión de Hibernate. A referencia pasada como parámetro débese descartar e a referencia retornada refírese o obxecto que está na sesión de Hibernate. O feito de ter que traballar coa referencia retornada pode complica-lo código, polo que unha boa estratexia é intentar face-lo *reattachment* dende as clases modelo antes de cargar, directa (mediante un DAO) ou indirectamente (navegación a través dunha relación *lazy*), calquera obxecto da base de datos, como se amosou no exemplo usado nesta sección.
Reattachment de obxectos non modificados en pasos intermedios Reattachment de obxectos non modificados en pasos intermedios
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Na sección anterior, o conxunto de localizacións obrigatorias do grupo modelouse coma un conxunto de nomes de localizacións (`figura 19`_ e `figura 21`_). Sen embargo, a opción máis coherente có modelo de obxectos sería modela-lo conxunto de localizacións obrigatorias coma un conxunto de Criterion (Set<Criterion>). A `figura 28`_ amosa a nova versión de ResourceGroup::validateLocations asumindo esta nova (e mellor) opción de modelado. Na sección anterior, o conxunto de localizacións obrigatorias do grupo modelouse coma un conxunto de nomes de localizacións (`figura 19`_ e `figura 21`_). Sen embargo, a opción máis coherente có modelo de obxectos sería modela-lo conxunto de localizacións obrigatorias coma un conxunto de *Criterion* (*Set<Criterion>*). A `figura 28`_ amosa a nova versión de *ResourceGroup::validateLocations* asumindo esta nova (e mellor) opción de modelado.
:: ::
@ -523,25 +544,25 @@ Na sección anterior, o conxunto de localizacións obrigatorias do grupo modelou
} }
} }
_`Figura 28`: Versión de ResourceGroup::validateLocations, modelando as localización obrigatorias coma Set<Criterion>. _`Figura 28`: Versión de *ResourceGroup::validateLocations*, modelando as localización obrigatorias coma *Set<Criterion>*.
É importante observar que para que o código mostrado na `figura 28`_ funcione é vital que as referencias que manteñen os CriterionSatisfaction ós Criterion de tódolos traballadores do grupo sexan consistentes, é dicir, non debe haber máis dunha instancia dun Criterion dun mesmo tipo. Na sección `Un exemplo inicial`_ empregouse unha técnica (`figura 18`_) para tentar acadar esta semántica. Sen embargo, a solución alí presentada só é válida para garantir esta semántica nos CriterionSatisfaction do traballador en edición. Para entendelo basta examina-la realización da operación WorkerModelImpl::unassignLocations (`figura 22`_). Esta operación recupera da base de datos ó ResourceGroup do traballador, e posteriormente invoca a ResourceGroup::validateLocations (`figura 28`_). Cando ResourceGroup::validateLocationscomeza a iterar polos recursos do grupo, estes obtéñense da base de datos (nunha soa consulta), excepto o traballador en edición/creación (dado que se lle fixo un reattachment en WorkerModelImpl::unassignLocations). Sobre cada recurso, invócase Resource::getCurrentLocationCriteria, que itera por tódolos CriterionSatisfaction do recurso da iteración actual para devolve-los Criterion correspondentes ás localización actuais do recurso. En cada iteración, excepto para o traballador en edición, os CriterionSatisfaction do recurso da iteración, así coma os seus Criterion asociados, vanse recuperando da base de datos e engadindo á sesión de Hibernate (dado que a sesión actúa como caché de primeiro nivel, cada tipo de Criterion só se recupera unha vez da base de datos). En consecuencia, os Criterion recuperados da base de datos non serán consistentes cós Criterion do traballador en edición, dado que estes estaban en memoria, pero non na sesión de Hibernate. Este problema tamén se pon de manifesto en WorkerModelImpl::confirm (`figura 23`_). É importante observar que para que o código mostrado na `figura 28`_ funcione é vital que as referencias que manteñen os *CriterionSatisfaction* ós *Criterion* de tódolos traballadores do grupo sexan consistentes, é dicir, non debe haber máis dunha instancia dun *Criterion* dun mesmo tipo. Na sección `Un exemplo inicial`_ empregouse unha técnica (`figura 18`_) para tentar acadar esta semántica. Sen embargo, a solución alí presentada só é válida para garantir esta semántica nos *CriterionSatisfaction* do traballador en edición. Para entendelo basta examina-la realización da operación *WorkerModelImpl::unassignLocations* (`figura 22`_). Esta operación recupera da base de datos ó *ResourceGroup* do traballador, e posteriormente invoca a *ResourceGroup::validateLocations* (`figura 28`_). Cando *ResourceGroup::validateLocationscomeza* a iterar polos recursos do grupo, estes obtéñense da base de datos (nunha soa consulta), excepto o traballador en edición/creación (dado que se lle fixo un *reattachment* en *WorkerModelImpl::unassignLocations*). Sobre cada recurso, invócase *Resource::getCurrentLocationCriteria*, que itera por tódolos *CriterionSatisfaction* do recurso da iteración actual para devolve-los *Criterion* correspondentes ás localización actuais do recurso. En cada iteración, excepto para o traballador en edición, os *CriterionSatisfaction* do recurso da iteración, así coma os seus *Criterion* asociados, vanse recuperando da base de datos e engadindo á sesión de Hibernate (dado que a sesión actúa como caché de primeiro nivel, cada tipo de *Criterion* só se recupera unha vez da base de datos). En consecuencia, os *Criterion* recuperados da base de datos non serán consistentes cós *Criterion* do traballador en edición, dado que estes estaban en memoria, pero non na sesión de Hibernate. Este problema tamén se pon de manifesto en *WorkerModelImpl::confirm* (`figura 23`_).
Para solucionar este problema é preciso que antes de que se invoque ResourceGroup::validateLocations en WorkerModelImpl::unassignLocations e WorkerModelImpl::confirm, tódolos Criterion do traballador en edición estean na sesión de Hibernate. É dicir, é preciso facer un reattachment destes Criterion. A `figura 29`_ presenta unha nova realización das operacións WorkerModelImpl::unassignLocations e WorkerModelImpl::confirm. A diferencia das versións anteriores, agora o reattachment do worker faise mediante o método privado WorkerModelImpl::reattachWorker, que ademais de facer un reattachment do worker (e os seus CriterionSatisfaction asociados), tamén fai un reattachment dos Criterion correspondentes a localizacións e situacións laborais. En realidade, chegaría con facer un reattachment de aqueles que usa o traballador en edición. Para elo, bastaría con iterar polos CriterionSatisfaction do traballador e facer un reattachment de cada Criterion. Certamente, esta posibilidade é correcta. No obstante, dado que a clase WorkerModelImpl cachea como parte do estado tódolos Criterion correspondentes ás situacións laborais e localizacións nos atributos laboralCriteria e locationCriteria, por consistencia, optouse por facer un reattachment de todos eles. Para solucionar este problema é preciso que antes de que se invoque *ResourceGroup::validateLocations* en *WorkerModelImpl::unassignLocations* e *WorkerModelImpl::confirm*, tódolos *Criterion* do traballador en edición estean na sesión de Hibernate. É dicir, é preciso facer un *reattachment* destes Criterion. A `figura 29`_ presenta unha nova realización das operacións *WorkerModelImpl::unassignLocations* e *WorkerModelImpl::confirm*. A diferencia das versións anteriores, agora o *reattachment* do *worker* faise mediante o método privado *WorkerModelImpl::reattachWorker*, que ademais de facer un *reattachment* do *worker* (e os seus *CriterionSatisfaction* asociados), tamén fai un *reattachment* dos *Criterion* correspondentes a localizacións e situacións laborais. En realidade, chegaría con facer un *reattachment* de aqueles que usa o traballador en edición. Para elo, bastaría con iterar polos *CriterionSatisfaction* do traballador e facer un *reattachment* de cada *Criterion*. Certamente, esta posibilidade é correcta. No obstante, dado que a clase *WorkerModelImpl* cachea como parte do estado tódolos *Criterion* correspondentes ás situacións laborais e localizacións nos atributos *laboralCriteria* e *locationCriteria*, por consistencia, optouse por facer un *reattachment* de todos eles.
Para face-lo reattachment dos Criterion non se emprega IGenericDao::save, dado que este método causa a execución de Sesssion.saveOrUpdate, que deixa o obxecto en estado dirty (aínda que non se modifique). En consecuencia, se se empregase IGenericDao::save sobre os Criterion no paso de confirmación, os Criterion actualizaríanse na base de datos (facendo que os seus números de versión se incrementen). Para evitar este problema, emprégase IGenericDao::reattachUnmodifiedEntity, que causa a execución de Sesssion.lock(LockMode.NONE, entity). Este método causa o reattachment da entidade pasada como parámetro, pero a diferencia de Session.saveOrUpdate, a entidade non se considera dirty. Para face-lo *reattachment* dos *Criterion* non se emprega *IGenericDao::save*, dado que este método causa a execución de *Sesssion.saveOrUpdate*, que deixa o obxecto en estado *dirty* (aínda que non se modifique). En consecuencia, se se empregase *IGenericDao::save* sobre os *Criterion* no paso de confirmación, os *Criterion* actualizaríanse na base de datos (facendo que os seus números de versión se incrementen). Para evitar este problema, emprégase *IGenericDao::reattachUnmodifiedEntity*, que causa a execución de *Sesssion.lock(LockMode.NONE, entity)*. Este método causa o reattachment da entidade pasada como parámetro, pero a diferencia de *Session.saveOrUpdate*, a entidade non se considera *dirty*.
Como alternativa a LockMode.NONE, é posible empregar LockMode.READ, que adicionalmente lanza unha consulta para comproba-lo número de versión do obxecto pasado como parámetro a Session.lock (só a primeira vez que se fai un reattachment). No obstante, o uso de LockMode.NONE, en xeral, parece suficiente e máis eficiente (no se lanza ningunha consulta). Como alternativa a *LockMode.NONE*, é posible empregar *LockMode.READ*, que adicionalmente lanza unha consulta para comproba-lo número de versión do obxecto pasado como parámetro a *Session.lock* (só a primeira vez que se fai un *reattachment*). No obstante, o uso de *LockMode.NONE*, en xeral, parece suficiente e máis eficiente (no se lanza ningunha consulta).
Con respecto á semántica da operación Session.lock, convén destacar: Con respecto á semántica da operación *Session.lock*, convén destacar:
* É posible invocar esta operación máis dunha vez co mesmo obxecto (o reattachment só ocorre a primeira vez). * É posible invocar esta operación máis dunha vez co mesmo obxecto (o reattachment só ocorre a primeira vez).
* O reattachment que causa Session.lock pódese propagar ás entidades relacionadas coa opción de cascada lock ou unha que a inclúa (e.g. all, all-delete-orphan). Sen embargo, a semántica do modo de bloqueo (LockMode.XXX) só se aplica a entidade pasada como parámetro. * O *reattachment* que causa *Session.lock* pódese propagar ás entidades relacionadas coa opción de cascada *lock* ou unha que a inclúa (e.g. *all*, *all-delete-orphan*). Sen embargo, a semántica do modo de bloqueo (*LockMode.XXX*) só se aplica a entidade pasada como parámetro.
* A entidade pasada como parámetro non debe estar modificada (pero se se modifica unha vez que se lle fixo o reattachment, considérase dirty). * A entidade pasada como parámetro non debe estar modificada (pero se se modifica unha vez que se lle fixo o *reattachment*, considérase *dirty*).
Como se indicou anterioremente, o método WorkerModelImpl::reattachWorker non precisa facer un reattachment de tódolos Criterion correspondentes ás situacións laborais e localizacións, senón que chega con facer un reattachment de aqueles que usa o traballador en creación/edición. Esta observación conduce de maneira natural a outra solución alternativa que garante a unicidade de instancias de Criterion do mesmo tipo. A `figura 30`_ amosa a nova solución. Agora non se precisa manter como estado da conversación os atributos laboralCriteria e locationCriteria, de maneira que desaparece o método initCriteria e simplifícase o método resetState (`figura 18`_) . Como se indicou anterioremente, o método *WorkerModelImpl::reattachWorker* non precisa facer un reattachment de tódolos *Criterion* correspondentes ás situacións laborais e localizacións, senón que chega con facer un *reattachment* de aqueles que usa o traballador en creación/edición. Esta observación conduce de maneira natural a outra solución alternativa que garante a unicidade de instancias de *Criterion* do mesmo tipo. A `figura 30`_ amosa a nova solución. Agora non se precisa manter como estado da conversación os atributos *laboralCriteria* e *locationCriteria*, de maneira que desaparece o método *initCriteria* e simplifícase o método *resetState* (`figura 18`_) .
Os métodos WorkerModelImpl::getLaboralCriteria e WorkerModelImpl::getNonAssignedLocationCriteria recuperan da base de datos tódolos Criterion correspondentes a situacións laborais e localizacións. Para garanti-la consistencia cós Criterion do traballador en edición/creación, antes de recupera-los Criterion da base de datos, ambos métodos fan un rettachment (WorkerModelImpl::reattachWorkerCriteria) dos Criterion que ten o traballador en edición/creación. Para garanti-la consistencia de Criterion que demanda o método Resource::validateLocations (`figura 28`_) entre o traballador en edición e o resto de traballadores do grupo, o método WorkerModelImpl::reattachWorker, fai o reattachment do traballador e os seus Criterion. Ademais, esta alternativa, con respecto á da `figura 29`_, non cachea o conxunto total de Criterion durante a conversación, de maneira que permitiría detectar cambios concorrentes (novos Criterion, Criterion modificados ou Criterion eliminados) durante o transcurso da conversación. Os métodos *WorkerModelImpl::getLaboralCriteria* e *WorkerModelImpl::getNonAssignedLocationCriteria* recuperan da base de datos tódolos *Criterion* correspondentes a situacións laborais e localizacións. Para garanti-la consistencia cós *Criterion* do traballador en edición/creación, antes de recupera-los *Criterion* da base de datos, ambos métodos fan un rettachment (*WorkerModelImpl::reattachWorkerCriteria*) dos *Criterion* que ten o traballador en edición/creación. Para garanti-la consistencia de *Criterion* que demanda o método *Resource::validateLocations* (`figura 28`_) entre o traballador en edición e o resto de traballadores do grupo, o método *WorkerModelImpl::reattachWorker*, fai o reattachment do traballador e os seus *Criterion*. Ademais, esta alternativa, con respecto á da `figura 29`_, non cachea o conxunto total de *Criterion* durante a conversación, de maneira que permitiría detectar cambios concorrentes (novos *Criterion*, *Criterion* modificados ou *Criterion* eliminados) durante o transcurso da conversación.
:: ::
@ -597,7 +618,7 @@ Os métodos WorkerModelImpl::getLaboralCriteria e WorkerModelImpl::getNonAssigne
} }
} }
_`Figura 29`: Reattachment do worker (inclusive os CriterionSatisfaction) e os Criterion. _`Figura 29`: *Reattachment* do *worker* (inclusive os *CriterionSatisfaction*) e os *Criterion*.
:: ::
@ -638,20 +659,20 @@ _`Figura 29`: Reattachment do worker (inclusive os CriterionSatisfaction) e os C
worker = null; worker = null;
} }
_`Figura 30`: Consistencia de instancias de Criterion sen necesidade de manter como estado tódalas instancias de Criterion. _`Figura 30`: Consistencia de instancias de *Criterion* sen necesidade de manter como estado tódalas instancias de *Criterion*.
Bloqueos pesimistas Bloqueos pesimistas
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Na sección `Técnicas Avanzadas`_ fíxose énfase en garantir que a validación das localizacións do traballador en edición fose compatible coa edición concorrente de outros membros do mesmo grupo, recuperando o ResourceGroup (e os seus recursos) da base de datos antes de valida-las localizacións, tanto en WorkerModelImpl::unassignLocations coma en WorkerModelImpl::confirm. Esta técnica é suficiente para facer fronte a edición concorrente de varios traballadores dun mesmo grupo, sempre e cando non ocorra que dúas o máis conversacións executen **xunto á vez** o método WorkerModelImpl::confirm. Para entendelo basta pensar en dúas conversacións (conversación 1 e conversación 2) que modifican os datos de dous traballadores (traballador 1 e traballador 2) dun mesmo grupo concorrentemente, de maneira que ambas tentan quita-la localización obrigatoria A dos respectivos traballadores, sendo eles os únicos que teñen esta localización actualmente. Na sección `Técnicas Avanzadas`_ fíxose énfase en garantir que a validación das localizacións do traballador en edición fose compatible coa edición concorrente de outros membros do mesmo grupo, recuperando o *ResourceGroup* (e os seus recursos) da base de datos antes de valida-las localizacións, tanto en *WorkerModelImpl::unassignLocations* coma en *WorkerModelImpl::confirm*. Esta técnica é suficiente para facer fronte a edición concorrente de varios traballadores dun mesmo grupo, sempre e cando non ocorra que dúas o máis conversacións executen **xunto á vez** o método *WorkerModelImpl::confirm*. Para entendelo basta pensar en dúas conversacións (conversación 1 e conversación 2) que modifican os datos de dous traballadores (traballador 1 e traballador 2) dun mesmo grupo concorrentemente, de maneira que ambas tentan quita-la localización obrigatoria A dos respectivos traballadores, sendo eles os únicos que teñen esta localización actualmente.
Imaxinemos a seguinte secuencia de pasos: Imaxinemos a seguinte secuencia de pasos:
* A conversación 1 elimina a localización A do traballador 1 (WorkerModelImpl::unassignLocations). * A conversación 1 elimina a localización A do traballador 1 (*WorkerModelImpl::unassignLocations*).
* A conversación 2 fai o mesmo co traballador 2 (pódeo facer porque en base de datos, o traballador 1 aínda ten a localización A). * A conversación 2 fai o mesmo co traballador 2 (pódeo facer porque en base de datos, o traballador 1 aínda ten a localización A).
* As dúas conversacións executan xunto á vez o método WorkerModelImpl::confirm(`figura 29`_). Neste caso ambas verían aínda hai outro traballador coa localización A, de maneira que ambas poderían confirma-los cambios, quedando o grupo sen ningún recurso na localización A. * As dúas conversacións executan **xunto á vez** o método *WorkerModelImpl::confirm* (`figura 29`_). Neste caso ambas verían aínda hai outro traballador coa localización A, de maneira que ambas poderían confirma-los cambios, quedando o grupo sen ningún recurso na localización A.
Para solucionar este problema, pódese empregar IGenericDao::lock sobre o grupo do traballador en edición na operación WorkerModelImpl::confirm (`figura 31`_). Esta operación causa a execución de Session.lock(LockMode.UPGRADE, entity). O modo LockMode.UPGRADE provoca que se lance unha sentencia SELECT ... WHERE id = ? AND version = ? FOR UPDATE. A cláusula FOR UPDATE establece un bloqueo de escritura sobre as filas afectadas, o que provoca que calquera outra transacción concorrente non poia escribir ou establecer outro bloqueo de escritura sobre esas filas. En consecuencia, se dúas conversacións executan a operación WorkerModelImpl::confirm concorrentemente, a primeira que execute o método IGenericDao::lock bloqueará á outra no mesmo método ata que remate a primeira transacción. Deste xeito, na secuencia de pasos plantexada anteriormente, cando as dúas conversacións executen o paso de confirmación á vez, unha executará antes o método IGenericDao::lock (impedindo que a outra avance ó chegar a este mesmo método) e superará con éxito a validación (porque hai outro traballador en base de datos que ten a localización A). Cando a transacción do paso de confirmación desta conversación remate, continúase coa execución do paso de confirmación da outra conversación, que xa non superará a validación, dado que non hai outro traballador en base de datos coa localización A. Para solucionar este problema, pódese empregar *IGenericDao::lock* sobre o grupo do traballador en edición na operación *WorkerModelImpl::confirm* (`figura 31`_). Esta operación causa a execución de *Session.lock(LockMode.UPGRADE, entity)*. O modo *LockMode.UPGRADE* provoca que se lance unha sentencia *SELECT ... WHERE id = ? AND version = ? FOR UPDATE*. A cláusula *FOR UPDATE* establece un bloqueo de escritura sobre as filas afectadas, o que provoca que calquera outra transacción concorrente non poia escribir ou establecer outro bloqueo de escritura sobre esas filas. En consecuencia, se dúas conversacións executan a operación *WorkerModelImpl::confirm* concorrentemente, a primeira que execute o método *IGenericDao::lock* bloqueará á outra no mesmo método ata que remate a primeira transacción. Deste xeito, na secuencia de pasos plantexada anteriormente, cando as dúas conversacións executen o paso de confirmación á vez, unha executará antes o método *IGenericDao::lock* (impedindo que a outra avance ó chegar a este mesmo método) e superará con éxito a validación (porque hai outro traballador en base de datos que ten a localización A). Cando a transacción do paso de confirmación desta conversación remate, continúase coa execución do paso de confirmación da outra conversación, que xa non superará a validación, dado que non hai outro traballador en base de datos coa localización A.
:: ::
@ -672,12 +693,12 @@ Para solucionar este problema, pódese empregar IGenericDao::lock sobre o grupo
resetState(); resetState();
} }
_`Figura 31`: Versión de WorkerModelImpl::confirm que contempla a posible concorrencia na confirmación de varias conversacións que actúan sobre traballadores dun mesmo grupo. _`Figura 31`: Versión de *WorkerModelImpl::confirm* que contempla a posible concorrencia na confirmación de varias conversacións que actúan sobre traballadores dun mesmo grupo.
Finalmente, é importante resaltar dous aspectos: Finalmente, é importante resaltar dous aspectos:
* O uso de LockMode.UPGRADE é incompatible coa semántica de readOnly = true en @Transactional, de maneira que IGenericDao::lock só se pode empregar nun paso de confirmación. * O uso de *LockMode.UPGRADE* é incompatible coa semántica de *readOnly = true* en *@Transactional*, de maneira que *IGenericDao::lock* só se pode empregar nun paso de confirmación.
* O uso de IGenericDao::lock non é específico ó concepto de conversación, senón que é un mecanismo que se pode aplicar para soluciona-los problemas de concorrencia que non son abordables có control de versións (no exemplo, cando se edita un traballador, non se modifica o ResourceGroup asociado, e en consecuencia, non se incrementa o número de versión do ResourceGroup). * O uso de *IGenericDao::lock* non é específico ó concepto de conversación, senón que é un mecanismo que se pode aplicar para soluciona-los problemas de concorrencia que non son abordables có control de versións (no exemplo, cando se edita un traballador, non se modifica o *ResourceGroup* asociado, e en consecuencia, non se incrementa o número de versión do *ResourceGroup*).
Convencións de nomeado Convencións de nomeado
---------------------- ----------------------
@ -687,9 +708,9 @@ Nomeado das operacións da conversación
Para nomea-las operacións dunha clase modelo que represente unha conversación, empregaranse as seguintes convencións: Para nomea-las operacións dunha clase modelo que represente unha conversación, empregaranse as seguintes convencións:
* Se só hai unha operación para inicia-la conversación, empregarase o nome init (e.g. IWorkerModel::init). Se a conversación se pode iniciar con distintas operacións, empregaranse nomes con prefixo init (e.g. IWorkerModel::initCreate, IWorkerModel::initEdit, etc.). * Se só hai unha operación para inicia-la conversación, empregarase o nome init (e.g. *IWorkerModel::init*). Se a conversación se pode iniciar con distintas operacións, empregaranse nomes con prefixo init (e.g. *IWorkerModel::initCreate*, *IWorkerModel::initEdit*, etc.).
* Se só hai unha operación para finaliza-la conversación con éxito, empregarase o nome confirm (e.g. IWorkerModel::confirm). Se é posible remata-la conversación con éxito con distintas operacións, empregaranse nomes con prefixo confirm. * Se só hai unha operación para finaliza-la conversación con éxito, empregarase o nome confirm (e.g. *IWorkerModel::confirm*). Se é posible remata-la conversación con éxito con distintas operacións, empregaranse nomes con prefixo confirm.
* A operación para cancela-los cambios chamarase cancel (e.g. IWorkerModel::cancel). * A operación para cancela-los cambios chamarase cancel (e.g. *IWorkerModel::cancel*).
Documentación do protocolo da conversación Documentación do protocolo da conversación
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -748,12 +769,12 @@ A `figura 32`_ ilustra o estilo de documentación que se deberá seguir para doc
* @author ... * @author ...
*/ */
_`Figura 32` : Exemplo de documentación da semántica dunha conversación (IWorkerModel). _`Figura 32` : Exemplo de documentación da semántica dunha conversación (*IWorkerModel*).
Outros aspectos Outros aspectos
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
* Cando unha operación reciba un parámetro cuxo tipo sexa unha entidade que actúa coma un DTO, o nome do parámetro usará o sufixo Dto (e.g. parámetro *newDataDo* na operación *updateLaboralHistoryItem* da `figura 13`_ ). FIXME[Quizais sexa preciso mover isto á sección 3 máis adiante] * Cando unha operación reciba un parámetro cuxo tipo sexa unha entidade que actúa coma un DTO, o nome do parámetro usará o sufixo Dto (e.g. parámetro *newDataDo* na operación *updateLaboralHistoryItem* da `figura 13`_ ). FIXME[Quizais sexa preciso mover isto á sección 3 máis adiante]