Repositórios JPA no Projeto ecafeteria Um objeto EntityManager representa uma ligação à base de dados. Quando um objeto EntityManger é criado também é criado um objeto EntityTransaction associado ao EntityManager. Operações que modificam a base de dados têm de ser executadas dentro de uma transação. Para executar essas operações temos de iniciar a transação, executar as operações e fazer o commit da transação. As operações na base de dados são efetuadas por métodos existentes nos Repositórios. Se a operação a executar na base de dados afeta apenas um repositório, o início e commit da transação podem ser feitos no método existente no repositório. Assim controladores que usam apenas um agregado criam um Repositório (subclasse de) JpaTransactionalRepository, no qual cada método abre e fecha a transação working in a single immediate transaction mode. Se a operação a executar na base de dados afeta mais que um repositório, o início e commit da transação podem ser feitos no método do Controlador que usa os vários repositórios. Assim controladores que usam mais que um agregado criam Repositórios (subclasses de) JpaWithTransactionalContextRepository, no qual cada método não abre nem fecha a transação working within a TransactionalContext. Uma transação é iniciada no Controlador, a seguir são efetuadas operações nos vários Repositórios e no fim a transação é fechada no Controlador. A classe JpaTransactionalContext do projeto framework (package eapli.framework.persistence.repositories.impl.jpa) permite criar um objeto que representa uma ligação à base de dados e respetiva transação, assim como abrir e fechar a transação e fazer commit ou rollback. A linha de código seguinte cria um objeto que representa uma ligação à base de dados e respetiva transação: private final TransactionalContext TxCtx = PersistenceContext.repositories().buildTransactionalContext(); Classes base para Repositórios: public abstract class JpaBaseRepository<T, K extends Serializable> - implementa a interface IterableDataRepository - superclasse para Repositórios. - contém vários métodos CRUD que não abrem nem fecham a transação. public class JpaWithTransactionlContextRepository<T, K extends Serializable> - subclasse de JpaBaseRepository - recebe na criação um objeto TransactionalContext, através do qual obtém o objeto EntityManager e EntityManagerFactory. - não tem métodos CRUD.
public class JpaTransactionalRepository<T, K extends Serializable> - subclasse de JpaWithTransactionalContextRepository - recebe na criação o nome da unidade de persistência, cria o objeto TransactionalContext correspondente, e passa-o à superclasse. - tem os métodos delete(), create() e save(). - os métodos abrem e fecham a transação. public class CafeteriaJpaRepositoryBase<T, K extends Serializable> - subclasse de JpaTransactionalRepository - invoca o construtor da superclasse com o nome da unidade de persistência. - superclasse para Repositórios da aplicação ecafeteria cujas operações a executar na base de dados afetam apenas um repositório. public class JpaAutoTxRepository<T, K extends Serializable> - superclasse para Repositórios cujas operações podem afetar mais que um repositório. - tem dois atributos: repo, do tipo JpaBaseRepository, e autotx do tipo TransactionalContext. - pode receber na criação: 1) um objeto TransactionalContext que guarda no atributo autotx; cria repo do tipo JpaWithTransactionalContextRepository (métodos não abrem nem fecham a transação). 2) uma string com o nome da unidade persistência; o atributo autotx fica com o valor null e cria repo do tipo JpaTransactionalRepository (métodos abrem e fecham a transação). - JpaAutoTxRepository tem os métodos delete(), save(), findall(), findone(), count() e iterator() que delegam as ações no objeto repo.
O Problema Operações que modificam a base de dados têm de ser executadas dentro de uma transação. A realização de alguns casos de uso afeta apenas um repositório, mas para outros casos de uso as operações necessárias afetam mais que um repositório. Se as operações de um caso de uso afetam apenas um repositório o início e commit da transação podem ser feitos no método existente no repositório. Se as operações de um caso de uso afetam mais que um repositório o início e commit da transação podem ser feitos no método do Controlador e os repositórios usados devem ter métodos sem controlo transacional. Considerando todos os casos de uso e para cada caso de uso os repositórios usados, verificamos que existem: 1. repositórios que são sempre usados num contexto que não afeta outros repositórios, como por exemplo DishRepository, DishTypeRepository e MaterialRepository. 2. repositórios que podem ser usados quer num contexto que afeta outros repositórios, quer num contexto que não afeta outros repositórios, como por exemplo SignupRequestRepository, UserRepository e CafeteriaUserRepository. Design da solução Hierarquia de classes genéricas destinadas a criar repositórios: JpaBaseRepository<T, K extends Serializable> - classe genérica com várias operações CRUD sem controlo transacional. JpaWithTransactionlContextRepository<T, K extends Serializable> - classe genérica que recebe na criação um objeto TransacionalContext, também sem controlo transacional. JpaTransactionlRepository<T, K extends Serializable> - classe genérica com controlo transacional.
O projeto framework tem uma classe para servir de superclasse para os repositórios que são sempre usados num contexto que não afeta outros repositórios - JpaTransactionalRepository<T, K extends Serializable>. No entanto usa-se uma subclasse de JpaTransactionalRepository<T, K extends Serializable> em vez da própria superclasse para: Obter um tipo próprio para representar cada repositório herdando todas as funcionalidades de JpaTransactionalRepository; Ser possível estender o comportamento com métodos específicos para o repositório criado. Na aplicação ecafeteria optou-se por criar, no projeto ecafeteria.persistence.impl, a classe CafeteriaJpaRepositoryBase<T, K extends Serializable> como subclasse de JpaTransactionalRepository, para servir de superclasse às classes dos repositórios que são sempre usados num contexto que não afeta outros repositórios.
O projeto framework tem outra classe para representar os repositórios usados num contexto que afeta outros repositórios - JpaWithTransactionlContextRepository<T, K extends Serializable>. Mas como estes repositórios podem ser usados quer em contextos que afetam outros repositórios, quer em contextos que não afetam outros repositórios, e porque não seria modular usar classes diferentes em diferentes cenários para representar o mesmo repositório, o projeto framework tem outra classe JpaAutoTxRepository<T, K extends Serializable> para servir de superclasse a estes repositórios. Esta classe, em vez de usar herança, usa composição. A composição é mais flexível que a herança, porque enquanto a herança adiciona funcionalidades em tempo de compilação, a composição adiciona funcionalidades em tempo de execução. A classe JpaAutoTxRepository possui dois atributos, um atributo repo do tipo JpaBaseRepository e outro atributo autotx do tipo TransactionalContext. Quando JpaAutoTxRepository é usado num contexto que afeta outros repositórios: repo é um objeto do tipo JpaWithTransactionalContextRepository, e autotx é um objeto do tipo JpaTransactionalContext. Quando JpaAutoTxRepository é usado num contexto que não afeta outros repositórios: repo é um objeto do tipo JpaTransactionalRepository, e autotx é null. A classe JpaRepositoryFactory do projeto ecafeteria.persistence.impl (não incluída no diagrama acima) possui um método para criar cada um dos repositórios que são sempre usados num contexto que não afeta outros repositórios, e dois métodos para criar cada um
dos repositórios que podem ser usados quer em contextos que afetam outros repositórios, quer em contextos que não afetam outros repositórios: public class JpaRepositoryFactory implements RepositoryFactory { public DishTypeRepository dishtypes() { return new JpaDishTypeRepository(); public DishRepository dishes() { return new JpaDishRepository(); public MaterialRepository materials() { return new JpaMaterialRepository(); public SignupRequestRepository signuprequests() { return new JpaSignupRequestRepository( Application.settings().getPersistenceUnitName()); public SignupRequestRepository signuprequests(transactionalcontext autotx) { return new JpaSignupRequestRepository(autoTx); public UserRepository users() { return new JpaUserRepository( Application.settings().getPersistenceUnitName()); public UserRepository users(transactionalcontext autotx) { return new JpaUserRepository(autoTx); public JpaCafeteriaUserRepository cafeteriausers() { return new JpaCafeteriaUserRepository( Application.settings().getPersistenceUnitName()); public JpaCafeteriaUserRepository cafeteriausers( TransactionalContext autotx) { return new JpaCafeteriaUserRepository(autoTx); public TransactionalContext buildtransactionalcontext() { return JpaAutoTxRepository.buildTransactionalContext( Application.settings().getPersistenceUnitName(), Application.settings().getExtendedPersistenceProperties()); public DishReportingRepository dishreporting() { return new JpaDishReportingRepository();
Que classe usar para criar um Repositório? Se o Repositório a criar vai conter operações (a executar na base de dados) que afetam apenas um repositório (o início e commit da transação são feitos no método existente no repositório) devemos criá-lo como subclasse de CafeteriaJpaRepositoryBase. Se o Repositório a criar vai conter operações (a executar na base de dados) que nalguns cenários podem afetar apenas um repositório mas noutros cenários podem afetar mais que um repositório, devemos criá-lo como subclasse de JpaAutoTxRepository. Nos cenários em que as operações afetam mais que um repositório passamos ao construtor de JpaAutoTxRepository um objeto TransactionalContext. Este objeto permite-nos iniciar a transação no Controlador, ser usado nos repositórios para efetuar as operações na base de dados, e por fim fazer o commit da transação no Controlador. O objeto JpaAutoTxRepository cria um atributo repo do tipo JpaWithTransactionlContextRepository, que herda métodos CRUD de JpaBaseRepository, que não abrem nem fecham a transação, embora as operações sejam realizadas dentro de um contexto transacional cujo controlo está fora do repositório. Nos cenários em que as operações só afetam um repositório passamos ao construtor de JpaAutoTxRepository uma string com o nome da unidade de persistência. O objeto JpaAutoTxRepository cria um atributo repo do tipo JpaTransactionlRepository que contém métodos CRUD que abrem e fecham a transação. Assim os repositórios a criar podem ser de 3 tipos: 1. Repositórios cujas operações são sempre realizadas num contexto em que só é necessário aceder a um repositório dentro da transação => subclasse de CafeteriaJpaRepositoryBase. 2. Repositórios cujas operações podem ser realizadas quer num contexto em que só é necessário aceder a um repositório dentro da transação, quer num contexto em que é necessário aceder a mais que um repositório dentro da transação => subclasse de JpaAutoTxRepository. 2.1. Para os cenários em que é necessário aceder a mais que um repositório dentro de uma transação passamos ao construtor de JpaAutoTxRepository um objeto TransationalContext. O atributo repo do objeto JpaAutoTxRepository, no qual ele delega as operações na base de dados, será do tipo JpaWithTransactionalContextRepository. 2.2. Para os cenários em que só é necessário aceder a um repositório dentro de uma transação passamos ao construtor de JpaAutoTxRepository o nome da unidade de persistência. O atributo repo do objeto JpaAutoTxRepository, no qual ele delega as operações na base de dados, será do tipo JpaTransactionalRepository.
Exemplo: No caso de uso Aceitar ou Recusar Pedido de Registo o Controlador tem de modificar 3 repositórios: 1. Repos. de SignUpRequest para mudar o objeto do estado Pending para Accepted. 2. Repos. de User para criar o objeto User correspondente ao pedido. 3. Repos. de CafeteriaUser para criar o objeto CafeteriaUser correspondente ao User criado. Para modificar 3 repositórios dentro da mesma transação tem de criar um objeto TransactionalContext, criar cada um dos 3 Repositórios (subclasses de JpaAutoTxRepository) e passar-lhes o objeto TransactionalContext: JpaSignUpRequestRepository JpaUserRepository JpaCafeteriaUserRepository class AcceptRefuseSignupRequestController private final TransactionalContext TxCtx = PersistenceContext.repositories().buildTransactionalContext(); private final SignupRequestRepository signuprequestsrepository = PersistenceContext.repositories().signupRequests(TxCtx); private final UserRepository userrepository = PersistenceContext.repositories().users(TxCtx); private final CafeteriaUserRepository cafeteriauserrepository = PersistenceContext.repositories().cafeteriaUsers(TxCtx); public SignupRequest acceptsignuprequest(signuprequest thesignuprequest) { // explicitly begin a transaction TxCtx.beginTransaction(); SystemUser newuser = createsystemuserforcafeteriauser(thesignuprequest); createcafeteriauser(thesignuprequest, newuser); thesignuprequest = acceptthesignuprequest(thesignuprequest); // explicitly commit the transaction TxCtx.commit(); return thesignuprequest;