3. JPA em uma arquitetura multicamadas
Para estudar o API JPA, utilizamos a seguinte arquitetura de teste:
![]() |
Nossos programas de teste eram aplicativos de console que consultavam diretamente a camada JPA. Nessa ocasião, descobrimos os principais métodos da camada JPA. Estávamos em um ambiente denominado “Java SE” (Standard Edition). O JPA funciona tanto no ambiente Java SE quanto no Java EE5 (Enterprise Edition).
Agora que já temos um certo domínio tanto da configuração da ponte relacional/objeto quanto do uso dos métodos da camada JPA, voltamos a uma arquitetura multicamadas mais clássica:
![]() |
A camada [JPA] será acessada por meio de uma arquitetura de duas camadas: [metier] e [dao]. O framework Spring [7] e, em seguida, o contêiner EJB3 de JBoss e [8] serão utilizados para interligar essas camadas entre si.
Mencionamos anteriormente que o JPA estava disponível nos ambientes SE e EE5. O ambiente Java EE5 oferece diversos serviços na área de acesso a dados persistentes, incluindo pools de conexão, gerenciadores de transações, etc. Pode ser interessante para um desenvolvedor aproveitar esses serviços. O ambiente Java EE5 ainda não é muito difundido (maio de 2007). Atualmente, ele está disponível no servidor de aplicativos Sun Application Server 9.x (Glassfish). Um servidor de aplicativos é, essencialmente, um servidor de aplicativos web. Se for desenvolvida uma aplicação gráfica autônoma do tipo Swing, não é possível dispor do ambiente EE e dos serviços que ele oferece. Isso é um problema. Começam a surgir ambientes EE “autônomos”, c.a.d. que podem ser utilizados fora de um servidor de aplicativos. É o caso do JBoss e do EJB3, que utilizaremos neste documento.
Em um ambiente EE5, as camadas são implementadas por objetos chamados EJB (Enterprise Java Bean). Nas versões anteriores do EE, os EJB (EJB e 2.x) eram considerados difíceis de implementar, testar e, às vezes, de baixo desempenho. Distinguem-se os EJB2.x “entity” e os EJB2.x “session”. Resumindo, um EJB2.x “entity” é a representação de uma linha de uma tabela de banco de dados e um EJB2.x “session” é um objeto utilizado para implementar as camadas [metier], [dao] de uma arquitetura multicamadas. Uma das principais críticas feitas às camadas implementadas com EJB é que elas só podem ser utilizadas dentro de contêineres EJB, um serviço fornecido pelo ambiente EE. Isso dificulta a realização de testes unitários. Assim, no esquema acima, os testes unitários das camadas [metier] e [dao], construídas com EJB, exigiriam a instalação de um servidor de aplicativos, uma operação bastante trabalhosa que não incentiva muito o desenvolvedor a realizar testes com frequência.
O framework Spring surgiu como resposta à complexidade dos EJB2. O Spring fornece, em um ambiente SE, um número significativo dos serviços normalmente oferecidos pelos ambientes EE. Assim, na parte “Persistência de dados”, que nos interessa aqui, o Spring fornece os pools de conexão e os gerenciadores de transações de que as aplicações precisam. O surgimento do Spring promoveu a cultura dos testes unitários, que de repente se tornaram muito mais fáceis de implementar. O Spring permite a implementação das camadas de uma aplicação por meio de objetos Java clássicos (POJO, Plain Old/Ordinary Java Object), possibilitando a reutilização desses objetos em outro contexto. Por fim, ele integra diversas ferramentas de terceiros de maneira bastante transparente, notadamente ferramentas de persistência como Hibernate, Ibatis, ...
O Java EE5 foi concebido para corrigir as lacunas da especificação anterior, EE. Os EJB e 2.x passaram a ser os EJB3. Estes são POJOs marcados por anotações que os tornam objetos específicos quando estão dentro de um contêiner EJB3. Nesse contêiner, o EJB3 poderá se beneficiar dos serviços do contêiner (pool de conexões, gerenciador de transações, etc.). Fora do contêiner EJB3, o EJB3 torna-se um objeto Java normal. Suas anotações EJB são ignoradas.
Acima, representamos o Spring e o JBoss EJB3 como uma possível infraestrutura (framework) para nossa arquitetura multicamadas. É essa infraestrutura que fornecerá os serviços de que precisamos: um pool de conexões e um gerenciador de transações.
- Com o Spring, as camadas serão implementadas com POJOs. Esses terão acesso aos serviços do Spring (pool de conexões, gerenciador de transações) por meio da injeção de dependências nesses POJOs: durante a construção deles, o Spring injeta referências aos serviços de que eles precisarão.
- JBoss EJB3 é um contêiner EJB capaz de funcionar fora de um servidor de aplicativos. Seu princípio de funcionamento (para o desenvolvedor) é análogo ao descrito para o Spring. Encontraremos poucas diferenças.
Concluiremos o documento com um exemplo de aplicação web de três camadas, básico, mas, ainda assim, representativo:
![]() |
3.1. Exemplo 1: Spring / JPA com a entidade Pessoa
Utilizamos a entidade Personne estudada no parágrafo 2.1 e a integramos em uma arquitetura multicamadas, na qual a integração das camadas é feita com o Spring e a camada de persistência é implementada pelo Hibernate.
![]() |
Presume-se que o leitor tenha conhecimentos básicos sobre o Spring. Caso contrário, pode-se consultar o documento a seguir, que explica o conceito de injeção de dependências, que é o cerne do Spring:
[ref3]: Spring IoC (Inversão de Controle) [http://tahe.developpez.com/java/springioc].
3.1.1. O projeto Eclipse / Spring / Hibernate
O projeto Eclipse é o seguinte:
![]() |
![]() |
- em [1]: o projeto Eclipse. Ele pode ser encontrado em [6] nos exemplos do tutorial [5]. Vamos importá-lo.
- em [2]: os códigos Java das camadas apresentados em pacotes:
- [entites]: o pacote de entidades JPA
- [dao]: a camada de acesso aos dados — baseia-se na camada JPA
- [service]: uma camada de serviços, mais do que de negócios. Nela será utilizado o serviço de transações dos contêineres.
- [tests]: reúne os programas de teste.
- em [3]: a biblioteca [jpa-spring] reúne os arquivos JAR necessários para o Spring (veja também [7] e [8]).
- em [4]: a pasta [conf] reúne os arquivos de configuração do Spring para cada um dos SGBD utilizados neste tutorial.
3.1.2. As entidades JPA
![]() |
Há apenas uma entidade gerenciada aqui, a entidade Personne, abordada no parágrafo 2.1, cuja configuração é relembrada a seguir:
package entites;
...
@Entity
@Table(name="jpa01_hb_personne")
public class Personne {
@Id
@Column(name = "ID", nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(name = "VERSION", nullable = false)
@Version
private int version;
@Column(name = "NOM", length = 30, nullable = false, unique = true)
private String nom;
@Column(name = "PRENOM", length = 30, nullable = false)
private String prenom;
@Column(name = "DATENAISSANCE", nullable = false)
@Temporal(TemporalType.DATE)
private Date datenaissance;
@Column(name = "MARIE", nullable = false)
private boolean marie;
@Column(name = "NBENFANTS", nullable = false)
private int nbenfants;
// construtores
public Personne() {
}
public Personne(String nom, String prenom, Date datenaissance, boolean marie,
int nbenfants) {
...
}
// toString
public String toString() {
return String.format("[%d,%d,%s,%s,%s,%s,%d]", getId(), getVersion(),
getNom(), getPrenom(), new SimpleDateFormat("dd/MM/yyyy")
.format(getDatenaissance()), isMarie(), getNbenfants());
}
// getters e setters
...
}
3.1.3. A camada [dao]
![]() | ![]() |
A camada [dao] apresenta a seguinte interface IDao:
package dao;
import java.util.List;
import entites.Personne;
public interface IDao {
// obter uma pessoa por meio de seu identificador
public Personne getOne(Integer id);
// resgatar todas as pessoas
public List<Personne> getAll();
// salvar um contato
public Personne saveOne(Personne personne);
// atualizar um contato
public Personne updateOne(Personne personne);
// excluir uma pessoa pelo seu identificador
public void deleteOne(Integer id);
// obter as pessoas cujo nome corresponde a um padrão
public List<Personne> getAllLike(String modele);
}
A implementação [Dao] dessa interface é a seguinte:
package dao;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import entites.Personne;
public class Dao implements IDao {
@PersistenceContext
private EntityManager em;
// excluir uma pessoa pelo seu identificador
public void deleteOne(Integer id) {
Personne personne = em.find(Personne.class, id);
if (personne == null) {
throw new DaoException(2);
}
em.remove(personne);
}
@SuppressWarnings("unchecked")
// obter todas as pessoas
public List<Personne> getAll() {
return em.createQuery("select p from Personne p").getResultList();
}
@SuppressWarnings("unchecked")
// obter as pessoas cujo nome corresponde a um padrão
public List<Personne> getAllLike(String modele) {
return em.createQuery("select p from Personne p where p.nom like :modele")
.setParameter("modele", modele).getResultList();
}
// obter uma pessoa por meio de seu identificador
public Personne getOne(Integer id) {
return em.find(Personne.class, id);
}
// salvar um usuário
public Personne saveOne(Personne personne) {
em.persist(personne);
return personne;
}
// atualizar um usuário
public Personne updateOne(Personne personne) {
return em.merge(personne);
}
}
- Em primeiro lugar, observe-se a simplicidade da implementação [Dao]. Isso se deve ao uso da camada JPA, que realiza a maior parte do trabalho de acesso aos dados.
- linha 10: a classe [Dao] implementa a interface [IDao]
- linha 13: o objeto do tipo [EntityManager], que será utilizado para manipular o contexto de persistência JPA. Por conveniência, às vezes o confundiremos com o próprio contexto de persistência. O contexto de persistência conterá entidades Personne.
- linha 12: em nenhum ponto do código o campo [EntityManager em] é inicializado. Ele será inicializado pelo Spring ao iniciar a aplicação. É a anotação JPA @PersistenceContext da linha 12 que solicita ao Spring que injete em em um gerenciador de contexto de persistência.
- linhas 26-28: a lista de todas as pessoas é obtida por meio de uma consulta JPQL.
- linhas 32-35: a lista de todas as pessoas cujo nome corresponde a um determinado modelo é obtida por meio de uma consulta JPQL.
- linhas 38-40: a pessoa com determinado identificador é obtida pelo método find do API JPA. Retorna um ponteiro null se a pessoa não existir.
- linhas 43-46: uma pessoa é tornada persistente pelo método `persist` do API JPA. O método torna a pessoa persistente.
- linhas 49-51: a atualização de uma pessoa é realizada pelo método `merge` do API JPA. Esse método só faz sentido se a pessoa assim atualizada estiver previamente desassociada. O método torna persistente a pessoa assim criada.
- linhas 16-22: a exclusão da pessoa cujo identificador nos é passado como parâmetro ocorre em duas etapas:
- linha 17: ela é procurada no contexto de persistência
- linhas 18-20: se não for encontrada, é lançada uma exceção com o código de erro 2
- linha 21: se ela for encontrada, é removida do contexto de persistência com o método remove do API JPA.
- O que não está visível no momento é que cada método será executado dentro de uma transação iniciada pela camada [service].
O aplicativo possui seu próprio tipo de exceção denominado [DaoException]:
package dao;
@SuppressWarnings("serial")
public class DaoException extends RuntimeException {
// código de erro
private int code;
public DaoException(int code) {
super();
this.code = code;
}
public DaoException(String message, int code) {
super(message);
this.code = code;
}
public DaoException(Throwable cause, int code) {
super(cause);
this.code = code;
}
public DaoException(String message, Throwable cause, int code) {
super(message, cause);
this.code = code;
}
// getter e setter
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
- linha 4: [DaoException] deriva de [RuntimeException]. Trata-se, portanto, de um tipo de exceção que o compilador não nos obriga a tratar por meio de um try/catch nem a incluir na assinatura dos métodos. É por esse motivo que [DaoException] não consta na assinatura do método [deleteOne] da interface [IDao]. Isso permite que essa interface seja implementada por uma classe que lance outro tipo de exceção, desde que essa classe também derive de [RuntimeException].
- Para diferenciar os erros que podem ocorrer, utiliza-se o código de erro da linha 7. Os três construtores das linhas 14, 19 e 24 são os da classe pai [RuntimeException], aos quais foi adicionado um parâmetro: o código de erro que se deseja atribuir à exceção.
3.1.4. A camada [metier / service]
![]() |
A camada [service] apresenta a seguinte interface [IService]:
package service;
import java.util.List;
import entites.Personne;
public interface IService {
// obter um usuário por meio de seu identificador
public Personne getOne(Integer id);
// obter todas as pessoas
public List<Personne> getAll();
// salvar um usuário
public Personne saveOne(Personne personne);
// atualizar um usuário
public Personne updateOne(Personne personne);
// excluir uma pessoa pelo seu identificador
public void deleteOne(Integer id);
// obter as pessoas cujo nome corresponde a um padrão
public List<Personne> getAllLike(String modele);
// excluir várias pessoas de uma só vez
public void deleteArray(Personne[] personnes);
// salvar várias pessoas de uma só vez
public Personne[] saveArray(Personne[] personnes);
// atualizar várias pessoas de uma só vez
public Personne[] updateArray(Personne[] personnes);
}
- linhas 8-24: a interface [IService] incorpora os métodos da interface [IDao]
- linha 27: o método [deleteArray] permite excluir um conjunto de pessoas dentro de uma transação: todas as pessoas são excluídas ou nenhuma.
- linhas 30 e 33: métodos análogos ao [deleteArray] para gravar (linha 30) ou atualizar (linha 33) um conjunto de pessoas dentro de uma transação.
A implementação [Service] da interface [IService] é a seguinte:
package service;
...
// todos os métodos da classe são executados em uma transação
@Transactional
public class Service implements IService {
// camada [dao]
private IDao dao;
public IDao getDao() {
return dao;
}
public void setDao(IDao dao) {
this.dao = dao;
}
// excluir várias pessoas de uma só vez
public void deleteArray(Personne[] personnes) {
for (Personne p : personnes) {
dao.deleteOne(p.getId());
}
}
// excluir uma pessoa por meio de seu identificador
public void deleteOne(Integer id) {
dao.deleteOne(id);
}
// obter todas as pessoas
public List<Personne> getAll() {
return dao.getAll();
}
// obter as pessoas cujo nome corresponde a um padrão
public List<Personne> getAllLike(String modele) {
return dao.getAllLike(modele);
}
// obter um usuário por meio de seu identificador
public Personne getOne(Integer id) {
return dao.getOne(id);
}
// salvar várias pessoas de uma só vez
public Personne[] saveArray(Personne[] personnes) {
Personne[] personnes2 = new Personne[personnes.length];
for (int i = 0; i < personnes.length; i++) {
personnes2[i] = dao.saveOne(personnes[i]);
}
return personnes2;
}
// salvar uma pessoa
public Personne saveOne(Personne personne) {
return dao.saveOne(personne);
}
// atualizar várias pessoas de uma só vez
public Personne[] updateArray(Personne[] personnes) {
Personne[] personnes2 = new Personne[personnes.length];
for (int i = 0; i < personnes.length; i++) {
personnes2[i] = dao.updateOne(personnes[i]);
}
return personnes2;
}
// atualizar um usuário
public Personne updateOne(Personne personne) {
return dao.updateOne(personne);
}
}
- linha 6: a anotação Spring @Transactional indica que todos os métodos da classe devem ser executados dentro de uma transação. Uma transação será iniciada antes do início da execução do método e encerrada após a execução. Se ocorrer uma exceção do tipo [RuntimeException] ou derivada durante a execução do método, um rollback automático anula toda a transação; caso contrário, um commit automático a valida. Vale ressaltar que o código Java não precisa se preocupar com transações. Elas são gerenciadas pelo Spring.
- linha 10: uma referência à camada [dao]. Veremos mais adiante que essa referência é inicializada pelo Spring ao iniciar a aplicação.
- Os métodos de [Service] limitam-se a chamar os métodos da interface [IDao dao] da linha 10. Deixamos que o leitor examine o código. Não há dificuldades específicas.
- Mencionamos anteriormente que cada método de [Service] é executado em uma transação. Essa transação está vinculada à thread de execução do método. Nessa thread, são executados métodos da camada [dao]. Esses métodos serão automaticamente vinculados à transação da thread de execução. O método [deleteArray] (linha 21), por exemplo, executa N vezes o método [deleteOne] da camada [dao]. Essas N execuções ocorrerão dentro da thread de execução do método [deleteArray], portanto, dentro da mesma transação. Assim, elas serão todas confirmadas (commit) se tudo correr bem ou todas revertidas (rollback) se ocorrer uma exceção em uma das N execuções do método [deleteOne] da camada [dao].
3.1.5. Configuração das camadas
![]() | ![]() |
A configuração das camadas [service], [dao] e [JPA] é feita por meio dos dois arquivos acima: [META-INF/persistence.xml] e [spring-config.xml]. Os dois arquivos devem estar no classpath do aplicativo, o que explica por que eles estão na pasta [src] do projeto Eclipse. O nome do arquivo [spring-config.xml] pode ser escolhido livremente.
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL" />
</persistence>
- linha 4: o arquivo declara uma unidade de persistência chamada jpa que utiliza transações “locais”, c.a.d, não fornecidas por um contêiner EJB3. Essas transações são criadas e gerenciadas pelo Spring e são configuradas no arquivo [spring-config.xml].
spring-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<!-- camadas de aplicação -->
<bean id="dao" class="dao.Dao" />
<bean id="service" class="service.Service">
<property name="dao" ref="dao" />
</bean>
<!-- camada de persistência JPA -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--
<property name="showSql" value="true" />
-->
<property name="databasePlatform"
value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="generateDdl" value="true" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean
class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<!-- a fonte de dados DBCP -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/jpa" />
<property name="username" value="jpa" />
<property name="password" value="jpa" />
</bean>
<!-- o gerenciador de transações -->
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory"
ref="entityManagerFactory" />
</bean>
<!-- tradução de exceções -->
<bean
class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- anotações de persistência -->
<bean
class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>
- linhas 2-5: a tag raiz <beans> do arquivo de configuração. Não comentaremos os diversos atributos dessa tag. É importante ter cuidado ao copiar e colar, pois um erro em qualquer um desses atributos pode causar erros que, às vezes, são difíceis de entender.
- linha 8: o bean “dao” é uma referência a uma instância da classe [dao.Dao]. Será criada uma única instância (singleton), que implementará a camada [dao] da aplicação.
- linhas 9-11: instanciação da camada [service]. O bean “service” é uma referência a uma instância da classe [service.Service]. Será criada uma única instância (singleton), que implementará a camada [service] do aplicativo. Vimos que a classe [service.Service] possuía um campo privado [IDao dao]. Esse campo é inicializado na linha 10 pelo bean “dao” definido na linha 8.
- No final, as linhas 8 a 11 configuraram as camadas [dao] e [service]. Veremos mais adiante em que momento e como elas serão instanciadas.
- linhas 35-42: uma fonte de dados é definida. Já nos deparamos com o conceito de fonte de dados ao estudar as entidades JPA com o Hibernate:
![]() |
No exemplo acima, [c3p0], chamado de “pool de conexões”, poderia ter sido chamado de “fonte de dados”. Uma fonte de dados fornece o serviço de “pool de conexões”. Com o Spring, utilizaremos uma fonte de dados diferente de [c3p0]. Trata-se de [DBCP], do projeto Apache Commons DBCP [http://jakarta.apache.org/commons/dbcp/]. Os arquivos do [DBCP] foram colocados na biblioteca do usuário [jpa-spring]:
![]() |
- linhas 38-41: para criar conexões com o banco de dados de destino, a fonte de dados precisa saber o driver JDBC utilizado (linha 38), a URL do banco de dados (linha 39), o usuário da conexão e sua senha (linhas 40-41).
- linhas 14-32: configuram a camada JPA
- linhas 14-15: definem um bean do tipo [EntityManagerFactory] capaz de criar objetos do tipo [EntityManager] para gerenciar os contextos de persistência. A classe instanciada [LocalContainerEntityManagerFactoryBean] é fornecida pelo Spring. Ela precisa de alguns parâmetros para ser instanciada, definidos nas linhas 16-31.
- linha 16: a fonte de dados a ser utilizada para obter conexões com o SGBD. Trata-se da fonte [DBCP] definida nas linhas 35 a 42.
- linhas 17 a 27: a implementação JPA a ser utilizada
- linhas 18-26: definem o Hibernate (linha 19) como a implementação JPA a ser utilizada
- linhas 23-24: o dialeto SQL que o Hibernate deve usar com o SGBD de destino, neste caso, MySQL5.
- linha 25: solicita que, ao iniciar o aplicativo, o banco de dados seja gerado (drop e create).
- linhas 28-31: definem um “carregador de classes”. Não sei explicar claramente a função desse bean utilizado pelo EntityManagerFactory da camada JPA. De qualquer forma, isso implica passar para o JVM, que executa o aplicativo, o nome de um arquivo cujo conteúdo irá gerenciar o carregamento das classes ao iniciar o aplicativo. Nesse caso, esse arquivo é o [spring-agent.jar], localizado na biblioteca do usuário [jpa-spring] (veja acima). Veremos que o Hibernate não precisa desse agente, mas que o Toplink precisa dele.
- linhas 45-50: definem o gerenciador de transações a ser utilizado
- linha 45: indica que as transações são gerenciadas com anotações Java (elas também poderiam ter sido declaradas em spring-config.xml). Trata-se, especificamente, da anotação @Transactional encontrada na classe [Service] (linha 6).
- linhas 46-50: o gerenciador de transações
- linha 47: o gerenciador de transações é uma classe fornecida pelo Spring
- linhas 48-49: o gerenciador de transações do Spring precisa conhecer a classe EntityManagerFactory, que gerencia a camada JPA. Trata-se da classe definida nas linhas 14-32.
- linhas 57-58: definem a classe que gerencia as anotações de persistência do Spring encontradas no código Java, como a anotação @PersistenceContext da classe [dao.Dao] (linha 12).
- linhas 53-54: definem a classe Spring que gerencia, entre outras coisas, a anotação @Repository, que torna uma classe assim anotada elegível para a conversão das exceções nativas do driver Jdbc de SGBD em exceções genéricas do Spring do tipo [DataAccessException]. Essa conversão encapsula a exceção nativa do Jdbc em um tipo [DataAccessException], que possui várias subclasses:

Essa conversão permite que o programa cliente gerencie as exceções de forma genérica, independentemente do SGBD de destino. Não utilizamos a anotação @Repository em nosso código Java. Portanto, as linhas 53-54 são desnecessárias. Nós as mantivemos apenas a título informativo.
Concluímos o arquivo de configuração do Spring. Ele é complexo e muitas coisas permanecem obscuras. Ele foi extraído da documentação do Spring. Felizmente, sua adaptação a diversas situações geralmente se resume a duas modificações:
- a alteração do banco de dados de destino: linhas 38-41. Apresentaremos um exemplo com o Oracle.
- a da implementação JPA: linhas 14-32. Apresentaremos um exemplo com o Toplink.
3.1.6. Programa cliente [InitDB]
Abordaremos a criação de um primeiro cliente da arquitetura descrita anteriormente:
![]() |
O código de [InitDB] é o seguinte:
package tests;
...
public class InitDB {
// camada de serviço
private static IService service;
// construtor
public static void main(String[] args) throws ParseException {
// configuração do aplicativo
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
// camada de serviço
service = (IService) ctx.getBean("service");
// esvaziar o banco de dados
clean();
// preenchimento do banco
fill();
// verificação visual
dumpPersonnes();
}
// exibição do conteúdo da tabela
private static void dumpPersonnes() {
System.out.format("[personnes]%n");
for (Personne p : service.getAll()) {
System.out.println(p);
}
}
// preenchimento da tabela
public static void fill() throws ParseException {
// criação de pessoas
Personne p1 = new Personne("p1", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
Personne p2 = new Personne("p2", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
// que se salva
service.saveArray(new Personne[] { p1, p2 });
}
// exclusão de elementos da tabela
public static void clean() {
for (Personne p : service.getAll()) {
service.deleteOne(p.getId());
}
}
}
- linha 12: o arquivo [spring-config.xml] é utilizado para criar um objeto [ApplicationContext ctx], que é uma imagem em memória do arquivo. Os beans definidos em [spring-config.xml] são instanciados nessa ocasião.
- linha 14: solicita-se ao contexto de aplicação ctx uma referência à camada [service]. Sabe-se que esta é representada por um bean chamado “service”.
- linha 16: o banco de dados é esvaziado por meio do método clean das linhas 41-45:
- linhas 42-44: solicita-se a lista de todas as pessoas ao contexto de persistência e faz-se um loop sobre elas para excluí-las uma a uma. Talvez nos lembremos de que o [spring-config.xml] especifica que o banco de dados deve ser gerado no início da aplicação. Portanto, no nosso caso, a chamada do método clean é desnecessária, já que partimos de um banco vazio.
- linha 18: o método fill preenche o banco de dados. Este é definido nas linhas 32-38:
- linhas 34-35: são criados dois registros
- linha 37: solicita-se à camada [service] que as torne persistentes.
- linha 20: o método dumpPersonnes exibe as pessoas persistentes. Ele está definido nas linhas 24 a 29
- linhas 26-28: solicita-se à camada [service] a lista de todas as pessoas persistentes e elas são exibidas no console.
A execução de [InitDB] produz o seguinte resultado:
3.1.7. Testes unitários [TestNG]
A instalação do plug-in [TestNG] é descrita no parágrafo 5.2.4. O código do programa [TestNG] é o seguinte:
package tests;
....
public class TestNG {
// camada de serviço
private IService service;
@BeforeClass
public void init() {
// log
log("init");
// configuração do aplicativo
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
// camada de serviço
service = (IService) ctx.getBean("service");
}
@BeforeMethod
public void setUp() throws ParseException {
// esvaziar o banco de dados
clean();
// preenchimento do banco
fill();
}
// registros
private void log(String message) {
System.out.println("----------- " + message);
}
// exibição do conteúdo da tabela
private void dump() {
log("dump");
System.out.format("[personnes]%n");
for (Personne p : service.getAll()) {
System.out.println(p);
}
}
// preenchimento da tabela
public void fill() throws ParseException {
log("fill");
// criação de pessoas
Personne p1 = new Personne("p1", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
Personne p2 = new Personne("p2", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
// que se salva
service.saveArray(new Personne[] { p1, p2 });
}
// exclusão de elementos da tabela
public void clean() {
log("clean");
for (Personne p : service.getAll()) {
service.deleteOne(p.getId());
}
}
@Test()
public void test01() {
...
}
...
}
- linha 9: a anotação @BeforeClass indica o método a ser executado para inicializar a configuração necessária para os testes. Ela é executada antes da execução do primeiro teste. A anotação @AfterClass, que não é utilizada aqui, indica o método a ser executado após a conclusão de todos os testes.
- linhas 10-17: o método `init`, anotado com @BeforeClass, utiliza o arquivo de configuração do Spring para instanciar as diferentes camadas da aplicação e obter uma referência à camada `[service]`. Todos os testes utilizam essa referência a partir daí.
- linha 19: a anotação @BeforeMethod indica o método a ser executado antes de cada teste. A anotação @AfterMethod, não utilizada aqui, indica o método a ser executado após cada teste.
- linhas 20-25: o método setUp, anotado por @BeforeMethod, esvazia o banco de dados (clean, linhas 52-56) e, em seguida, o preenche com duas pessoas (fill, linhas 42-49).
- linha 59: a anotação @Test indica um método de teste a ser executado. Descreveremos agora esses testes.
@Test()
public void test01() {
log("test1");
dump();
// lista de pessoas
List<Personne> personnes = service.getAll();
assert 2 == personnes.size();
}
@Test()
public void test02() {
log("test2");
// busca de pessoas pelo nome
List<Personne> personnes = service.getAllLike("p1%");
assert 1 == personnes.size();
Personne p1 = personnes.get(0);
assert "Paul".equals(p1.getPrenom());
}
@Test()
public void test03() throws ParseException {
log("test3");
// criação de uma nova pessoa
Personne p3 = new Personne("p3", "x", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
// a pessoa é gravada
service.saveOne(p3);
// solicitação do registro
Personne loadedp3 = service.getOne(p3.getId());
// exibição
System.out.println(loadedp3);
// verificação
assert "p3".equals(loadedp3.getNom());
}
- linhas 2-8: o teste 01. É preciso lembrar que, no início de cada teste, o banco de dados contém duas pessoas, chamadas p1 e p2, respectivamente.
- linha 6: solicita-se a lista de pessoas
- linha 7: verifica-se se o número de pessoas na lista obtida é 2
- linha 14: solicita-se a lista de pessoas cujo nome comece por p1
- verifica-se se a lista obtida contém apenas um elemento (linha 15) e se o nome da única pessoa encontrada é “Paul” (linha 17)
- linha 24: cria-se uma pessoa chamada p3
- linha 25: ela é persistida
- linha 28: solicita-se novamente a pessoa ao contexto de persistência para verificação
- linha 32: verifica-se se a pessoa obtida tem, de fato, o nome p3.
@Test()
public void test04() throws ParseException {
log("test4");
// carregamento do usuário p1
List<Personne> personnes = service.getAllLike("p1%");
Personne p1 = personnes.get(0);
// exibindo
System.out.println(p1);
// verifica-se
assert "p1".equals(p1.getNom());
int version1 = p1.getVersion();
// alterando o nome
p1.setPrenom("x");
// salva-se
service.updateOne(p1);
// recarrega
p1 = service.getOne(p1.getId());
// exibe-se
System.out.println(p1);
// verifica-se se a versão foi incrementada
assert (version1 + 1) == p1.getVersion();
}
- linha 5: solicitamos a pessoa p1
- linha 10: verifica-se o nome dela
- linha 11: registra-se o número de versão dela
- linha 13: altera-se o nome de batismo
- linha 15: salva-se a alteração
- linha 17: solicita-se novamente a pessoa p1
- linha 21: verifica-se se o número de versão aumentou em 1
@Test()
public void test05() {
log("test5");
// carrega a pessoa p2
List<Personne> personnes = service.getAllLike("p2%");
Personne p2 = personnes.get(0);
// exibe-se
System.out.println(p2);
// verifica-se
assert "p2".equals(p2.getNom());
// exclui-se a pessoa p2
service.deleteOne(p2.getId());
// recarrega-se
p2 = service.getOne(p2.getId());
// verifica-se se foi obtido um ponteiro nulo
assert null == p2;
// exibe-se a tabela
dump();
}
- linha 5: solicita-se a pessoa p2
- linha 10: verifica-se o nome dela
- linha 12: a pessoa é excluída
- linha 14: solicita-se novamente a pessoa
- linha 16: verifica-se se ela não foi encontrada
@Test()
public void test06() throws ParseException {
log("test6");
// é criado um registro com duas pessoas com o mesmo nome (viola a regra de exclusividade do nome)
Personne[] personnes = { new Personne("p3", "x", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2),
new Personne("p4", "x", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2),
new Personne("p4", "x", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2)};
// essa tabela é gravada — deve ocorrer uma exceção e um rollback
boolean erreur = false;
try {
service.saveArray(personnes);
} catch (RuntimeException e) {
erreur = true;
}
// dump
dump();
// verificações
assert erreur;
// busca por pessoa com o nome p3
List<Personne> personnesp3 = service.getAllLike("p3%");
assert 0 == personnesp3.size();
// dump
dump();
}
- linha 5: cria-se uma tabela com três pessoas, das quais duas têm o mesmo nome “p4”. Isso viola a regra de exclusividade do nome da @Entity Personne:
@Column(name = "NOM", length = 30, nullable = false, unique = true)
private String nom;
- linha 11: a matriz com as três pessoas é inserida no contexto de persistência. A adição da segunda pessoa, p4, deveria falhar. Como o método [saveArray] é executado em uma transação, todas as inserções que possam ter sido feitas anteriormente serão revertidas. No final, nenhuma adição será feita.
- linha 18: verifica-se se o método [saveArray] realmente lançou uma exceção
- linhas 20-21: verifica-se se a pessoa p3, que poderia ter sido adicionada, não foi adicionada.
@Test()
public void test07() {
log("test7");
// teste de bloqueio otimista
// carregando a pessoa p1
List<Personne> personnes = service.getAllLike("p1%");
Personne p1 = personnes.get(0);
// exibindo
System.out.println(p1);
// aumentando o número de filhos
int nbEnfants1 = p1.getNbenfants();
p1.setNbenfants(nbEnfants1 + 1);
// salvando p1
Personne newp1 = service.updateOne(p1);
assert (nbEnfants1 + 1) == newp1.getNbenfants();
System.out.println(newp1);
// salva-se pela segunda vez — deve ocorrer uma exceção, pois p1 não está mais na versão correta
// é o newp1 que o possui
boolean erreur = false;
try {
service.updateOne(p1);
} catch (RuntimeException e) {
erreur = true;
}
// verificação
assert erreur;
// aumentamos o número de filhos de newp1
int nbEnfants2 = newp1.getNbenfants();
newp1.setNbenfants(nbEnfants2 + 1);
// salvamos newp1
service.updateOne(newp1);
// recarregando
p1 = service.getOne(p1.getId());
// verifica-se
assert (nbEnfants1 + 2) == p1.getNbenfants();
System.out.println(p1);
}
- linha 6: solicita-se a pessoa p1
- linha 12: aumenta-se em 1 o número de filhos
- linha 14: atualiza-se a pessoa p1 no contexto de persistência. O método [updateOne] torna a nova versão newp1 persistente a partir de p1. Ela difere de p1 pelo seu número de versão, que deve ter sido incrementado.
- linha 15: verifica-se o número de filhos de newp1.
- linha 21: solicita-se novamente uma atualização da pessoa p1 a partir da versão anterior p1. Deve ocorrer uma exceção, pois p1 não é a versão mais recente da pessoa p1. Essa versão mais recente é newp1.
- linha 23: verifica-se se o erro realmente ocorreu
- linhas 27-35: verifica-se se, caso uma atualização seja feita a partir da última versão newp1, tudo ocorre normalmente.
@Test()
public void test08() {
log("test8");
// teste de reversão em updateArray
// carregando a pessoa p1
List<Personne> personnes = service.getAllLike("p1%");
Personne p1 = personnes.get(0);
// exibindo
System.out.println(p1);
// aumentando o número de filhos
int nbEnfants1 = p1.getNbenfants();
p1.setNbenfants(nbEnfants1 + 1);
// salva-se duas alterações, sendo que a segunda deve falhar (pessoa inicializada incorretamente)
// devido à transação, ambas devem então ser canceladas
boolean erreur = false;
try {
service.updateArray(new Personne[] { p1, new Personne() });
} catch (RuntimeException e) {
erreur = true;
}
// verificações
assert erreur;
// recarrega-se a pessoa p1
personnes = service.getAllLike("p1%");
p1 = personnes.get(0);
// o número de filhos dela não deve ter mudado
assert nbEnfants1 == p1.getNbenfants();
}
- O teste 8 é semelhante ao teste 6: ele verifica o rollback em um updateArray operando em uma matriz de duas pessoas, na qual a segunda não foi inicializada corretamente. Do ponto de vista do JPA, a operação de mesclagem na segunda pessoa, queexiste ainda irá gerar uma ordem SQL insert que falhará devido às restrições nullable=false existentes em alguns dos campos da entidade Personne.
@Test()
public void test09() {
log("test9");
// teste de reversão em deleteArray
// dump
dump();
// carregando a pessoa p1
List<Personne> personnes = service.getAllLike("p1%");
Personne p1 = personnes.get(0);
// exibindo
System.out.println(p1);
// são feitas duas exclusões, sendo que a segunda deve falhar (pessoa desconhecida)
// devido à transação, ambas devem então ser canceladas
boolean erreur = false;
try {
service.deleteArray(new Personne[] { p1, new Personne() });
} catch (RuntimeException e) {
erreur = true;
}
// verificações
assert erreur;
// recarrega-se a pessoa p1
personnes = service.getAllLike("p1%");
// verificação
assert 1 == personnes.size();
// dump
dump();
}
- o teste 9 é semelhante ao anterior: ele verifica o rollback em um deleteArray que opera em uma tabela com duas pessoas, na qual a segunda não existe. No entanto, nesse caso, o método [deleteOne] da camada [dao] lança uma exceção.
// bloqueio otimista – acesso multithread
@Test()
public void test10() throws Exception {
// adição de uma pessoa
Personne p3 = new Personne("X", "X", new SimpleDateFormat("dd/MM/yyyy").parse("01/02/2006"), true, 0);
service.saveOne(p3);
int id3 = p3.getId();
// criação de N threads para atualizar o número de filhos
final int N = 20;
Thread[] taches = new Thread[N];
for (int i = 0; i < taches.length; i++) {
taches[i] = new ThreadMajEnfants("thread n° " + i, service, id3);
taches[i].start();
}
// aguarda-se o término dos threads
for (int i = 0; i < taches.length; i++) {
taches[i].join();
}
// recuperação da pessoa
p3 = service.getOne(id3);
// ela deve ter N filhos
assert N == p3.getNbenfants();
// exclusão da pessoa p3
service.deleteOne(p3.getId());
// verificação
p3 = service.getOne(p3.getId());
// deve haver um ponteiro nulo
assert p3 == null;
}
- A ideia do teste 10 é iniciar N threads (linha 9) para incrementar, paralelamente, o número de filhos de uma pessoa. Queremos verificar se o sistema de número de versão resiste bem a esse cenário. Ele foi criado para isso.
- linhas 5-6: uma pessoa chamada p3 é criada e, em seguida, persistida. Ela tem 0 filho no início.
- linha 7: registra-se seu identificador.
- linhas 9-14: são iniciadas N threads em paralelo, todas encarregadas de incrementar em 1 o número de filhos de p3.
- linhas 16-18: aguarda-se o término de todas as threads
- linha 20: solicita-se a presença da pessoa p3
- linha 22: verifica-se se ela agora tem N filhos
- linha 24: a pessoa p3 é excluída.
O thread [ThreadMajEnfants] é o seguinte:
package tests;
...
public class ThreadMajEnfants extends Thread {
// nome do thread
private String name;
// referência na camada [service]
private IService service;
// o ID da pessoa com quem vamos trabalhar
private int idPersonne;
// construtor
public ThreadMajEnfants(String name, IService service, int idPersonne) {
this.name = name;
this.service = service;
this.idPersonne = idPersonne;
}
// núcleo do tópico
public void run() {
// acompanhamento
suivi("lancé");
// repetimos o ciclo até conseguirmos incrementar em 1
// o número de filhos da pessoa idPersonne
boolean fini = false;
int nbEnfants = 0;
while (!fini) {
// recupera-se uma cópia da pessoa de idPersonne
Personne personne = service.getOne(idPersonne);
nbEnfants = personne.getNbenfants();
// próximo
suivi("" + nbEnfants + " -> " + (nbEnfants + 1) + " pour la version " + personne.getVersion());
// incrementa em 1 o número de filhos da pessoa
personne.setNbenfants(nbEnfants + 1);
// espera 10 ms para liberar o processador
try {
// continuação
suivi("début attente");
// interrompe-se para liberar o processador
Thread.sleep(10);
// acompanhamento
suivi("fin attente");
} catch (Exception ex) {
throw new RuntimeException(ex.toString());
}
// espera concluída — tentando validar a cópia
// enquanto isso, outras threads podem ter alterado o original
try {
// tentando modificar o original
service.updateOne(personne);
// concluído — o original foi alterado
fini = true;
} catch (javax.persistence.OptimisticLockException e) {
// versão incorreta do objeto: a exceção está sendo ignorada para recomeçar
} catch (org.springframework.transaction.UnexpectedRollbackException e2) {
// exceção do Spring que ocorre de vez em quando
} catch (RuntimeException e3) {
// outro tipo de exceção — estamos repassando-a
throw e3;
}
}
// rastreamento
suivi("a terminé et passé le nombre d'enfants à " + (nbEnfants + 1));
}
// acompanhamento
private void suivi(String message) {
System.out.println(name + " [" + new Date().getTime() + "] : " + message);
}
}
- linhas 15-19: o construtor armazena as informações necessárias para seu funcionamento: seu nome (linha 16), a referência na camada [service] que deve utilizar (linha 17) e o identificador da pessoa p cujo número de filhos deve ser incrementado (linha 18).
- linhas 22-66: o método [run] executado por todas as threads em paralelo.
- linha 29: o thread tenta repetidamente incrementar o número de filhos da pessoa p. Ele só para quando consegue.
- linha 31: a pessoa p é consultada
- linha 36: o número de filhos é incrementado na memória
- linhas 38-47: é feita uma pausa de 10 ms. Isso permitirá que outras threads obtenham a mesma versão da pessoa p. Assim, teremos, ao mesmo tempo, várias threads com a mesma versão da pessoa p e querendo modificá-la. É isso que se deseja.
- linha 52: uma vez terminada a pausa, o thread solicita à camada [service] que persista a modificação. Sabemos que, de vez em quando, ocorrerão exceções; por isso, envolvemos a operação em um try/catch.
- linha 55: os testes mostram que ocorrem exceções do tipo [javax.persistence.OptimisticLockException]. Isso é normal: trata-se da exceção lançada pela camada JPA quando um thread tenta modificar a pessoa p sem possuir a versão mais recente dela. Essa exceção é ignorada para permitir que o thread tente a operação novamente até que consiga.
- linha 57: os testes mostram que também ocorrem exceções do tipo [org.springframework.transaction.UnexpectedRollbackException]. Isso é incômodo e inesperado. Não tenho explicações a dar. Agora estamos dependentes do Spring, embora quiséssemos evitar isso. Isso significa que, se executarmos nossa aplicação no JBoss Ejb3, por exemplo, o código da thread precisará ser alterado. A exceção do Spring também é ignorada aqui para permitir que a thread tente novamente a operação de incremento.
- linha 59: os outros tipos de exceção são repassados para a aplicação.
Quando o [TestNG] é executado, obtemos os seguintes resultados:

Os 10 testes foram aprovados com sucesso.
O teste 10 merece explicações adicionais, pois o fato de ter sido bem-sucedido tem um lado mágico. Voltemos, em primeiro lugar, à configuração da camada [dao]:
public class Dao implements IDao {
@PersistenceContext
private EntityManager em;
- linha 4: um objeto [EntityManager] é injetado no campo `em` por meio da anotação JPA @PersistenceContext. A camada [dao] é instanciada uma única vez. Trata-se de um singleton utilizado por todas as threads que utilizam a camada JPA. Assim, o EntityManager `em` é comum a todas as threads. É possível verificar isso exibindo o valor de `em` no método [updateOne] utilizado pelas threads [ThreadMajEnfants]: obtém-se o mesmo valor para todas as threads.
Assim, podemos nos perguntar se os objetos persistentes das diferentes threads, manipulados pelo EntityManager em — que é o mesmo para todas as threads —, não vão se misturar e criar conflitos entre si. Um exemplo do que poderia acontecer encontra-se no [ThreadMajEnfants]:
while (!fini) {
// recuperamos uma cópia do registro de idPersonne
Personne personne = service.getOne(idPersonne);
nbEnfants = personne.getNbenfants();
// rastreamento
suivi("" + nbEnfants + " -> " + (nbEnfants + 1) + " pour la version " + personne.getVersion());
// incrementa em 1 o número de filhos da pessoa
personne.setNbenfants(nbEnfants + 1);
// espera 10 ms para liberar o processador
try {
// continuação
suivi("début attente");
// interrompe-se para liberar o processador
Thread.sleep(10);
// acompanhamento
suivi("fin attente");
} catch (Exception ex) {
throw new RuntimeException(ex.toString());
}
- linha 3: uma thread T1 recupera a pessoa p
- linha 8: ela incrementa o número de filhos de p
- linha 14: o thread T1 faz uma pausa
Um thread T2 assume o controle e também executa a linha 3: ele solicita a mesma pessoa p que o T1. Se o contexto de persistência dos threads fosse o mesmo, a pessoa p, já presente no contexto graças ao T1, deveria ser devolvida ao T2. De fato, o método [getOne] utiliza o método [EntityManager].O método API acessa o JPA, e esse método só acessa o banco de dados se o objeto solicitado não fizer parte do contexto de persistência; caso contrário, ele retorna o objeto do contexto de persistência. Se fosse esse o caso, T1 e T2 teriam a mesma pessoa p. T2 incrementaria então o número de filhos de p em 1 novamente (linha 8). Se uma das threads conseguir realizar sua atualização após a pausa, o número de filhos de p terá aumentado em 2 e não em 1, como previsto. Seria de se esperar, então, que as N threads aumentassem o número de filhos não para N, mas para um valor maior. No entanto, esse não é o caso. Pode-se, então, concluir que T1 e T2 não têm a mesma referência p. Isso é verificado exibindo-se o endereço de p pelas threads: ele é diferente para cada uma delas.
Portanto, parece que as threads:
- compartilhem o mesmo gerenciador de contexto de persistência (EntityManager)
- mas cada um tenha seu próprio contexto de persistência.
São apenas suposições, e a opinião de um especialista seria útil neste caso.
3.1.8. Alterar para SGBD
![]() |
Para alterar o SGBD, basta substituir o arquivo [src/spring-config.xml] [2] pelo arquivo [spring-config.xml] do SGBD em questão na pasta [conf] [1].
O arquivo [spring-config.xml] da Oracle é, por exemplo, o seguinte:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
...
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--
<property name="showSql" value="true" />
-->
<property name="databasePlatform" value="org.hibernate.dialect.OracleDialect" />
<property name="generateDdl" value="true" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<!-- a fonte de dados DBCP -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
<property name="username" value="jpa" />
<property name="password" value="jpa" />
</bean>
...
</beans>
Apenas algumas linhas mudam em relação ao mesmo arquivo usado anteriormente para o MySQL5:
- linha 14: o dialeto SQL que o Hibernate deve utilizar
- linhas 25-28: as características da conexão JDBC com o SGBD
Sugere-se ao leitor que repita os testes descritos para o MySQL5 com outros SGBD.
3.1.9. Alterar a implementação do JPA
Voltemos à arquitetura dos testes anteriores:
![]() |
Substituímos a implementação JPA / Hibernate por uma implementação JPA / Toplink. Como o Toplink não utiliza as mesmas bibliotecas que o Hibernate, usamos um novo projeto Eclipse:
![]() |
- em [1]: o projeto Eclipse. Ele é idêntico ao anterior. Apenas mudam o arquivo de configuração [spring-config.xml] [2] e a biblioteca [jpa-toplink], que substitui a biblioteca [jpa-hibernate].
- em [3]: a pasta com os exemplos deste tutorial. Em [4], o projeto do Eclipse a ser importado.
O arquivo de configuração [spring-config.xml] para o Toplink passa a ser o seguinte:
<?xml version="1.0" encoding="UTF-8"?>
<!-- o JVM deve ser executado com o argumento -javaagent:C:\data\2006-2007\eclipse\dvp-jpa\lib\spring\spring-agent.jar
(à remplacer par le chemin exact de spring-agent.jar)-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<!-- camadas de aplicação -->
<bean id="dao" class="dao.Dao" />
<bean id="service" class="service.Service">
<property name="dao" ref="dao" />
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<!--
<property name="showSql" value="true" />
-->
<property name="databasePlatform" value="oracle.toplink.essentials.platform.database.MySQL4Platform" />
<property name="generateDdl" value="true" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<!-- a fonte de dados DBCP -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/jpa" />
<property name="username" value="jpa" />
<property name="password" value="jpa" />
</bean>
<!-- o gerenciador de transações -->
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- tradução das exceções -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- persistência -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>
Poucas linhas precisam ser alteradas para mudar do Hibernate para o Toplink:
- linha 19: a implementação JPA agora é feita pelo Toplink
- linha 23: a propriedade [databasePlatform] tem um valor diferente do utilizado com o Hibernate: o nome de uma classe específica do Toplink. Onde encontrar esse nome foi explicado no parágrafo 2.1.15.2.
É isso. Vale destacar a facilidade com que é possível alterar o SGBD ou a implementação JPA com o Spring.
Mas ainda não terminamos completamente. Ao executar [InitDB], por exemplo, ocorre uma exceção que não é fácil de entender:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [spring-config.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Must start with Java agent to use InstrumentationLoadTimeWeaver. See Spring documentation.
Caused by: java.lang.IllegalStateException: Must start with Java agent to use
A mensagem de erro da linha 1 leva a consultar a documentação do Spring. Lá, descobrimos um pouco mais sobre o papel desempenhado por uma declaração obscura do arquivo [spring-config.xml]:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--
<property name="showSql" value="true" />
-->
<property name="databasePlatform" value="org.hibernate.dialect.OracleDialect" />
<property name="generateDdl" value="true" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
A linha 1 da exceção faz referência a uma classe chamada [InstrumentationLoadTimeWeaver], classe que encontramos na linha 13 do arquivo de configuração do Spring. A documentação do Spring explica que essa classe é necessária, em certos casos, para carregar as classes da aplicação e que, para que ela seja utilizada, a JVM deve ser iniciada com um agente. Esse agente é fornecido pelo Spring e se chama [spring-agent]:
![]() |
- o arquivo [spring-agent.jar] está na pasta <exemplos>/lib [1]. Ele é fornecido com a distribuição do Spring 2.x (ver parágrafo 5.11).
- No [3], cria-se uma configuração de execução [Run/Run...]
- em [4], cria-se uma configuração de execução Java (existem diversos tipos de configurações de execução)
![]() |
- em [5], seleciona-se a aba [Main]
- em [6], nomeia-se a configuração
- em [7], nomeie o projeto do Eclipse relacionado a essa configuração (use o botão Browse)
- em [8], nomeie a classe Java que contém o método [main] (use o botão “Browse”)
- em [9], acessa-se a aba [Arguments]. Nela, é possível especificar dois tipos de argumentos:
- em [9], aqueles passados para o método [main]
- em [10], aqueles passados para o JVM, que executará o código. O agente Spring é definido por meio do parâmetro -javaagent:valor do JVM. O valor é o caminho do arquivo [spring-agent.jar].
- no [11]: valida-se a configuração
- no [12]: a configuração é criada
- em [13]: executa-se
Feito isso, o [InitDB] é executado e apresenta os mesmos resultados que com o Hibernate. Para o [TestNG], deve-se proceder da mesma forma:
![]() |
- em [1], cria-se uma configuração de execução [Run/Run...]
- em [2], cria-se uma configuração de execução TestNG
- em [3], seleciona-se a aba [Test]
- em [4], nomeia-se a configuração
- em [5], nomeia-se o projeto Eclipse relacionado a essa configuração (use o botão Browse)
- em [6], nomeie a classe de testes (use o botão “Browse”)
![]() |
- em [7], acessa-se a aba [Arguments].
- em [8]: define-se o argumento -javaagent do JVM.
- no [9]: valida-se a configuração
- no [10]: a configuração é criada
- em [11]: ela é executada
Feito isso, o [TestNG] é executado e apresenta os mesmos resultados que com o Hibernate.
3.2. Exemplo 2: JBoss EJB3 / JPA com a entidade Pessoa
Retomamos o mesmo exemplo anterior, mas o executamos em um contêiner EJB3, o mesmo de JBoss:
![]() |
Um contêiner Ejb3 é normalmente integrado a um servidor de aplicativos. O JBoss fornece um contêiner Ejb3 “autônomo”, que pode ser utilizado fora de um servidor de aplicativos. Veremos que ele oferece serviços semelhantes aos fornecidos pelo Spring. Tentaremos avaliar qual desses contêineres se mostra mais prático.
A instalação do contêiner JBoss EJB3 está descrita no parágrafo 5.12.
3.2.1. O projeto Eclipse / JBoss EJB3 / Hibernate
O projeto Eclipse é o seguinte:
![]() |
![]() |
- em [1]: o projeto Eclipse. Ele pode ser encontrado em [6] nos exemplos do tutorial [5]. Vamos importá-lo.
- em [2]: os códigos Java das camadas apresentados em pacotes:
- [entites]: o pacote de entidades JPA
- [dao]: a camada de acesso aos dados — baseia-se na camada JPA
- [service]: uma camada de serviços, mais do que de negócios. Nela será utilizado o serviço de transações do contêiner EJB3.
- [tests]: reúne os programas de teste.
- em [3]: a biblioteca [jpa-jbossejb3] reúne os arquivos JAR necessários para o JBoss EJB3 (veja também [7] e [8]).
- em [4]: a pasta [conf] reúne os arquivos de configuração para cada um dos SGBD utilizados neste tutorial. Há sempre dois: o [persistence.xml], que configura a camada JPA, e o [jboss-config.xml], que configura o contêiner Ejb3.
3.2.2. As entidades JPA
![]() |
Há apenas uma entidade gerenciada aqui, a entidade Personne, analisada anteriormente no parágrafo 3.1.2.
3.2.3. A camada [dao]
![]() |
A camada [dao] apresenta a interface [IDao] descrita anteriormente no parágrafo 3.1.3.
A implementação [Dao] dessa interface é a seguinte:
package dao;
...
@Stateless
public class Dao implements IDao {
@PersistenceContext
private EntityManager em;
// excluir uma pessoa pelo seu identificador
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteOne(Integer id) {
Personne personne = em.find(Personne.class, id);
if (personne == null) {
throw new DaoException(2);
}
em.remove(personne);
}
// obter todas as pessoas
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public List<Personne> getAll() {
return em.createQuery("select p from Personne p").getResultList();
}
// obter pessoas cujo nome corresponda a um padrão
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public List<Personne> getAllLike(String modele) {
return em.createQuery("select p from Personne p where p.nom like :modele")
.setParameter("modele", modele).getResultList();
}
// obter uma pessoa por meio de seu identificador
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Personne getOne(Integer id) {
return em.find(Personne.class, id);
}
// salvar um usuário
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Personne saveOne(Personne personne) {
em.persist(personne);
return personne;
}
// atualizar um usuário
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Personne updateOne(Personne personne) {
return em.merge(personne);
}
}
- esse código é em todos os aspectos idêntico ao que tínhamos com o Spring. Apenas as anotações Java mudam, e é sobre isso que vamos comentar.
- linha 4: a anotação @Stateless torna a classe [Dao] um EJB sem estado. A anotação @Stateful torna uma classe um EJB com estado. Um EJB com estado possui campos privados cujos valores devem ser mantidos ao longo do tempo. Um exemplo clássico é o de uma classe que contém informações relacionadas ao usuário da web de um aplicativo. Uma instância dessa classe está vinculada a um usuário específico e, quando o thread de execução de uma solicitação desse usuário é concluído, a instância deve ser mantida para estar disponível na próxima solicitação do mesmo cliente. Um EJB @Stateless não possui estado. Retomando o mesmo exemplo, ao final da thread de execução de uma solicitação de um usuário, o EJB @Stateless retorna a um pool de EJBs @Stateless e fica disponível para a thread de execução de uma solicitação de outro usuário.
- Para o desenvolvedor, o conceito de EJB3 @Stateless é semelhante ao do singleton do Spring. Ele o utilizará nos mesmos casos.
- linha 7: a anotação @PersistenceContext é a mesma encontrada na versão Spring da camada [dao]. Ela designa o campo que receberá o EntityManager, o que permitirá que a camada [dao] manipule o contexto de persistência.
- linha 11: a anotação @TransactionAttribute aplicada a um método serve para configurar a transação na qual o método será executado. Aqui estão alguns valores possíveis para essa anotação:
- TransactionAttributeType.REQUIRED: o método deve ser executado em uma transação. Se uma transação já tiver sido iniciada, as operações de persistência do método ocorrem nessa transação. Caso contrário, uma transação é criada e iniciada.
- TransactionAttributeType.REQUIRES_NEW: o método deve ser executado em uma nova transação. Esta é criada e iniciada.
- TransactionAttributeType.MANDATORY: o método deve ser executado em uma transação existente. Se ela não existir, uma exceção é lançada.
- TransactionAttributeType.NEVER: o método nunca é executado em uma transação.
- ...
A anotação poderia ter sido colocada na própria classe:
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Dao implements IDao {
O atributo é, então, aplicado a todos os métodos da classe.
3.2.4. A camada [metier / service]
![]() |
A camada [service] apresenta a interface [IService] analisada anteriormente no parágrafo 3.1.4. A implementação [Service] da interface [IService] é idêntica à implementação analisada anteriormente no parágrafo 3.1.4, com exceção de três detalhes:
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Service implements IService {
// camada [dao]
@EJB
private IDao dao;
public IDao getDao() {
return dao;
}
public void setDao(IDao dao) {
this.dao = dao;
}
- linha 2: a classe [Service] é um EJB sem estado
- linha 3: todos os métodos da classe [Service] devem ser executados em uma transação
- linhas 7-8: uma referência ao EJB da camada [dao] será injetada pelo contêiner EJB no campo [IDao dao] da linha 8. É a anotação @EJB da linha 7 que solicita essa injeção. O objeto injetado deve ser um EJB. Essa é uma diferença importante em relação ao Spring, onde qualquer tipo de objeto pode ser injetado em outro objeto.
3.2.5. Configuração das camadas
![]() |
A configuração das camadas [service], [dao] e [JPA] é feita pelos seguintes arquivos:
- [META-INF/persistence.xml] configura a camada JPA
- [jboss-config.xml] configura o contêiner Ejb3. Ele próprio utiliza os arquivos [default.persistence.properties, ejb3-interceptors-aop.xml, embedded-jboss-beans.xml, jndi.properties]. Esses últimos arquivos são fornecidos com o JBoss Ejb3 e garantem uma configuração padrão que normalmente não é alterada. O desenvolvedor se interessa apenas pelo arquivo [jboss-config.xml]
Vamos examinar os dois arquivos de configuração:
persistence.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="jpa">
<!-- o provedor JPA é o Hibernate -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- a DataSource JTA é gerenciada pelo ambiente Java EE5 -->
<jta-data-source>java:/datasource</jta-data-source>
<properties>
<!-- busca por entidades da camada JBA -->
<property name="hibernate.archive.autodetection" value="class, hbm" />
<!-- logs do Hibernate SQL
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
-->
<!-- o tipo de SGBD gerenciado -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect" />
<!-- recriação de todas as tabelas (drop+create) na implantação da unidade de persistência -->
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
</persistence-unit>
</persistence>
Este arquivo se assemelha aos que já vimos ao estudar as entidades JPA. Ele configura uma camada JPA do Hibernate. As novidades são as seguintes:
- linha 5: a unidade de persistência jpa não possui o atributo transaction-type, que sempre estava presente até agora:
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL" />
Na ausência de valor, o atributo transaction-type tem o valor padrão “JTA” (para Java Transaction API), o que indica que o gerenciador de transações é fornecido por um contêiner EJB3. Um gerenciador “JTA” pode fazer mais do que um gerenciador “RESOURCE_LOCAL”: ele pode gerenciar transações que abrangem várias conexões. Com JTA, é possível abrir uma transação t1 em uma conexão c1 em um SGBD 1, uma transação t2 em uma conexão c2 com um SGBD 2 e ser capaz de considerar (t1,t2) como uma única transação na qual ou todas as operações são bem-sucedidas (commit) ou nenhuma (rollback).
Aqui, estamos trabalhando com o gerenciador JTA do contêiner JBoss EJB3.
- linha 11: declara a fonte de dados que o gerenciador JTA deve utilizar. Ela é fornecida na forma de um nome JNDI (Java Naming and Directory Interface). Essa fonte de dados está definida em [jboss-config.xml].
jboss-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer bean-deployer_1_0.xsd"
xmlns="urn:jboss:bean-deployer:2.0">
<!-- fábrica da DataSource -->
<bean name="datasourceFactory" class="org.jboss.resource.adapter.jdbc.local.LocalTxDataSource">
<!-- nome JNDI da DataSource -->
<property name="jndiName">java:/datasource</property>
<!-- banco de dados gerenciado -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="connectionURL">jdbc:mysql://localhost:3306/jpa</property>
<property name="userName">jpa</property>
<property name="password">jpa</property>
<!-- propriedades do pool de conexões -->
<property name="minSize">0</property>
<property name="maxSize">10</property>
<property name="blockingTimeout">1000</property>
<property name="idleTimeout">100000</property>
<!-- gerenciador de transações, neste caso JTA -->
<property name="transactionManager">
<inject bean="TransactionManager" />
</property>
<!-- gerenciador de cache do Hibernate -->
<property name="cachedConnectionManager">
<inject bean="CachedConnectionManager" />
</property>
<!-- propriedades de instanciação JNDI? -->
<property name="initialContextProperties">
<inject bean="InitialContextProperties" />
</property>
</bean>
<!-- o DataSource é solicitado a uma fábrica -->
<bean name="datasource" class="java.lang.Object">
<constructor factoryMethod="getDatasource">
<factory bean="datasourceFactory" />
</constructor>
</bean>
</deployment>
- linha 3: a tag raiz do arquivo é <deployment>. Esse arquivo de implantação tem como objetivo principal configurar a fonte de dados java:/datasource que foi declarada em persistence.xml.
- A fonte de dados é definida pelo bean “datasource” da linha 38. Observa-se que a fonte de dados é obtida (linha 40) a partir de uma “fábrica” definida pelo bean “datasourceFactory” da linha 7. Para obter a fonte de dados do aplicativo, o cliente deverá chamar o método [getDatasource] da classe factory (linha 39).
- linha 7: a factory, que fornece a fonte de dados, é uma classe JBoss.
- linha 9: o nome JNDI da fonte de dados. Esse nome deve ser o mesmo declarado na tag <jta-data-source> do arquivo persistence.xml. De fato, a camada JPA utilizará esse nome JNDI para solicitar a fonte de dados.
- linhas 12-15: algo mais convencional: as características JDBC da conexão com o SGBD
- linhas 18-21: configuração do pool de conexões interno do contêiner JBoss EJB3.
- linhas 24-26: o gerenciador JTA. A classe [TransactionManager] injetada na linha 25 é definida no arquivo [embedded-jboss-beans.xml].
- linhas 28-30: o cache do Hibernate, um conceito que ainda não abordamos. A classe [CachedConnectionManager], injetada na linha 29, está definida no arquivo [embedded-jboss-beans.xml]. Observe-se que a configuração agora depende do Hibernate, o que nos causará problemas quando quisermos migrar para o Toplink.
- linhas 32-34: configuração do serviço JNDI.
Concluímos a análise do arquivo de configuração do JBoss EJB3. Ele é complexo e muitos aspectos permanecem obscuros. Ele foi extraído do [ref1]. No entanto, seremos capazes de adaptá-lo a outro SGBD (linhas 12-15 de jboss-config.xml, linha 24 de persistence.xml). A migração para o Toplink não foi possível por falta de exemplos.
3.2.6. Programa cliente [InitDB]
Vamos abordar a criação de um primeiro cliente da arquitetura descrita anteriormente:
![]() |
O código de [InitDB] é o seguinte:
package tests;
...
public class InitDB {
// camada de serviço
private static IService service;
// construtor
public static void main(String[] args) throws ParseException, NamingException {
// inicializa-se o contêiner EJB3 JBoss
// os arquivos de configuração ejb3-interceptors-aop.xml e embedded-jboss-beans.xml são processados
EJB3StandaloneBootstrap.boot(null);
// Criação dos beans específicos da aplicação
EJB3StandaloneBootstrap.deployXmlResource("META-INF/jboss-config.xml");
// Implantar todos os EJBs encontrados no classpath (lento, verifica todos)
// EJB3StandaloneBootstrap.scanClasspath();
// São implementados todos os EJB encontrados no classpath do aplicativo
EJB3StandaloneBootstrap.scanClasspath("bin".replace("/", File.separator));
// Inicializa-se o contexto JNDI. O arquivo jndi.properties é processado
InitialContext initialContext = new InitialContext();
// instanciação da camada de serviço
service = (IService) initialContext.lookup("Service/local");
// Esvazia-se o banco de dados
clean();
// preenchendo-a
fill();
// verificação visual
dumpPersonnes();
// parando o contêiner EJB
EJB3StandaloneBootstrap.shutdown();
}
// exibição do conteúdo da tabela
private static void dumpPersonnes() {
System.out.format("[personnes]-------------------------------------------------------------------%n");
for (Personne p : service.getAll()) {
System.out.println(p);
}
}
// preenchimento da tabela
public static void fill() throws ParseException {
// criação de pessoas
Personne p1 = new Personne("p1", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
Personne p2 = new Personne("p2", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
// que se salva
service.saveArray(new Personne[] { p1, p2 });
}
// exclusão de elementos da tabela
public static void clean() {
for (Personne p : service.getAll()) {
service.deleteOne(p.getId());
}
}
}
- A maneira de iniciar o contêiner JBoss EJB3 foi encontrada em [ref1].
- linha 13: o contêiner é iniciado. [EJB3StandaloneBootstrap] é uma classe do contêiner.
- linha 16: a unidade de implantação configurada por [jboss-config.xml] é implantada no contêiner: o gerenciador JTA, a fonte de dados, o pool de conexões, o cache do Hibernate e o serviço JNDI são configurados.
- linha 22: solicita-se ao contêiner que examine a pasta `bin` do projeto Eclipse para localizar os EJBs. Os EJBs das camadas [service] e [dao] serão localizados e gerenciados pelo contêiner.
- linha 25: um contexto JNDI é inicializado. Ele nos servirá para localizar os EJBs.
- linha 28: o EJB correspondente à classe [Service] da camada [service] é solicitado ao serviço JNDI. É possível acessar um EJB localmente (local) ou pela rede (remoto). Aqui, o nome “Service/local” do EJB procurado refere-se à classe [Service] da camada [service] para um acesso local.
- Agora, o aplicativo está implantado e temos uma referência à camada [service]. Estamos na mesma situação que após a linha 11 abaixo do código [InitDB] da versão Spring. Encontramos, então, o mesmo código nas duas versões.
public class InitDB {
// camada de serviço
private static IService service;
// construtor
public static void main(String[] args) throws ParseException {
// configuração do aplicativo
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
// camada de serviço
service = (IService) ctx.getBean("service");
// esvaziar o banco de dados
clean();
// preenchimento do banco
fill();
// verifica-se visualmente
dumpPersonnes();
}
...
- linha 36 (JBoss EJB3): paramos o contêiner EJB3.
A execução de [InitDB] produz os seguintes resultados:
Recomenda-se ao leitor que leia esses logs. Neles, encontram-se informações interessantes sobre o que o contêiner EJB3 está fazendo.
3.2.7. Testes unitários [TestNG]
O código do programa [TestNG] é o seguinte:
package tests;
...
public class TestNG {
// camada de serviço
private IService service = null;
@BeforeClass
public void init() throws NamingException, ParseException {
// log
log("init");
// inicializando o contêiner EJB3 JBoss
// os arquivos de configuração ejb3-interceptors-aop.xml e embedded-jboss-beans.xml são utilizados
EJB3StandaloneBootstrap.boot(null);
// Criação dos beans específicos da aplicação
EJB3StandaloneBootstrap.deployXmlResource("META-INF/jboss-config.xml");
// Implantar todos os EJBs encontrados no classpath (lento, verifica todos)
// EJB3StandaloneBootstrap.scanClasspath();
// São implementados todos os EJB encontrados no classpath do aplicativo
EJB3StandaloneBootstrap.scanClasspath("bin".replace("/", File.separator));
// Inicializa-se o contexto JNDI. O arquivo jndi.properties é processado
InitialContext initialContext = new InitialContext();
// instanciação da camada de serviço
service = (IService) initialContext.lookup("Service/local");
// Esvazia-se o banco de dados
clean();
// preenchendo-a
fill();
// verificação visual
dumpPersonnes();
}
@AfterClass
public void terminate() {
// registro
log("terminate");
// Desligamento do contêiner EJB
EJB3StandaloneBootstrap.shutdown();
}
@BeforeMethod
public void setUp() throws ParseException {
...
}
...
}
- O método init (linhas 10-37), que serve para configurar o ambiente necessário para os testes, utiliza o código explicado anteriormente em [InitDB].
- O método `terminate` (linhas 40-45), que é executado ao final dos testes (presença da anotação @AfterClass), encerra o contêiner Ejb3 (linha 44).
- Todo o restante é idêntico ao que havia na versão Spring.
Os testes são bem-sucedidos:

3.2.8. Alterar para SGBD
![]() |
Para alterar o SGBD, basta substituir o conteúdo da pasta [META-INF] [2] pelo conteúdo da pasta SGBD na pasta [conf] [1]. Tomemos como exemplo o servidor SQL:
O arquivo [persistence.xml] é o seguinte:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="jpa">
<!-- o provedor JPA é o Hibernate -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- a DataSource JTA é gerenciada pelo ambiente Java EE5 -->
<jta-data-source>java:/datasource</jta-data-source>
<properties>
<!-- busca por entidades da camada JBA -->
<property name="hibernate.archive.autodetection" value="class, hbm" />
<!-- logs do Hibernate SQL
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
-->
<!-- o tipo de SGBD gerenciado -->
<property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect" />
<!-- recriação de todas as tabelas (drop+create) na implantação da unidade de persistência -->
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
</persistence-unit>
</persistence>
Apenas uma linha foi alterada:
- linha 24: o dialeto SQL que o Hibernate deve utilizar
O arquivo [jboss-config.xml] do servidor SQL é o seguinte:
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer bean-deployer_1_0.xsd"
xmlns="urn:jboss:bean-deployer:2.0">
<!-- fábrica da DataSource -->
<bean name="datasourceFactory" class="org.jboss.resource.adapter.jdbc.local.LocalTxDataSource">
<!-- nome JNDI da DataSource -->
<property name="jndiName">java:/datasource</property>
<!-- banco de dados gerenciado -->
<property name="driverClass">com.microsoft.sqlserver.jdbc.SQLServerDriver</property>
<property name="connectionURL">jdbc:sqlserver://localhost\\SQLEXPRESS:1246;databaseName=jpa</property>
<property name="userName">jpa</property>
<property name="password">jpa</property>
<!-- propriedades do pool de conexões -->
...
</bean>
</deployment>
Apenas as linhas 12 a 15 foram alteradas: elas definem as características da nova conexão JDBC.
Recomenda-se ao leitor que repita, com outros arquivos SGBD, os testes descritos para o arquivo MySQL5.
3.2.9. Alterar a implementação JPA
Conforme mencionado acima, não encontramos nenhum exemplo de uso do contêiner JBoss EJB3 com o TopLink. Até o momento (junho de 2007), ainda não sei se essa configuração é possível.
3.3. Outros exemplos
Vamos resumir o que foi feito com a entidade Personne. Criamos três arquiteturas para realizar os mesmos testes:
1 - uma implementação Spring/Hibernate
![]() |
2 - uma implementação Spring/Toplink
![]() |
3 - uma implementação Jboss Ejb3 / Hibernate
![]() |
Os exemplos do tutorial abordam essas três arquiteturas, juntamente com outras entidades estudadas na primeira parte do tutorial:
Categoria - Artigo
![]() |
- em [1]: a versão Spring / Hibernate
- em [2]: a versão Spring / Toplink
- em [3]: a versão Jboss Ejb3 / Hibernate
Pessoa - Endereço - Atividade
![]() |
- em [1]: a versão Spring / Hibernate
- em [2]: a versão Spring / Toplink
- em [3]: a versão JBoss EJB3 / Hibernate
Esses exemplos não trazem novidades em relação à arquitetura. Eles simplesmente se inserem em um contexto em que há várias entidades a serem gerenciadas com relações um-para-vários ou vários-para-vários entre si, o que não ocorria nos exemplos com a entidade Personne.
3.4. Exemplo 3: Spring / JPA em uma aplicação web
3.4.1. Apresentação
Retomamos aqui uma aplicação apresentada no documento a seguir:
[ref4]: Noções básicas de desenvolvimento web MVC em Java [http://tahe.developpez.com/java/baseswebmvc/].
Este documento apresenta os fundamentos do desenvolvimento web MVC em Java. Para compreender o exemplo a seguir, o leitor deve possuir esses conhecimentos básicos. A aplicação web utilizará o servidor Tomcat. Sua instalação e utilização no Eclipse são apresentadas no parágrafo 5.3.
A aplicação havia sido desenvolvida com uma camada [dao] baseada na ferramenta Ibatis / SqlMap [http://ibatis.apache.org/], que garantia a ponte entre o modelo relacional e o modelo de objetos. Limitar-nos-emos a substituir o Ibatis pelo JPA. A arquitetura da aplicação será a seguinte:
![]() |
A aplicação web que vamos desenvolver permitirá gerenciar um grupo de pessoas por meio de quatro operações:
- lista das pessoas do grupo
- adição de uma pessoa ao grupo
- alteração de uma pessoa do grupo
- exclusão de uma pessoa do grupo
Essas quatro operações básicas correspondem às de uma tabela de banco de dados. As capturas de tela a seguir mostram as páginas que o aplicativo exibe ao usuário.
![]() |
![]() |
![]() |
![]() |
![]() |
3.4.2. O projeto Eclipse
O projeto Eclipse do aplicativo é o seguinte:
![]() |
- em [1]: o projeto web. Trata-se de um projeto Eclipse do tipo [Dynamic Web Project] [2]. Ele pode ser encontrado em [4], na pasta [3] dos exemplos do tutorial. Vamos importá-lo.
![]() |
- no [5]: as fontes e a configuração das camadas [service, dao, jpa]. Mantemos o que já foi desenvolvido no [dao, entites, service] do projeto Eclipse [hibernate-spring-personnes-metier-dao], analisado no parágrafo 3.1.1. Desenvolvemos apenas a camada [web], representada aqui pelo pacote [web]. Além disso, mantemos os arquivos de configuração [persistence.xml, spring-config.xml] desse projeto, com a única diferença de que utilizaremos o Postgres SGBD, o que se traduz nas seguintes alterações no [spring-config.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
...
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
...
<property name="databasePlatform" value="org.hibernate.dialect.PostgreSQLDialect" />
...
</property>
...
</bean>
<!-- a fonte de dados DBCP -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql:jpa" />
<property name="username" value="jpa" />
<property name="password" value="jpa" />
</bean>
....
</beans>
As linhas 8 e 16 a 19 foram adaptadas para o Postgres.
- No [6]: a pasta [WebContent] contém as páginas JSP do projeto, bem como as bibliotecas necessárias. Estas últimas estão apresentadas no [8]
- O aplicativo pode ser utilizado com diversos SGBD. Basta alterar o arquivo [spring-config.xml]. A pasta [conf] [7] contém o arquivo [spring-config.xml] adaptado a diversos SGBD.
3.4.3. A camada [web]
Nosso aplicativo possui a seguinte arquitetura em camadas:
![]() |
A camada [web] exibirá telas ao usuário para permitir que ele gerencie o grupo de pessoas:
- lista de pessoas do grupo
- adição de uma pessoa ao grupo
- alteração de uma pessoa do grupo
- exclusão de uma pessoa do grupo
Para isso, ela se baseará na camada [service], que, por sua vez, utilizará a camada [dao]. Já apresentamos as telas gerenciadas pela camada [web] (parágrafo 3.4.1). Para descrever a camada web, apresentaremos sucessivamente:
- sua configuração
- suas visualizações
- seu controlador
- alguns testes
3.4.3.1. Configuração da aplicação web
Vamos revisar a arquitetura do projeto Eclipse:
![]() | ![]() |
- no pacote [web], encontramos o controlador da aplicação web: a classe [Application].
- As páginas JSP / JSTL do aplicativo estão em [WEB-INF/vues].
- A pasta [WEB-INF/lib] contém os arquivos de terceiros necessários para a aplicação. Eles estão visíveis na pasta [Web App Libraries].
[web.xml]
O arquivo [web.xml] é o arquivo utilizado pelo servidor web para carregar o aplicativo. Seu conteúdo é o seguinte:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>spring-jpa-hibernate-personnes-crud</display-name>
<!-- ServletPersonne -->
<servlet>
<servlet-name>personnes</servlet-name>
<servlet-class>web.Application</servlet-class>
<init-param>
<param-name>urlEdit</param-name>
<param-value>/WEB-INF/vues/edit.jsp</param-value>
</init-param>
<init-param>
<param-name>urlErreurs</param-name>
<param-value>/WEB-INF/vues/erreurs.jsp</param-value>
</init-param>
<init-param>
<param-name>urlList</param-name>
<param-value>/WEB-INF/vues/list.jsp</param-value>
</init-param>
</servlet>
<!-- Mapeamento ServletPersonne-->
<servlet-mapping>
<servlet-name>personnes</servlet-name>
<url-pattern>/do/*</url-pattern>
</servlet-mapping>
<!-- arquivos de página inicial -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- Página de erro inesperado -->
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/WEB-INF/vues/exception.jsp</location>
</error-page>
</web-app>
- linhas 23-26: as URLs [/do/*] serão processadas pelo servlet [personnes]
- linhas 7-8: o servlet [personnes] é uma instância da classe [Application], uma classe que iremos criar.
- linhas 9-20: definem três parâmetros [urlList, urlEdit, urlErreurs] que identificam as URLs das páginas JSP das visualizações [list, edit, erreurs].
- linhas 28-30: o aplicativo possui uma página inicial padrão [index.jsp], localizada na raiz da pasta do aplicativo web.
- linhas 32-35: o aplicativo possui uma página de erros padrão que é exibida quando o servidor web detecta uma exceção não tratada pelo aplicativo.
- linha 37: a tag <exception-type> indica o tipo de exceção tratada pela diretiva <error-page>; neste caso, o tipo [java.lang.Exception] e seus derivados, ou seja, todas as exceções.
- linha 38: a tag <location> indica a página JSP a ser exibida quando ocorrer uma exceção do tipo definido por <exception-type>. A exceção ocorrida está disponível nessa página em um objeto chamado exception, caso a página contenha a diretiva:
<%@ page isErrorPage="true" %>
- (continuação)
- se <exception-type> especificar um tipo T1 e uma exceção do tipo T2, não derivada de T1, for reportada ao servidor web, este enviará ao cliente uma página de exceção proprietária, geralmente pouco intuitiva. Daí a importância da tag <error-page> no arquivo [web.xml].
[index.jsp]
Essa página é exibida se um usuário solicitar diretamente o contexto do aplicativo sem especificar uma URL, c.a.d. Aqui, [/spring-jpa-hibernate-personnes-crud]. Seu conteúdo é o seguinte:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<c:redirect url="/do/list"/>
[index.jsp] redireciona (linha 4) o cliente para a URL [/do/list]. Essa URL exibe a lista de pessoas do grupo.
3.4.3.2. As páginas JSP / JSTL do aplicativo
A visualização [list.jsp]
Ela serve para exibir a lista de pessoas:

Seu código é o seguinte:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib uri="/WEB-INF/taglibs-datetime.tld" prefix="dt" %>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="<c:url value="/ressources/standard.jpg"/>">
<c:if test="${erreurs!=null}">
<h3>Les erreurs suivantes se sont produites :</h3>
<ul>
<c:forEach items="${erreurs}" var="erreur">
<li><c:out value="${erreur}"/></li>
</c:forEach>
</ul>
<hr>
</c:if>
<h2>Liste des personnes</h2>
<table border="1">
<tr>
<th>Id</th>
<th>Version</th>
<th>Prénom</th>
<th>Nom</th>
<th>Date de naissance</th>
<th>Marié</th>
<th>Nombre d'enfants</th>
<th></th>
</tr>
<c:forEach var="personne" items="${personnes}">
<tr>
<td><c:out value="${personne.id}"/></td>
<td><c:out value="${personne.version}"/></td>
<td><c:out value="${personne.prenom}"/></td>
<td><c:out value="${personne.nom}"/></td>
<td><dt:format pattern="dd/MM/yyyy">${personne.datenaissance.time}</dt:format></td>
<td><c:out value="${personne.marie}"/></td>
<td><c:out value="${personne.nbenfants}"/></td>
<td><a href="<c:url value="/do/edit?id=${personne.id}"/>">Modifier</a></td>
<td><a href="<c:url value="/do/delete?id=${personne.id}"/>">Supprimer</a></td>
</tr>
</c:forEach>
</table>
<br>
<a href="<c:url value="/do/edit?id=-1"/>">Ajout</a>
</body>
</html>
- essa visualização recebe dois elementos em seu modelo:
- o elemento [personnes] associado a um objeto do tipo [List], que contém objetos do tipo [Personne]: uma lista de pessoas.
- o elemento opcional [erreurs] associado a um objeto do tipo [List], que contém objetos do tipo [String]: uma lista de mensagens de erro.
- linhas 31-43: percorre-se a lista ${pessoas} para exibir uma tabela HTML contendo as pessoas do grupo.
- linha 40: a URL apontada pelo link [Modifier] é configurada pelo campo [id] da pessoa atual, para que o controlador associado à URL [/do/edit] saiba qual é a pessoa a ser modificada.
- linha 41: o mesmo procedimento é seguido para o link [Supprimer].
- linha 37: para exibir a data de nascimento da pessoa no formato JJ/MM/AAAA, usa-se a tag <dt> da biblioteca de tags [DateTime] do projeto Apache [Jakarta Taglibs]:

O arquivo de descrição dessa biblioteca de tags está definido na linha 3.
- linha 46: o link [Ajout] para adicionar uma nova pessoa tem como destino a URL [/do/edit], assim como o link [Modifier] da linha 40. É o valor -1 do parâmetro [id] que indica que se trata de uma adição, e não de uma modificação.
- linhas 10-18: se o elemento ${erros} estiver no modelo, exibem-se as mensagens de erro nele contidas.
A visualização [edit.jsp]
Ela serve para exibir o formulário de adição de uma nova pessoa ou de alteração de uma pessoa existente:
![]() |
O código da visualização [edit.jsp] é o seguinte:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib uri="/WEB-INF/taglibs-datetime.tld" prefix="dt" %>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="../ressources/standard.jpg">
<h2>Ajout/Modification d'une personne</h2>
<c:if test="${erreurEdit!=''}">
<h3>Echec de la mise à jour :</h3>
L'erreur suivante s'est produite : ${erreurEdit}
<hr>
</c:if>
<form method="post" action="<c:url value="/do/validate"/>">
<table border="1">
<tr>
<td>Id</td>
<td>${id}</td>
</tr>
<tr>
<td>Version</td>
<td>${version}</td>
</tr>
<tr>
<td>Prénom</td>
<td>
<input type="text" value="${prenom}" name="prenom" size="20">
</td>
<td>${erreurPrenom}</td>
</tr>
<tr>
<td>Nom</td>
<td>
<input type="text" value="${nom}" name="nom" size="20">
</td>
<td>${erreurNom}</td>
</tr>
<tr>
<td>Date de naissance (JJ/MM/AAAA)</td>
<td>
<input type="text" value="${datenaissance}" name="datenaissance">
</td>
<td>${erreurDateNaissance}</td>
</tr>
<tr>
<td>Marié</td>
<td>
<c:choose>
<c:when test="${marie}">
<input type="radio" name="marie" value="true" checked>Oui
<input type="radio" name="marie" value="false">Non
</c:when>
<c:otherwise>
<input type="radio" name="marie" value="true">Oui
<input type="radio" name="marie" value="false" checked>Non
</c:otherwise>
</c:choose>
</td>
</tr>
<tr>
<td>Nombre d'enfants</td>
<td>
<input type="text" value="${nbenfants}" name="nbenfants">
</td>
<td>${erreurNbEnfants}</td>
</tr>
</table>
<br>
<input type="hidden" value="${id}" name="id">
<input type="hidden" value="${version}" name="version">
<input type="submit" value="Valider">
<a href="<c:url value="/do/list"/>">Annuler</a>
</form>
</body>
</html>
Essa visualização apresenta um formulário para adicionar uma nova pessoa ou atualizar uma pessoa existente. Daqui em diante, para simplificar a redação, utilizaremos apenas o termo [mise à jour]. O botão [Valider] (linha 73) aciona o POST do formulário na URL [/do/validate] (linha 16). Se o POST falhar, a visualização [edit.jsp] é exibida novamente com o(s) erro(s) ocorrido(s); caso contrário, a visualização [list.jsp] é exibida.
- A visualização [edit.jsp], exibida tanto em um GET quanto em um POST que falha, recebe os seguintes elementos em seu modelo:
atributo | GET | POST |
identificador da pessoa atualizada | idem | |
sua versão | idem | |
seu nome | nome inserido | |
sobrenome | sobrenome inserido | |
data de nascimento | data de nascimento inserida | |
estado civil | estado civil inserido | |
número de filhos | número de filhos inserido | |
em branco | uma mensagem de erro indicando que a adição ou da modificação no momento da execução do POST, acionado pelo botão [Envoyer]. Vazio se não houver erro. | |
vazio | indica um nome incorreto – vazio caso contrário | |
vazio | indica um sobrenome incorreto – vazio caso contrário | |
vazio | indica uma data de nascimento incorreta – vazio caso contrário | |
vazio | indica um número incorreto de filhos – vazio caso contrário |
- linhas 11-15: se o POST do formulário apresentar erro, será exibido [erreurEdit!=''] e uma mensagem de erro será exibida.
- linha 16: o formulário será enviado para a URL [/do/validate]
- linha 20: o elemento [id] do modelo é exibido
- linha 24: o elemento [version] do modelo é exibido
- linhas 26-32: inserção do nome da pessoa:
- na exibição inicial do formulário (GET), ${prenom} exibe o valor atual do campo [prenom] do objeto [Personne] atualizado e ${erreurPrenom} está vazio.
- em caso de erro após o POST, exibe-se novamente o valor inserido ${prenom}, bem como a eventual mensagem de erro ${erreurPrenom}
- linhas 33-39: inserção do nome da pessoa
- linhas 40-46: inserção da data de nascimento da pessoa
- linhas 47-61: inserção do estado civil da pessoa por meio de um botão de opção. Utiliza-se o valor do campo [marie] do objeto [Personne] para determinar qual dos dois botões de opção deve ser marcado.
- linhas 62-68: inserção do número de filhos da pessoa
- linha 71: um campo oculto HTML denominado [id], cujo valor corresponde ao campo [id] da pessoa que está sendo atualizada; -1 para uma adição, outro valor para uma modificação.
- linha 72: um campo oculto HTML, denominado [version], cujo valor corresponde ao campo [id] da pessoa que está sendo atualizada.
- linha 73: o botão [Valider] do tipo [Submit] do formulário
- linha 74: um link que permite retornar à lista de pessoas. Ele foi denominado [Annuler] porque permite sair do formulário sem validá-lo.
A visualização [exception.jsp]
Ela serve para exibir uma página informando que ocorreu uma exceção não tratada pelo aplicativo e que foi encaminhada ao servidor web.
Por exemplo, vamos excluir uma pessoa que não existe no grupo:
![]() |
O código da visualização [exception.jsp] é o seguinte:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ page isErrorPage="true" %>
<%
response.setStatus(200);
%>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="<c:url value="/ressources/standard.jpg"/>">
<h2>MVC - personnes</h2>
L'exception suivante s'est produite :
<%= exception.getMessage()%>
<br><br>
<a href="<c:url value="/do/list"/>">Retour à la liste</a>
</body>
</html>
- Essa visualização recebe uma chave em seu modelo, o elemento [exception], que é a exceção interceptada pelo servidor web. Para que esse elemento seja incluído no modelo da página JSP pelo servidor web, é necessário que a página tenha definido a tag da linha 3.
- linha 6: define-se o código de estado HTTP da resposta como 200. Esse é o primeiro cabeçalho HTTP da resposta. O código 200 indica ao cliente que sua solicitação foi atendida. Geralmente, um documento HTML foi incluído na resposta do servidor. É o que ocorre neste caso. Se o código de estado HTTP da resposta não for definido como 200, ele terá aqui o valor 500, o que significa que ocorreu um erro. De fato, o servidor web, ao interceptar uma exceção não tratada, considera essa situação anormal e a sinaliza com o código 500. A reação ao código 500 varia de acordo com os navegadores: o Firefox exibe o documento que pode acompanhar essa resposta, enquanto o Firefox ignora esse documento e exibe sua própria página. É por esse motivo que substituímos o código 500 pelo código 200.
- linha 16: o texto da exceção é exibido
- linha 18: é oferecido ao usuário um link para voltar à lista de pessoas
A visualização [erreurs.jsp]
Ela serve para exibir uma página que sinaliza os erros de inicialização do aplicativo, c.a.d, e os erros detectados durante a execução do método [init] do servlet do controlador. Pode ser, por exemplo, a ausência de um parâmetro no arquivo [web.xml], conforme mostra o exemplo abaixo:

O código da página [erreurs.jsp] é o seguinte:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
<c:forEach var="erreur" items="${erreurs}">
<li>${erreur}</li>
</c:forEach>
</ul>
</body>
</html>
A página recebe em seu modelo um elemento [erreurs], que é um objeto do tipo [ArrayList] contendo objetos [String], sendo estes últimos mensagens de erro. Elas são exibidas pelo loop das linhas 13 a 15.
3.4.3.3. O controlador da aplicação
O controlador [Application] está definido no pacote [web]:

Estrut tura e inicialização do controlador
A estrutura do controlador [Application] é a seguinte:
package web;
...
@SuppressWarnings("serial")
public class Application extends HttpServlet {
// parâmetros de instância
private String urlErreurs = null;
private ArrayList erreursInitialisation = new ArrayList<String>();
private String[] paramètres = { "urlList", "urlEdit", "urlErreurs" };
private Map params = new HashMap<String, String>();
// serviço
private IService service = null;
// inicialização
@SuppressWarnings("unchecked")
public void init() throws ServletException {
// recuperando os parâmetros de inicialização do servlet
ServletConfig config = getServletConfig();
// processando os demais parâmetros de inicialização
String valeur = null;
for (int i = 0; i < paramètres.length; i++) {
// valor do parâmetro
valeur = config.getInitParameter(paramètres[i]);
// o parâmetro está presente?
if (valeur == null) {
// registra-se o erro
erreursInitialisation.add("Le paramètre [" + paramètres[i] + "] n'a pas été initialisé");
} else {
// armazenamos o valor do parâmetro
params.put(paramètres[i], valeur);
}
}
// a URL da visualização [erreurs] tem um tratamento especial
urlErreurs = config.getInitParameter("urlErreurs");
if (urlErreurs == null)
throw new ServletException("Le paramètre [urlErreurs] n'a pas été initialisé");
// configuração do aplicativo
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
// camada de serviço
service = (IService) ctx.getBean("service");
// esvazia-se o banco de dados
clean();
// preenchimento do banco
try {
fill();
} catch (ParseException e) {
throw new ServletException(e);
}
}
// preenchimento da tabela
public void fill() throws ParseException {
// criação de pessoas
Personne p1 = new Personne("p1", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
Personne p2 = new Personne("p2", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
// salvando
service.saveArray(new Personne[] { p1, p2 });
}
// exclusão de elementos da tabela
public void clean() {
for (Personne p : service.getAll()) {
service.deleteOne(p.getId());
}
}
// GET
@SuppressWarnings("unchecked")
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
...
}
// exibição da lista de pessoas
private void doListPersonnes(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
// alteração/adição de uma pessoa
private void doEditPersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
// exclusão de uma pessoa
private void doDeletePersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
// confirmação da alteração/adição de uma pessoa
public void doValidatePersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
// exibição do formulário pré-preenchido
private void showFormulaire(HttpServletRequest request, HttpServletResponse response, String erreurEdit) throws ServletException, IOException {
...
}
// envio
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// passando o controle para o GET
doGet(request, response);
}
}
- linhas 21-34: recuperam-se os parâmetros esperados no arquivo [web.xml].
- linhas 37-39: o parâmetro [urlErreurs] deve estar obrigatoriamente presente, pois indica a URL da visualização [erreurs] capaz de exibir eventuais erros de inicialização. Se ele não existir, a aplicação é interrompida com a execução de uma [ServletException] (linha 39). Essa exceção será encaminhada ao servidor web e tratada pela tag <error-page> do arquivo [web.xml]. A visualização [exception.jsp] é, portanto, exibida:

O link [Retour à la liste] acima não funciona. Ao clicar nele, obtém-se a mesma resposta enquanto a aplicação não for modificada e recarregada. Ele é útil para outros tipos de exceções, como já vimos.
- linhas 40-43: utilizam o arquivo de configuração do Spring para recuperar uma referência à camada [service]. Após a inicialização do controlador, seus métodos dispõem de uma referência [service] na camada [service] (linha 15), que serão utilizadas para executar as ações solicitadas pelo usuário. Essas ações serão interceptadas pelo método [doGet], que as encaminhará para um método específico do controlador:
Url | Método HTTP | método do controlador |
GET | doListPersonnes | |
GET | doEditPersonne | |
POST | doValidatePersonne | |
GET | doDeletePersonne |
O método [doGet]
Este método tem como objetivo direcionar o processamento das ações solicitadas pelo usuário para o método correto. Seu código é o seguinte:
// GET
@SuppressWarnings("unchecked")
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// verifica-se como ocorreu a inicialização do servlet
if (erreursInitialisation.size() != 0) {
// passamos o controle para a página de erros
request.setAttribute("erreurs", erreursInitialisation);
getServletContext().getRequestDispatcher(urlErreurs).forward(request, response);
// fim
return;
}
// recuperamos o método de envio da solicitação
String méthode = request.getMethod().toLowerCase();
// recupera-se a ação a ser executada
String action = request.getPathInfo();
// ação?
if (action == null) {
action = "/list";
}
// execução da ação
if (méthode.equals("get") && action.equals("/list")) {
// lista de pessoas
doListPersonnes(request, response);
return;
}
if (méthode.equals("get") && action.equals("/delete")) {
// exclusão de uma pessoa
doDeletePersonne(request, response);
return;
}
if (méthode.equals("get") && action.equals("/edit")) {
// exibição do formulário de adição/alteração de uma pessoa
doEditPersonne(request, response);
return;
}
if (méthode.equals("post") && action.equals("/validate")) {
// validação do formulário de adição/alteração de uma pessoa
doValidatePersonne(request, response);
return;
}
// outros casos
doListPersonnes(request, response);
}
- linhas 7-13: verifica-se se a lista de erros de inicialização está vazia. Caso contrário, exibe-se a visualização [erreurs(erreurs)], que indicará o(s) erro(s).
- linha 15: recupera-se o método [get] ou [post] que o cliente utilizou para fazer sua solicitação.
- linha 17: recupera-se o valor do parâmetro [action] da consulta.
- linhas 23-27: processamento da solicitação [GET /do/list], que solicita a lista de pessoas.
- linhas 28-32: processamento da solicitação [GET /do/delete], que solicita a exclusão de uma pessoa.
- linhas 33-37: processamento da solicitação [GET /do/edit], que solicita o formulário de atualização de uma pessoa.
- linhas 38-42: processamento da solicitação [POST /do/validate], que solicita a validação do dado atualizado.
- linha 44: se a ação solicitada não for uma das cinco anteriores, então procede-se como se fosse [GET /do/list].
O método [doListPersonnes]
Este método processa a solicitação [GET /do/list], que solicita a lista de pessoas:

Seu código é o seguinte:
// exibição da lista de pessoas
private void doListPersonnes(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// o modelo da visualização [list]
request.setAttribute("personnes", service.getAll());
// exibição da visualização [list]
getServletContext().getRequestDispatcher((String) params.get("urlList")).forward(request, response);
}
- linha 4: solicita-se à camada [service] a lista de pessoas do grupo e insere-se essa lista no modelo sob a chave “pessoas”.
- linha 6: exibe-se a visualização [list.jsp] descrita no parágrafo 3.4.3.2.
O método [doDeletePersonne]
Este método processa a consulta [GET /do/delete?id=XX], que solicita a exclusão da pessoa com id=XX. A URL [/do/delete?id=XX] é a dos links [Supprimer] da visualização [list.jsp]:

cujo código é o seguinte:
...
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="<c:url value="/ressources/standard.jpg"/>">
...
<c:forEach var="personne" items="${personnes}">
<tr>
...
<td><a href="<c:url value="/do/edit?id=${personne.id}"/>">Modifier</a></td>
<td><a href="<c:url value="/do/delete?id=${personne.id}"/>">Supprimer</a></td>
</tr>
</c:forEach>
</table>
<br>
<a href="<c:url value="/do/edit?id=-1"/>">Ajout</a>
</body>
</html>
Na linha 12, vemos a URL [/do/delete?id=XX] do link [Supprimer]. O método [doDeletePersonne], que deve processar essa URL, deve remover a pessoa com id=XX e, em seguida, exibir a nova lista de pessoas do grupo. Seu código é o seguinte:
// exclusão de uma pessoa
private void doDeletePersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// recuperação do ID da pessoa
int id = Integer.parseInt(request.getParameter("id"));
// exclui-se a pessoa
service.deleteOne(id);
// redirecionamento para a lista de pessoas
response.sendRedirect("list");
}
- linha 4: a URL processada tem o formato [/do/delete?id=XX]. Recupera-se o valor [XX] do parâmetro [id].
- linha 6: solicita-se à camada [service] a exclusão da pessoa com o ID obtido. Não realizamos nenhuma verificação. Se a pessoa que se pretende excluir não existir, a camada [dao] lança uma exceção que é repassada pela camada [service]. Também não a tratamos aqui, no controlador. Portanto, ela será repassada até o servidor web, que, por configuração, exibirá a página [exception.jsp], descrita no parágrafo 3.4.3.2:

- linha 9: se a exclusão ocorreu (sem exceção), solicita-se ao cliente que seja redirecionado para a URL relativa [list]. Como a página que acabou de ser processada é [/do/delete], a URL de redirecionamento será [/do/list]. O navegador será, portanto, levado a acessar [GET /do/list], o que provocará a exibição da lista de pessoas.
O método [doEditPersonne]
Este método processa a solicitação [GET /do/edit?id=XX], que solicita o formulário de atualização da pessoa com id=XX. A URL [/do/edit?id=XX] é a dos links [Modifier] e a do link [Ajout] da visualização [list.jsp]:

cujo código é o seguinte:
...
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="<c:url value="/ressources/standard.jpg"/>">
...
<c:forEach var="personne" items="${personnes}">
<tr>
...
<td><a href="<c:url value="/do/edit?id=${personne.id}"/>">Modifier</a></td>
<td><a href="<c:url value="/do/delete?id=${personne.id}"/>">Supprimer</a></td>
</tr>
</c:forEach>
</table>
<br>
<a href="<c:url value="/do/edit?id=-1"/>">Ajout</a>
</body>
</html>
Na linha 11, vemos a URL [/do/edit?id=XX] do link [Modifier] e, na linha 17, a URL [/do/edit?id=-1] do link [Ajout]. O método [doEditPersonne] deve exibir o formulário de edição da pessoa com id=XX ou, caso se trate de uma adição, apresentar um formulário vazio.
![]() |
- no [1] acima, o formulário de adição e, no [2], o formulário de modificação.
O código do método [doEditPersonne] é o seguinte:
// alteração/adição de uma pessoa
private void doEditPersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// recupera-se o ID da pessoa
int id = Integer.parseInt(request.getParameter("id"));
// adição ou modificação?
Personne personne = null;
if (id != -1) {
// alteração — recupera-se a pessoa a ser alterada
personne = service.getOne(id);
request.setAttribute("id", personne.getId());
request.setAttribute("version", personne.getVersion());
} else {
// adição — cria-se uma pessoa vazia
personne = new Personne();
request.setAttribute("id", -1);
request.setAttribute("version", -1);
}
// coloca-se o objeto [Personne] na sessão do usuário
request.getSession().setAttribute("personne", personne);
// e no modelo da visualização [edit]
request.setAttribute("erreurEdit", "");
request.setAttribute("prenom", personne.getPrenom());
request.setAttribute("nom", personne.getNom());
Date dateNaissance = personne.getDatenaissance();
if (dateNaissance != null) {
request.setAttribute("datenaissance", new SimpleDateFormat("dd/MM/yyyy").format(dateNaissance));
} else {
request.setAttribute("datenaissance", "");
}
request.setAttribute("marie", personne.isMarie());
request.setAttribute("nbenfants", personne.getNbenfants());
// exibição da vista [edit]
getServletContext().getRequestDispatcher((String) params.get("urlEdit")).forward(request, response);
}
- o GET tem como destino uma URL do tipo [/do/edit?id=XX]. Na linha 4, recuperamos o valor de [id]. Em seguida, há dois casos:
- se o id for diferente de -1, trata-se de uma modificação e é necessário exibir um formulário pré-preenchido com as informações da pessoa a ser modificada. Na linha 9, essa pessoa é solicitada à camada [service].
- Se o id for igual a -1, trata-se de uma adição e é necessário exibir um formulário vazio. Para isso, é criado um registro vazio na linha 14.
- Em ambos os casos, os elementos [id, version] do modelo da página [edit.jsp], descrito no parágrafo 3.4.3.2, são inicializados.
- O objeto [Personne] obtido é inserido no modelo da página [edit.jsp]. Esse modelo inclui os seguintes elementos: [erreurEdit, id, version, prenom, erreurPrenom, nom, erreurNom, datenaissance, erreurDateNaissance, marie, nbenfants, erreurNbEnfants]. Esses elementos são inicializados nas linhas 19 a 31, com exceção daqueles cujo valor é a string vazia [erreurPrenom, erreurNom, erreurDateNaissance, erreurNbEnfants]. Sabe-se que, na ausência deles no modelo, a biblioteca JSTL exibirá uma string vazia como valor. Embora o elemento [erreurEdit] também tenha como valor uma string vazia, ele é, no entanto, inicializado, pois é realizada uma verificação de seu valor na página [edit.jsp].
- Assim que o modelo estiver pronto, o controle é passado para a página [edit.jsp], linha 33, que irá gerar a visualização [edit].
O método [doValidatePersonne]
Este método processa a solicitação [POST /do/validate], que valida o formulário de atualização. Essa solicitação POST é acionada pelo botão [Valider]:

Recordemos os campos de preenchimento do formulário HTML da visualização acima:
<form method="post" action="<c:url value="/do/validate"/>">
...
<input type="text" value="${nom}" name="nom" size="20">
...
<input type="text" value="${datenaissance}" name="datenaissance">
...
<c:choose>
<c:when test="${marie}">
<input type="radio" name="marie" value="true" checked>Oui
<input type="radio" name="marie" value="false">Non
</c:when>
<c:otherwise>
<input type="radio" name="marie" value="true">Oui
<input type="radio" name="marie" value="false" checked>Non
</c:otherwise>
</c:choose>
...
<input type="text" value="${nbenfants}" name="nbenfants">
...
<input type="hidden" value="${id}" name="id">
<input type="hidden" value="${version}" name="version">
<input type="submit" value="Valider">
<a href="<c:url value="/do/list"/>">Annuler</a>
</form>
A consulta POST contém os parâmetros [prenom, nom, datenaissance, marie, nbenfants, id] e é enviada para a URL [/do/validate] (linha 1). Ela é processada pelo seguinte método [doValidatePersonne]:
// validação da modificação/adição de uma pessoa
public void doValidatePersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// recuperação dos elementos lançados
boolean formulaireErroné = false;
boolean erreur;
// o nome
String prenom = request.getParameter("prenom").trim();
// o nome é válido?
if (prenom.length() == 0) {
// registra-se o erro
request.setAttribute("erreurPrenom", "Le prénom est obligatoire");
formulaireErroné = true;
}
// o sobrenome
String nom = request.getParameter("nom").trim();
// o nome é válido?
if (nom.length() == 0) {
// registrando o erro
request.setAttribute("erreurNom", "Le nom est obligatoire");
formulaireErroné = true;
}
// data de nascimento
Date datenaissance = null;
try {
datenaissance = new SimpleDateFormat("dd/MM/yyyy").parse(request.getParameter("datenaissance").trim());
} catch (ParseException e) {
// registrando o erro
request.setAttribute("erreurDateNaissance", "Date incorrecte");
formulaireErroné = true;
}
// estado civil
boolean marie = Boolean.parseBoolean(request.getParameter("marie").trim());
// número de filhos
int nbenfants = 0;
erreur = false;
try {
nbenfants = Integer.parseInt(request.getParameter("nbenfants").trim());
if (nbenfants < 0) {
erreur = true;
}
} catch (NumberFormatException ex) {
// observa-se o erro
erreur = true;
}
// número de filhos incorreto?
if (erreur) {
// o erro é sinalizado
request.setAttribute("erreurNbEnfants", "Nombre d'enfants incorrect");
formulaireErroné = true;
}
// ID da pessoa
int id = Integer.parseInt(request.getParameter("id"));
// o formulário está com erros?
if (formulaireErroné) {
// o formulário é exibido novamente com as mensagens de erro
showFormulaire(request, response, "");
// concluído
return;
}
// o formulário está correto — atualiza-se o registro da pessoa que foi inserida na sessão
// com as informações enviadas pelo cliente
Personne personne = (Personne)request.getSession().getAttribute("personne");
personne.setDatenaissance(datenaissance);
personne.setMarie(marie);
personne.setNbenfants(nbenfants);
personne.setNom(nom);
personne.setPrenom(prenom);
// persistência
try {
if (id == -1) {
// criação
service.saveOne(personne);
} else {
// atualização
service.updateOne(personne);
}
} catch (DaoException ex) {
// o formulário é exibido novamente com a mensagem do erro ocorrido
showFormulaire(request, response, ex.getMessage());
// concluído
return;
}
// redireciona para a lista de pessoas
response.sendRedirect("list");
}
// exibição do formulário pré-preenchido
private void showFormulaire(HttpServletRequest request, HttpServletResponse response, String erreurEdit) throws ServletException, IOException {
// prepara-se o modelo da visualização [edit]
request.setAttribute("erreurEdit", erreurEdit);
request.setAttribute("id", request.getParameter("id"));
request.setAttribute("version", request.getParameter("version"));
request.setAttribute("prenom", request.getParameter("prenom").trim());
request.setAttribute("nom", request.getParameter("nom").trim());
request.setAttribute("datenaissance", request.getParameter("datenaissance").trim());
request.setAttribute("marie", request.getParameter("marie"));
request.setAttribute("nbenfants", request.getParameter("nbenfants").trim());
// exibição da visualização [edit]
getServletContext().getRequestDispatcher((String) params.get("urlEdit")).forward(request, response);
}
- linhas 7-13: o parâmetro [prenom] da solicitação POST é recuperado e sua validade é verificada. Caso esteja incorreto, o elemento [erreurPrenom] é inicializado com uma mensagem de erro e inserido nos atributos da consulta.
- linhas 15-21: procede-se de maneira semelhante para o parâmetro [nom]
- linhas 23-30: procede-se de maneira semelhante para o parâmetro [datenaissance]
- linha 32: recupera-se o parâmetro [marie]. Não se verifica sua validade porque, a princípio, ele provém do valor de um botão de opção. Dito isso, nada impede que um programa gere um [POST /.../do/validate] acompanhado de um parâmetro [marie] inventado. Portanto, devemos testar a validade desse parâmetro. Aqui, contamos com nosso gerenciamento de exceções, que faz com que a página [exception.jsp] seja exibida caso o controlador não as gerencie por conta própria. Portanto, se a conversão do parâmetro [marie] em booleano falhar na linha 32, será gerada uma exceção que resultará no envio da página [exception.jsp] ao cliente. Esse funcionamento nos satisfaz.
- linhas 34-50: recuperamos o parâmetro [nbenfants] e verificamos seu valor.
- linha 52: recuperamos o parâmetro [id] sem verificar seu valor
- linhas 54-59: se o formulário estiver com erros, ele é exibido novamente com as mensagens de erro geradas anteriormente
- linhas 62-67: se estiver válido, cria-se um novo objeto [Personne] com os elementos do formulário
- linhas 69-82: o usuário é salvo. O salvamento pode falhar. Em um ambiente com vários usuários, o usuário a ser alterado pode ter sido excluído ou já alterado por outra pessoa. Nesse caso, a camada [dao] lançará uma exceção que é tratada aqui.
- linha 84: se não houver exceção, redirecionamos o cliente para a URL [/do/list] para apresentar a ele o novo estado do grupo.
- linha 79: se ocorreu uma exceção durante o salvamento, solicitamos novamente a exibição do formulário inicial, passando a mensagem de erro da exceção (3º parâmetro).
O método [showFormulaire] (linhas 88-97) constrói o modelo necessário para a página [edit.jsp] com os valores inseridos (request.getParameter(" ... ")). Vale lembrar que as mensagens de erro já foram inseridas no modelo pelo método [doValidatePersonne]. A página [edit.jsp] é exibida na linha 99.
3.4.4. Testes da aplicação web
Vários testes foram apresentados no parágrafo 3.4.1. Convidamos o leitor a repeti-los. Mostramos aqui outras capturas de tela que ilustram os casos de conflitos de acesso aos dados em um ambiente com vários usuários:
[Firefox] será o navegador do usuário U1. Este solicita a URL [http://localhost:8080/spring-jpa-hibernate-personnes-crud/do/list]:

[IE7] será o navegador do usuário U2. Este solicita a mesma URL:

O usuário U1 acessa a edição do perfil da pessoa [p2]:

O usuário U2 faz o mesmo:

O usuário U1 faz alterações e confirma:
![]() |
O usuário U2 faz o mesmo:
![]() |
O usuário U2 retorna à lista de pessoas por meio do link [Retour à la liste] do formulário:

Ele encontra a pessoa [Lemarchand] tal como U1 a modificou (casada, 2 filhos). O número da versão do p2 mudou. Agora, U2 exclui [p2]:
![]() |
U1 ainda mantém sua própria lista e deseja modificar [p2] novamente:
![]() |
U1 usa o link [Retour à la liste] para ver do que se trata:

Ele descobre que, de fato, o [p2] não faz mais parte da lista...
3.4.5. Versão 2
Modificamos ligeiramente a versão anterior para usar os arquivos das camadas [service, dao, jpa], em vez de seus códigos-fonte:
![]() |
- em [1]: o novo projeto Eclipse. Observe-se o desaparecimento dos pacotes [service, dao, entites]. Estes foram encapsulados no arquivo [service-dao-jpa-personne.jar] [2], localizado em [WEB-INF/lib].
- A pasta do projeto está em [4]. Vamos importá-la.
Não há mais nada a fazer. Quando a nova aplicação web é iniciada e solicitamos a lista de pessoas, recebemos a seguinte resposta:
![]() |
O Hibernate não encontra a entidade [Personne]. Para resolver esse problema, é necessário declarar explicitamente em [persistence.xml] as entidades gerenciadas:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
<class>entites.Personne</class>
</persistence-unit>
</persistence>
- linha 7: a entidade Personne é declarada.
Feito isso, a exceção desaparece:
![]() |
3.4.6. Alterar a implementação JPA
![]() |
- para [1]: o novo projeto Eclipse
- em [2]: as bibliotecas Toplink substituíram as bibliotecas Hibernate
- a pasta do projeto está em [4]. Ela será importada.
A mudança de implementação JPA implica apenas algumas alterações no arquivo [spring-config.xml]. Nada mais muda. As alterações feitas no arquivo [spring-config.xml] foram explicadas no parágrafo 3.1.9:
<?xml version="1.0" encoding="UTF-8"?>
<!-- o JVM deve ser executado com o argumento -javaagent:C:\data\2006-2007\eclipse\dvp-jpa\lib\spring\spring-agent.jar
(à remplacer par le chemin exact de spring-agent.jar)-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
...
<property name="databasePlatform" value="oracle.toplink.essentials.platform.database.MySQL4Platform" />
...
</bean>
...
</beans>
São poucas as linhas que precisam ser alteradas para migrar do Hibernate para o Toplink:
- linha 11: a implementação JPA agora é feita pelo Toplink
- linha 13: a propriedade [databasePlatform] tem um valor diferente do utilizado no Hibernate: o nome de uma classe específica do Toplink. Onde encontrar esse nome foi explicado no parágrafo 2.1.15.2.
É isso. Observe a facilidade com que é possível alterar o SGBD ou a implementação JPA com o Spring. No entanto, ainda não terminamos completamente. Ao executar a aplicação, ocorre uma exceção:
![]() |
Reconhece-se aqui um problema encontrado e descrito no parágrafo 3.1.9. Ele é resolvido executando o JVM com um agente Spring. Para isso, altera-se a configuração de execução do Tomcat:
![]() |
- para [1]: escolheu-se a opção [Run / Run...] para alterar a configuração do Tomcat
- para [2]: selecionamos a guia [Arguments]
- em [3]: adicionamos o parâmetro -javaagent conforme descrito no parágrafo 3.1.9.
Feito isso, é possível solicitar a lista de pessoas:

3.5. Outros exemplos
Gostaríamos de ter mostrado um exemplo na web em que o contêiner Spring fosse substituído pelo contêiner Jboss Ejb3, abordado no parágrafo 3.2:
![]() |
- em [1]: o projeto Eclipse
- em [3]: sua localização na pasta de exemplos. Vamos importá-lo.
Retomamos a configuração [jboss-config.xml, persistence.xml] descrita no parágrafo 3.2 e, em seguida, modificamos o método [init] do controlador [Application.java] da seguinte maneira:
// init
@SuppressWarnings("unchecked")
public void init() throws ServletException {
try {
// recuperam-se os parâmetros de inicialização do servlet
ServletConfig config = getServletConfig();
// processa-se os demais parâmetros de inicialização
String valeur = null;
for (int i = 0; i < paramètres.length; i++) {
// valor do parâmetro
valeur = config.getInitParameter(paramètres[i]);
// o parâmetro está presente?
if (valeur == null) {
// registra-se o erro
erreursInitialisation.add("Le paramètre [" + paramètres[i] + "] n'a pas été initialisé");
} else {
// armazenamos o valor do parâmetro
params.put(paramètres[i], valeur);
}
}
// a URL da visualização [erreurs] tem um tratamento especial
urlErreurs = config.getInitParameter("urlErreurs");
if (urlErreurs == null)
throw new ServletException("Le paramètre [urlErreurs] n'a pas été initialisé");
// configuração do aplicativo
// inicializa-se o contêiner EJB3 JBoss
// os arquivos de configuração ejb3-interceptors-aop.xml e embedded-jboss-beans.xml são utilizados
EJB3StandaloneBootstrap.boot(null);
// Criação dos beans específicos da aplicação
EJB3StandaloneBootstrap.deployXmlResource("META-INF/jboss-config.xml");
// são implantados todos os EJB encontrados no classpath da aplicação
//EJB3StandaloneBootstrap.scanClasspath("WEB-INF/classes".replace("/", File.separator));
EJB3StandaloneBootstrap.scanClasspath();
// Inicializa-se o contexto JNDI. O arquivo jndi.properties é processado
InitialContext initialContext = new InitialContext();
// instanciação da camada de serviço
service = (IService) initialContext.lookup("Service/local");
// esvazia-se o banco de dados
clean();
// preenchendo-a
fill();
} catch (Exception e) {
throw new ServletException(e);
}
}
- linhas 28-38: iniciamos o contêiner Ejb3. Este substitui o contêiner Spring.
- linha 41: solicitamos uma referência à camada [service] do aplicativo.
A princípio, essas são as únicas alterações a serem feitas. Na execução, ocorre o seguinte erro:
![]() |
Não consegui entender onde exatamente estava o problema. A exceção relatada pelo Tomcat parece indicar que o objeto denominado “TransactionManager” foi solicitado ao serviço JNDI e que este não o reconheceu. Deixo aos leitores a tarefa de encontrar uma solução para esse problema. Se for encontrada uma solução, ela será incorporada ao documento.





























































