4. JPA Resumo do
Propomos apresentar o JPA (Java Persistence API) com alguns exemplos. O JPA é abordado no curso:
- Persistência Java 5 na prática: [http://tahe.developpez.com/java/jpa] — fornece as ferramentas para construir a camada de acesso aos dados com JPA
4.1. O papel do JPA em uma arquitetura em camadas
Recomenda-se ao leitor que releia o início deste documento (parágrafo 2), que explica o papel da camada JPA em uma arquitetura em camadas. A camada JPA se insere nas camadas de acesso aos dados:
![]() |
A camada [DAO] interage com a especificação JPA. Independentemente do produto que a implemente, a interface da camada JPA apresentada à camada [DAO] permanece a mesma. Apresentamos a seguir alguns exemplos extraídos de [ref1] que nos permitirão construir nossa própria camada JPA.
4.2. JPA — exemplos
4.2.1. Exemplo 1 — Representação de objeto de uma única tabela
4.2.1.1. A tabela [personne]
Consideremos um banco de dados com uma única tabela [personne], cuja função é armazenar algumas informações sobre indivíduos:
![]() |
chave primária da tabela | |
versão do registro na tabela. Sempre que a pessoa é alterada, seu número de versão é incrementado. | |
nome da pessoa | |
seu nome | |
data de nascimento | |
número inteiro 0 (solteiro) ou 1 (casado) | |
número de filhos da pessoa |
4.2.1.2. A entidade [Personne]
Estamos no seguinte ambiente de execução:
![]() |
A camada JPA [5] deve servir de ponte entre o mundo relacional do banco de dados [7] e o mundo de objetos [4] manipulado pelos programas Java [3]. Essa ponte é estabelecida por meio de configuração, e há duas maneiras de fazê-lo:
- com arquivos XML. Essa era praticamente a única maneira de fazer isso até o advento do JDK 1.5
- com anotações Java a partir do JDK 1.5
Neste documento, utilizaremos exclusivamente o segundo método.
O objeto [Personne] correspondente à tabela [personne] apresentada anteriormente poderia ser o seguinte:
...
@SuppressWarnings("unused")
@Entity
@Table(name="Personne")
public class Personne implements Serializable{
@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;
// fabricantes
public Personne() {
}
public Personne(String nom, String prenom, Date datenaissance, boolean marie,
int nbenfants) {
setNom(nom);
setPrenom(prenom);
setDatenaissance(datenaissance);
setMarie(marie);
setNbenfants(nbenfants);
}
// toString
public String toString() {
...
}
// getters e setters
...
}
A configuração é feita por meio de anotações Java @Annotation. As anotações Java são interpretadas pelo compilador ou por ferramentas especializadas no momento da execução. Com exceção da anotação da linha 3, destinada ao compilador, todas as anotações aqui se destinam à implementação JPA utilizada, seja Hibernate ou Toplink. Portanto, elas serão interpretadas na execução. Na ausência de ferramentas capazes de interpretá-las, essas anotações são ignoradas. Assim, a classe [Personne] acima poderia ser utilizada em um contexto fora do JPA.
É preciso distinguir dois casos de uso das anotações JPA em uma classe C associada a uma tabela T:
- a tabela T já existe: as anotações JPA devem, nesse caso, reproduzir o que já existe (nome e definição das colunas, restrições de integridade, chaves estrangeiras, chaves primárias, etc.)
- a tabela T não existe e será criada com base nas anotações encontradas na classe C.
O caso 2 é o mais fácil de lidar. Com a ajuda das anotações JPA, indicamos a estrutura da tabela T que desejamos. O caso 1 costuma ser mais complexo. A tabela T pode ter sido criada, há muito tempo, fora de qualquer contexto JPA. Sua estrutura pode, portanto, estar mal adaptada à ponte relacional/objeto de JPA. Para simplificar, vamos considerar o caso 2, em que a tabela T associada à classe C será criada com base nas anotações JPA da classe C.
Vamos comentar as anotações JPA da classe [Personne]:
- linha 4: a anotação @Entity é a primeira anotação indispensável. Ela é colocada antes da linha que declara a classe e indica que a classe em questão deve ser gerenciada pela camada de persistência JPA. Na ausência dessa anotação, todas as outras anotações JPA seriam ignoradas.
- linha 5: a anotação @Table indica a tabela do banco de dados que a classe representa. Seu principal argumento é name, que indica o nome da tabela. Na ausência desse argumento, a tabela receberá o nome da classe, neste caso, [Personne]. Em nosso exemplo, a anotação @Table é, portanto, supérflua.
- linha 8: a anotação @Id serve para indicar o campo na classe que representa a chave primária da tabela. Essa anotação é obrigatória. Ela indica, neste caso, que o campo id da linha 11 representa a chave primária da tabela.
- linha 9: a anotação @Column serve para estabelecer a ligação entre um campo da classe e a coluna da tabela que esse campo representa. O atributo name indica o nome da coluna na tabela. Na ausência desse atributo, a coluna recebe o mesmo nome do campo. No nosso exemplo, o argumento name não era, portanto, obrigatório. O argumento nullable=false indica que a coluna associada ao campo não pode ter o valor NULL e que, portanto, o campo deve necessariamente ter um valor.
- linha 10: a anotação @GeneratedValue indica como a chave primária é gerada quando criada automaticamente pelo SGBD. Esse será o caso em todos os nossos exemplos. Isso não é obrigatório. Assim, nosso aluno poderia ter um número de matrícula que serviria como chave primária e que não seria gerado pelo SGBD, mas definido pela aplicação. Nesse caso, a anotação @GeneratedValue estaria ausente. O argumento strategy indica como a chave primária é gerada quando produzida pelo SGBD. Nem todos os SGBD utilizam a mesma técnica para gerar os valores da chave primária. Por exemplo:
utiliza um gerador de valores chamado antes de cada inserção | |
o campo da chave primária é definido como tendo o tipo Identity. O resultado é semelhante ao gerador de valores do Firebird, exceto que o valor da chave só é conhecido após a inserção da linha. | |
utiliza um objeto chamado SEQUENCE que, mais uma vez, desempenha a função de um gerador de valores |
A camada JPA deve gerar ordens SQL diferentes de acordo com os SGBD para criar o gerador de valores. Por meio da configuração, é indicado a ela o tipo de SGBD que ela deve gerenciar. Assim, ela pode saber qual é a estratégia habitual de geração de valores de chave primária desse SGBD. O argumento strategy = GenerationType.AUTO indica à camada JPA que ela deve utilizar essa estratégia habitual. Essa técnica funcionou em todos os exemplos deste documento para os sete SGBD utilizados.
- linha 14: a anotação @Version identifica o campo utilizado para gerenciar acessos simultâneos a uma mesma linha da tabela.
Para compreender esse problema de acesso simultâneo a uma mesma linha da tabela [personne], suponhamos que um aplicativo web permita a atualização de uma pessoa e examinemos o seguinte caso:
No momento T1, um usuário U1 inicia a edição de um registro de pessoa P. Nesse momento, o número de filhos é 0. Ele altera esse número para 1, mas antes que ele confirme sua alteração, um usuário U2 inicia a edição do mesmo perfil P. Como U1 ainda não confirmou sua alteração, U2 vê na tela que o número de filhos é 0. U2 altera o nome da pessoa P para letras maiúsculas. Em seguida, U1 e U2 confirmam suas alterações nessa ordem. É a alteração de U2 que prevalecerá: no banco de dados, o nome passará a estar em maiúsculas e o número de filhos permanecerá zero, mesmo que U1 acredite ter alterado esse valor para 1.
O conceito de versão de pessoa nos ajuda a resolver esse problema. Vamos retomar o mesmo caso de uso:
No momento em que T1 está ativo, um usuário U1 inicia a edição de uma pessoa P. Nesse momento, o número de filhos é 0 e a versão é V1. Ele altera o número de filhos para 1, mas, antes de confirmar sua alteração, um usuário U2 inicia a edição da mesma pessoa P. Como U1 ainda não confirmou sua alteração, U2 vê o número de filhos como 0 e a versão como V1. U2 altera o nome da pessoa P para letras maiúsculas. Em seguida, U1 e U2 validam suas alterações nessa ordem. Antes de validar uma alteração, verifica-se se quem está alterando a pessoa P possui a mesma versão que a pessoa P atualmente registrada. Esse será o caso do usuário U1. Sua alteração é, portanto, aceita e, então, altera-se a versão da pessoa modificada de V1 para V2 para indicar que a pessoa sofreu uma alteração. Ao validar a alteração de U2, perceberemos que U2 possui uma versão V1 da pessoa P, enquanto que, atualmente, a versão desta é V2. Será então possível informar ao usuário U2 que alguém já agiu antes dele e que ele deve partir da nova versão da pessoa P. Ele fará isso, recuperará uma pessoa P da versão V2, que agora tem um filho, colocará o nome em maiúsculas e validará. Sua modificação será aceita se a pessoa P registrada ainda tiver a versão V2. No final, as modificações feitas por U1 e U2 serão consideradas, enquanto que, no caso de uso sem versão, uma das modificações teria sido perdida.
A camada [DAO] do aplicativo cliente pode gerenciar por conta própria a versão da classe [Personne]. Sempre que houver uma modificação em um objeto P, a versão desse objeto será incrementada em 1 na tabela. A anotação @Version permite transferir esse gerenciamento para a camada JPA. O campo em questão não precisa necessariamente se chamar version, como no exemplo. Ele pode ter qualquer nome.
Os campos correspondentes às anotações @Id e @Version estão presentes devido à persistência. Eles não seriam necessários se a classe [Personne] não precisasse ser persistida. Vemos, portanto, que um objeto não tem a mesma representação dependendo de precisar ou não ser persistido.
- linha 17: novamente a anotação @Column para fornecer informações sobre a coluna da tabela [personne] associada ao campo nom da classe Personne. Encontramos aqui dois novos argumentos:
- unique=true indica que o nome de uma pessoa deve ser único. Isso se traduzirá no banco de dados pela adição de uma restrição de exclusividade na coluna NOM da tabela [personne].
- length=30 define em 30 o número de caracteres da coluna NOM. Isso significa que o tipo dessa coluna será VARCHAR(30).
- linha 24: a anotação @Temporal serve para indicar qual tipo SQL deve ser atribuído a uma coluna/campo do tipo data/hora. O tipo TemporalType.DATE designa apenas uma data, sem hora associada. Os outros tipos possíveis são TemporalType.TIME para codificar uma hora e TemporalType.TIMESTAMP para codificar uma data com hora.
Vamos agora comentar o restante do código da classe [Personne]:
- linha 6: a classe implementa a interface Serializable. A sérialisation de um objeto consiste em transformá-lo em uma sequência de bits. A désérialisation é a operação inversa. A serialização/desserialização é utilizada, principalmente, em aplicações cliente/servidor, nas quais os objetos são trocados pela rede. As aplicações cliente ou servidor não têm conhecimento dessa operação, que é realizada de forma transparente pelos JVM. Para que isso seja possível, é necessário, no entanto, que as classes dos objetos trocados sejam “marcadas” com a palavra-chave Serializable.
- linha 37: um construtor da classe. Observe-se que os campos id e version não fazem parte dos parâmetros. De fato, esses dois campos são gerenciados pela camada JPA e não pela aplicação.
- linhas 51 e seguintes: os métodos get e set de cada um dos campos da classe. Vale ressaltar que as anotações JPA podem ser colocadas nos métodos get dos campos, em vez de nos próprios campos. A localização das anotações indica o modo que JPA deve utilizar para acessar os campos:
- se as anotações forem colocadas no nível do campo, o JPA acessará diretamente os campos para lê-los ou gravá-los
- se as anotações forem colocadas no nível do get, o JPA acessará os campos por meio dos métodos get/set para lê-los ou gravá-los
É a posição da anotação @Id que determina a posição das anotações JPA em uma classe. Quando colocada no nível do campo, ela indica acesso direto aos campos; quando colocada no nível get, indica acesso aos campos por meio dos métodos get e set. As demais anotações devem, então, ser colocadas da mesma forma que a anotação @Id.
4.2.2. Configuração da camada JPA
Os testes da camada JPA podem ser realizados com a seguinte arquitetura:
![]() |
- em [7]: o banco de dados que será gerado a partir das anotações da entidade [Personne], bem como de configurações complementares feitas em um arquivo chamado [persistence.xml]
- em [5, 6]: uma camada JPA implementada pelo Hibernate
- em [4]: a entidade [Personne]
- em [3]: um programa de teste do tipo console
A configuração da camada JPA é feita pelo arquivo [META-INF/persistence.xml]:
![]() |
Durante a execução, o arquivo [META-INF/persistence.xml] é procurado no Classpath do aplicativo.
Vamos examinar a configuração da camada JPA definida no arquivo [persistence.xml] do nosso projeto:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
<!-- provedor -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<!-- Classes persistentes -->
<property name="hibernate.archive.autodetection" value="class, hbm" />
<!-- registros SQL
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
-->
<!-- conexão JDBC -->
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa" />
<property name="hibernate.connection.username" value="jpa" />
<property name="hibernate.connection.password" value="jpa" />
<!-- criação automática do esquema -->
<property name="hibernate.hbm2ddl.auto" value="create" />
<!-- Dialeto -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<!-- propriedades DataSource c3p0 -->
<property name="hibernate.c3p0.min_size" value="5" />
<property name="hibernate.c3p0.max_size" value="20" />
<property name="hibernate.c3p0.timeout" value="300" />
<property name="hibernate.c3p0.max_statements" value="50" />
<property name="hibernate.c3p0.idle_test_period" value="3000" />
</properties>
</persistence-unit>
</persistence>
Para entender essa configuração, precisamos revisar a arquitetura de acesso aos dados do nosso aplicativo:
![]() |
- o arquivo [persistence.xml] configurará as camadas [4, 5, 6]
- [4]: implementação do Hibernate para JPA
- [5]: o Hibernate acessa o banco de dados por meio de um pool de conexões. Um pool de conexões é uma reserva de conexões abertas com o SGBD. Um SGBD é acessado por vários usuários, embora, por motivos de desempenho, não possa exceder um número limite N de conexões abertas simultaneamente. Um código bem escrito abre uma conexão com o SGBD pelo menor tempo possível: ele emite comandos SQL e fecha a conexão. Ele fará isso repetidamente, sempre que precisar trabalhar com o banco de dados. O custo de abrir e fechar uma conexão não é insignificante, e é aí que entra o pool de conexões. Ao iniciar a aplicação, o pool abrirá N1 conexões com o SGBD. É a ele que o aplicativo solicitará uma conexão aberta quando precisar. Essa conexão será devolvida ao pool assim que o aplicativo não precisar mais dela, de preferência o mais rápido possível. A conexão não é fechada e permanece disponível para o próximo usuário. Um pool de conexões é, portanto, um sistema de compartilhamento de conexões abertas.
- [6]: o driver JDBC do SGBD utilizado
Agora, vamos ver como o arquivo [persistence.xml] configura as camadas [4, 5, 6] acima:
- linha 2: a tag raiz do arquivo XML é <persistence>.
- linha 3: <persistence-unit> serve para definir uma unidade de persistência. Pode haver várias unidades de persistência. Cada uma delas possui um nome (atributo name) e um tipo de transação (atributo transaction-type). O aplicativo terá acesso à unidade de persistência por meio do nome dela, neste caso, jpa. O tipo de transação RESOURCE_LOCAL indica que a própria aplicação gerencia as transações com o SGBD. Esse será o caso aqui. Quando a aplicação é executada em um contêiner EJB3, ela pode utilizar o serviço de transações desse contêiner. Nesse caso, definiremos transaction-type=JTA (Transação Java API). JTA é o valor padrão quando o atributo transaction-type está ausente.
- linha 5: a tag <provider> serve para definir uma classe que implementa a interface [javax.persistence.spi.PersistenceProvider], interface que permite que o aplicativo inicialize a camada de persistência. Como estamos utilizando uma implementação JPA / Hibernate, a classe utilizada aqui é uma classe do Hibernate.
- linha 6: a tag <properties> define propriedades específicas do provider escolhido. Assim, dependendo da escolha entre Hibernate, Toplink, Kodo, etc., teremos propriedades diferentes. As que se seguem são específicas do Hibernate.
- linha 8: solicita ao Hibernate que explore o classpath do projeto para localizar as classes com a anotação @Entity, a fim de gerenciá-las. As classes @Entity também podem ser declaradas por meio das tags <class>nom_de_la_classe</class>, diretamente abaixo da tag <persistence-unit>. É isso que faremos com o provider JPA / Toplink.
- As linhas 10 a 12, aqui colocadas como comentários, configuram os logs de console do Hibernate:
- linha 10: para definir se as ordens emitidas pelo Hibernate serão exibidas ou não no SQL. Isso é muito útil durante a fase de aprendizado. Devido à ponte relacional/objeto, a aplicação trabalha com objetos persistentes aos quais aplica operações do tipo [persist, merge, remove]. É muito interessante saber quais são os comandos SQL realmente emitidos nessas operações. Ao analisá-los, aos poucos passamos a adivinhar os comandos SQL que o Hibernate irá gerar quando realizamos determinada operação nos objetos persistentes, e a ponte relacional/objeto começa a ganhar forma em nossa mente.
- linha 11: as instruções SQL exibidas no console podem ser formatadas de maneira mais agradável para facilitar a leitura
- linha 12: os comandos SQL exibidos serão, além disso, comentados
- as linhas 15-19 definem a camada JDBC (camada [6] na arquitetura):
- linha 15: a classe do driver JDBC do SGBD, neste caso MySQL5
- linha 16: a URL do banco de dados utilizado
- linhas 17 e 18: o usuário da conexão e sua senha
- linha 22: o Hibernate precisa saber qual é o SGBD com o qual está trabalhando. De fato, todos os SGBD possuem extensões SQL proprietárias, uma forma específica de gerenciar a geração automática dos valores de uma chave primária, ... o que faz com que o Hibernate precise conhecer o SGBD com o qual está trabalhando, a fim de enviar a ele os comandos SQL que este compreenderá. [MySQL5InnoDBDialect] designa o SGBD MySQL5 com tabelas do tipo InnoDB que suportam transações.
- as linhas 24-28 configuram o pool de conexões c3p0 (camada [5] na arquitetura):
- linhas 24 e 25: o número mínimo (padrão 3) e máximo de conexões (padrão 15) no pool. O número inicial de conexões por padrão é 3.
- linha 26: tempo máximo, em milissegundos, de espera por uma solicitação de conexão do cliente. Após esse prazo, o c3p0 retornará uma exceção.
- linha 27: para acessar a BD, o Hibernate utiliza ordens SQL preparadas (PreparedStatement) que o c3p0 pode armazenar em cache. Isso significa que, se a aplicação solicitar pela segunda vez uma ordem SQL preparada que já esteja no cache, ela não precisará ser preparada novamente (a preparação de uma ordem SQL tem um custo) e a que está no cache será utilizada. Aqui, indica-se o número máximo de ordens SQL preparadas que o cache pode conter, considerando todas as conexões (uma ordem SQL preparada pertence a uma conexão).
- linha 28: frequência de verificação, em milissegundos, da validade das conexões. Uma conexão do pool pode se tornar inválida por diversos motivos (o driver JDBC invalida a conexão por ela estar demorando demais, o driver JDBC apresenta “bugs”, etc.).
- linha 20: aqui, solicita-se que, na inicialização da unidade de persistência, seja gerado o esquema do banco de dados dos objetos @Entity. O Hibernate agora dispõe de todas as ferramentas para emitir os comandos SQL de geração das tabelas do banco de dados:
- a configuração dos objetos @Entity permite que ele identifique as tabelas a serem geradas
- as linhas 15-18 e 24-28 permitem que ele obtenha uma conexão com o SGBD
- a linha 22 permite que ele saiba qual dialeto SQL usar para gerar as tabelas
Assim, o arquivo [persistence.xml] utilizado aqui recria um banco de dados novo a cada nova execução do aplicativo. As tabelas são recriadas (create table) após terem sido excluídas (drop table), caso já existissem. Observe que isso obviamente não deve ser feito em um banco de dados em produção...
4.2.3. Exemplo 2: relação um-para-muitos
4.2.3.1. O esquema “ ” do banco de dados
1 ![]() | 2 |
- em [1], o banco de dados, e em [2], sua DDL (MySQL5)
Um artigo A(id, versão, nome) pertence exatamente a uma categoria C(id, versão, nome). Uma categoria C pode conter 0, 1 ou vários artigos. Temos uma relação um-para-muitos (Categoria -> Artigo) e a relação inversa muitos-para-um (Artigo -> Categoria). Essa relação é representada pela chave estrangeira que a tabela [article] possui na tabela [categorie] (linhas 24-28 da DDL).
4.2.3.2. Os objetos @Entity que representam o banco de dados
Um artigo é representado pela seguinte @Entity [Article]:
package entites;
...
@Entity
@Table(name="jpa05_hb_article")
public class Article implements Serializable {
// campos
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@SuppressWarnings("unused")
@Version
private int version;
@Column(length = 30)
private String nom;
// relação principal Artigo (muitos) -> Categoria (um)
// implementada por uma chave estrangeira (categorie_id) na tabela Artigo
// 1 Artigo tem necessariamente 1 Categoria (nullable=false)
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "categorie_id", nullable = false)
private Categorie categorie;
// construtores
public Article() {
}
// getters e setters
...
// toString
public String toString() {
return String.format("Article[%d,%d,%s,%d]", id, version, nom, categorie.getId());
}
}
- linhas 9-11: chave primária da @Entity
- linhas 13-15: seu número de versão
- linhas 17-18: nome do artigo
- linhas 20-25: relação muitos-para-um que vincula a @Entity Article à @Entity Categorie:
- linha 23: a anotação ManyToOne. O “Many” refere-se à @Entity Article, na qual estamos, e o “One” à @Entity Categorie (linha 25). Uma categoria (One) pode ter vários itens (Many).
- linha 24: a anotação ManyToOne define a coluna de chave estrangeira na tabela [article]. Ela se chamará (name) categorie_id e cada linha deverá ter um valor nessa coluna (nullable=false).
- linha 25: a categoria à qual o artigo pertence. Quando um artigo for inserido no contexto de persistência, solicita-se que sua categoria não seja inserida imediatamente (fetch=FetchType.LAZY, linha 23). Não se sabe se essa solicitação faz sentido. Veremos.
Uma categoria é representada pela seguinte @Entity [Categorie]:
package entites;
...
@Entity
@Table(name="jpa05_hb_categorie")
public class Categorie implements Serializable {
// campos
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@SuppressWarnings("unused")
@Version
private int version;
@Column(length = 30)
private String nom;
// relação inversa Categoria (um) -> Artigo (muitos) da relação Artigo (muitos) -> Categoria (um)
// cascata de inserção de Categoria -> inserção de Artigos
// cascata de atualização de Categoria -> atualização de Artigos
// eliminação em cascata de Categoria -> eliminação de Artigos
@OneToMany(mappedBy = "categorie", cascade = { CascadeType.ALL })
private Set<Article> articles = new HashSet<Article>();
// construtores
public Categorie() {
}
// getters e setters
...
// toString
public String toString() {
return String.format("Categorie[%d,%d,%s]", id, version, nom);
}
// associação bidirecional Categoria <--> Artigo
public void addArticle(Article article) {
// o artigo é adicionado à coleção de artigos da categoria
articles.add(article);
// o artigo muda de categoria
article.setCategorie(this);
}
}
- linhas 8-11: a chave primária da @Entity
- linhas 12-14: sua versão
- linhas 16-17: o nome da categoria
- linhas 19-24: o conjunto (set) de artigos da categoria
- linha 23: a anotação @OneToMany indica uma relação um-para-muitos. O “One” refere-se à @Entity [Categorie] na qual estamos, e o “Many” ao tipo [Article] da linha 24: uma (One) categoria possui vários (Many) artigos.
- linha 23: a anotação é o inverso (mappedBy) da anotação ManyToOne aplicada ao campo categorie da @Entity Article: mappedBy=categoria. A relação ManyToOne definida no campo categorie da @Entity Article é a relação principal. Ela é indispensável. Ela representa a relação de chave estrangeira que vincula a @Entity Article à @Entity Categorie. A relação OneToMany definida no campo articles da @Entity Categorie é a relação inversa. Ela não é indispensável. Trata-se de uma facilidade para obter os artigos de uma categoria. Sem essa facilidade, esses artigos seriam obtidos por meio de uma consulta JPQL.
- linha 23: cascadeType.ALL determina que as operações (persist, merge, remove) realizadas em uma @Entity Categorie sejam propagadas para seus artigos.
- linha 24: os artigos de uma categoria serão colocados em um objeto do tipo Set<Article>. O tipo Set não aceita duplicatas. Portanto, não é possível inserir duas vezes o mesmo artigo no objeto Set<Article>. O que significa “o mesmo artigo”? Para indicar que o artigo a é o mesmo que o artigo b, o Java utiliza a expressão a.equals(b). Na classe Object, classe-pai de todas as classes, a.equals(b) é verdadeira se a==b, c.a.d. se os objetos a e b tiverem o mesmo endereço de memória. Pode-se querer dizer que os itens a e b são os mesmos se tiverem o mesmo nome. Nesse caso, o desenvolvedor deve redefinir dois métodos na classe [Article]:
- equals: deve retornar verdadeiro se os dois itens tiverem o mesmo nome
- hashCode: deve retornar um valor inteiro idêntico para dois objetos [Article] que o método equals considere iguais. Aqui, o valor será, portanto, construído a partir do nome do artigo. O valor retornado por hashCode pode ser qualquer número inteiro. Ele é utilizado em diversos contêineres de objetos, notadamente nos dicionários (Hashtable).
A relação OneToMany pode utilizar outros tipos além do Set para armazenar o Many, como objetos do tipo List, por exemplo. Não abordaremos esses casos neste documento. O leitor poderá encontrá-los em [ref1].
- linha 38: o método [addArticle] permite adicionar um artigo a uma categoria. O método se encarrega de atualizar ambas as extremidades da relação OneToMany que liga [Categorie] a [Article].
4.3. O API da camada JPA
Vamos explicar o ambiente de execução de um cliente JPA:
![]() |
Sabemos que a camada JPA [2] cria uma ponte entre o objeto [3] e o banco de dados relacional [4]. Denomina-se “contexto de persistência” o conjunto de objetos gerenciados pela camada JPA no âmbito dessa ponte objeto/relacional. Para acessar os dados do contexto de persistência, um cliente JPA [1] deve passar pela camada JPA [2]:
- ele pode criar um objeto e solicitar à camada JPA que o torne persistente. O objeto passa então a fazer parte do contexto de persistência.
- ele pode solicitar à camada [JPA] uma referência a um objeto persistente existente.
- ele pode modificar um objeto persistente obtido da camada JPA.
- ele pode solicitar à camada JPA que remova um objeto do contexto de persistência.
A camada JPA apresenta ao cliente uma interface chamada [EntityManager] que, como o próprio nome indica, permite gerenciar os objetos @Entity do contexto de persistência. Apresentamos a seguir os principais métodos dessa interface:
insere entity no contexto de persistência | |
remove entity do contexto de persistência | |
mescla um objeto entity do cliente, não gerenciado pelo contexto de persistência, com o objeto entity do contexto de persistência que possui a mesma chave primária. O resultado retornado é o objeto entity do contexto de persistência. | |
insere no contexto de persistência um objeto buscado no banco de dados por meio de sua chave primária. O tipo T do objeto permite que a camada JPA saber qual tabela consultar. O objeto persistente assim criado é retornado ao cliente. | |
cria um objeto Query a partir de uma consulta JPQL (Java Persistence Query Language). Uma consulta JPQL é análoga a uma consulta SQL se , exceto pelo fato de que consulta objetos em vez de tabelas. | |
método semelhante ao anterior, com a diferença de que queryText é um ordem SQL e não JPQL. | |
método idêntico ao de createQuery, com a diferença de que a ordem JPQL queryText foi foi externalizado para um arquivo de configuração e associado a um nome. É esse nome que constitui o parâmetro do método. |
Um objeto EntityManager possui um ciclo de vida que não é necessariamente o mesmo da aplicação. Ele tem um início e um fim. Assim, um cliente JPA pode trabalhar sucessivamente com diferentes objetos EntityManager. O contexto de persistência associado a um EntityManager tem o mesmo ciclo de vida que ele. Eles são indissociáveis um do outro. Quando um objeto EntityManager é fechado, seu contexto de persistência é, se necessário, sincronizado com o banco de dados e, em seguida, deixa de existir. É necessário criar um novo EntityManager para dispor novamente de um contexto de persistência.
O cliente JPA pode criar um EntityManager e, portanto, um contexto de persistência com a seguinte instrução:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("nom d'une unité de persistance");
- javax.persistence.Persistence é uma classe estática que permite obter uma fábrica (factory) de objetos EntityManager. Essa fábrica está vinculada a uma unidade de persistência específica. Vale lembrar que o arquivo de configuração [META-INF/persistence.xml] permite definir unidades de persistência e que estas possuem um nome:
<persistence-unit name="elections-dao-jpa-mysql-01PU" transaction-type="RESOURCE_LOCAL">
No exemplo acima, a unidade de persistência se chama elections-dao-jpa-mysql-01PU. Junto com ela, vem toda uma configuração específica, incluindo o SGBD com o qual ela opera. A instrução [Persistence.createEntityManagerFactory("elections-dao-jpa-mysql-01PU")] cria uma fábrica de objetos do tipo EntityManagerFactory capaz de fornecer objetos EntityManager destinados a gerenciar contextos de persistência vinculados à unidade de persistência denominada elections-dao-jpa-mysql-01PU. A obtenção de um objeto EntityManager e, portanto, de um contexto de persistência, é feita a partir do objeto EntityManagerFactory da seguinte maneira:
Os seguintes métodos da interface [EntityManager] permitem gerenciar o ciclo de vida do contexto de persistência:
o contexto de persistência é fechado. Força a sincronização do contexto de persistência com o banco de dados:
| |
o contexto de persistência é esvaziado de todos os seus objetos, mas não é fechado. | |
o contexto de persistência é sincronizado com o banco de dados da maneira descrita para close() |
O cliente JPA pode forçar a sincronização do contexto de persistência com o banco de dados usando o método [EntityManager].flush anterior. A sincronização pode ser explícita ou implícita. No primeiro caso, cabe ao cliente executar as operações flush quando desejar realizar sincronizações; caso contrário, elas ocorrem em determinados momentos que especificaremos a seguir. O modo de sincronização é gerenciado pelos seguintes métodos da interface [EntityManager]:
Existem dois valores possíveis para flushmode: FlushModeType.AUTO (padrão): a sincronização ocorre antes cada consulta SELECT feita no banco de dados. FlushModeType.COMMIT: a sincronização ocorre somente ao ao final das transações no banco de dados. | |
indica o modo atual de sincronização |
Vamos resumir. No modo FlushModeType.AUTO, que é o modo padrão, o contexto de persistência será sincronizado com o banco de dados nos seguintes momentos:
- antes de cada operação SELECT no banco de dados
- ao final de uma transação no banco de dados
- após uma operação flush ou close no contexto de persistência
No modo FlushModeType.COMMIT, ocorre o mesmo, exceto pela operação 1, que não é executada. O modo normal de interação com a camada JPA é um modo transacional. O cliente realiza diversas operações no contexto de persistência, dentro de uma transação. Nesse caso, os momentos de sincronização do contexto de persistência com o banco de dados correspondem aos casos 1 e 2 acima no modo AUTO, e apenas ao caso 2 no modo COMMIT.
Concluímos com o API da interface Query, que permite emitir ordens JPQL no contexto de persistência ou ordens SQL diretamente no banco de dados para recuperar dados. A interface Query é a seguinte:
![]() |
- 1 - o método getResultList executa um SELECT que retorna vários objetos. Esses objetos serão obtidos em um objeto List. Esse objeto é uma interface. Ela oferece um objeto Iterator que permite percorrer os elementos da lista L da seguinte forma:
Iterator iterator = L.iterator();
while (iterator.hasNext()) {
// chamar o objeto iterator.next() que representa o elemento atual da lista
...
}
A lista L também pode ser explorada com um for:
for (Object o : L) {
// utilizar o objeto o
}
- 2 - o método getSingleResult executa uma ordem JPQL / SQL SELECT que retorna um único objeto.
- 3 - O método executeUpdate executa uma ordem SQL de atualização ou exclusão e retorna o número de linhas afetadas pela operação.
- 4 - O método setParameter(String, Object) permite atribuir um valor a um parâmetro nomeado de uma ordem JPQL configurada
- 5 - o método setParameter(int, Object), porém o parâmetro não é designado pelo nome, mas pela posição na ordem JPQL.
4.4. As consultas JPQL
JPQL (Java Persistence Query Language) é a linguagem de consultas da camada JPA. A linguagem JPQL é semelhante à linguagem SQL dos bancos de dados. Enquanto o SQL trabalha com tabelas, o JPQL trabalha com os objetos de imagem dessas tabelas. Vamos analisar um exemplo dentro da seguinte arquitetura:
![]() |
O banco de dados, que chamaremos de [dbrdvmedecins2] , é um banco de dados MySQL5 com quatro tabelas:
![]() |
Ela reúne informações que permitem gerenciar as consultas de um grupo de médicos.
4.4.1. A tabela [MEDECINS]
Ela contém informações sobre os médicos.
![]() | ![]() |
- ID: número que identifica o médico — chave primária da tabela
- VERSION: número que identifica a versão da linha na tabela. Esse número é incrementado em 1 sempre que uma alteração é feita na linha.
- NOM: o sobrenome do médico
- PRENOM: seu nome
- TITRE: seu título (Srta., Sra., Sr.)
4.4.2. A tabela [CLIENTS]
Os clientes dos diferentes médicos estão registrados na tabela [CLIENTS]:
![]() | ![]() |
- ID: número de identificação do cliente — chave primária da tabela
- VERSION: número que identifica a versão da linha na tabela. Esse número é incrementado em 1 sempre que uma modificação é feita na linha.
- NOM: o nome do cliente
- PRENOM: seu nome
- TITRE: seu título (Srta., Sra., Sr.)
4.4.3. A tabela [CRENEAUX]
Ela lista os horários em que os RV são possíveis:
![]() |
![]() |
- ID: número que identifica o intervalo horário — chave primária da tabela (linha 8)
- VERSION: número que identifica a versão da linha na tabela. Esse número é incrementado em 1 sempre que uma alteração é feita na linha.
- ID_MEDECIN: número que identifica o médico ao qual esse horário pertence – chave estrangeira na coluna MEDECINS (ID).
- HDEBUT: hora de início do horário
- MDEBUT: minutos de início do horário
- HFIN: hora de término do horário
- MFIN: minutos de término do intervalo
A segunda linha da tabela [CRENEAUX] (ver [1] acima) indica, por exemplo, que o horário nº 2 começa às 8h20 e termina às 8h40 e pertence à médica nº 1 (Sra. Marie PELISSIER).
4.4.4. A tabela [RV]
Ela lista os RV atribuídos a cada médico:
![]() |
- ID: número que identifica o RV de forma exclusiva – chave primária
- JOUR: dia do RV
- ID_CRENEAU: horário do RV – chave estrangeira no campo [ID] da tabela [CRENEAUX] – define tanto o horário quanto o médico em questão.
- ID_CLIENT: número do cliente para o qual a reserva foi feita – chave estrangeira no campo [ID] da tabela [CLIENTS]
Esta tabela possui uma restrição de unicidade na sobre os valores das colunas associadas (JOUR, ID_CRENEAU):
Se uma linha da tabela [RV] tiver o valor (JOUR1, ID_CRENEAU1) para as colunas (JOUR, ID_CRENEAU), esse valor não pode aparecer em nenhum outro lugar. Caso contrário, isso significaria que dois RV foram registrados ao mesmo tempo para o mesmo médico. Do ponto de vista da programação em Java, o driver JDBC do banco de dados aciona um SQLException quando esse caso ocorre.
A linha de id igual a 3 (ver [1] acima) significa que um RV foi agendado para o horário nº 20 e o cliente nº 4 em 23/08/2006. A tabela [CRENEAUX] nos informa que o horário n.º 20 corresponde ao intervalo das 16h20 às 16h40 e pertence à médica n.º 1 (Sra. Marie PELISSIER). A tabela [CLIENTS] nos informa que o cliente nº 4 é a Srta. Brigitte BISTROU.
4.4.5. Geração do banco de dados
Para criar as tabelas e preenchê-las, pode-se utilizar o script [dbrdvmedecins2.sql]. Com o [WampServer], pode-se proceder da seguinte forma:
![]() |
- em [1], clique no ícone de [WampServer] e selecione a opção [PhpMyAdmin] [2],
- em [3], na janela que se abriu, selecione o link [Bases de données],
![]() |
- em [2], crie um banco de dados com o nome [4] e a codificação [5],
- em [7], o banco de dados foi criado. Clique no link correspondente,
![]() |
- em [8], importamos um arquivo SQL,
- que é selecionado no sistema de arquivos com o botão [9],
![]() |
- em [11], seleciona-se o script SQL e, em [12], ele é executado,
- em [13], as quatro tabelas do banco de dados foram criadas. Seguimos um dos links,
![]() |
- em [14], o conteúdo da tabela.
A partir daí, não voltaremos mais a abordar esse banco de dados. Mas o leitor está convidado a acompanhar sua evolução ao longo dos programas, principalmente quando algo não funcionar.
4.4.6. A camada [JPA]
Voltemos à arquitetura do exemplo:
![]() |
Agora, vamos construir o projeto Maven da camada [JPA].
4.4.7. O projeto NetBeans
É o seguinte:
![]() |
- em [1], criamos um projeto Maven do tipo [Java Application] [2],
- em [3], nomeia-se o projeto,
![]() |
- em [4], o projeto gerado.
4.4.8. Geração da camada [JPA]
Voltemos à arquitetura que precisamos construir:
![]() |
Com o NetBeans, é possível gerar automaticamente a camada [JPA]. É interessante conhecer esses métodos de geração automática, pois o código gerado fornece indicações valiosas sobre como escrever entidades JPA.
4.4.9. Criação de uma conexão do NetBeans com o banco de dados
- execute o SGBD MySQL 5 para que o BD fique disponível,
- criar uma conexão do NetBeans com o banco de dados [dbrdvmedecins2],
![]() |
- na guia [Services] [1], no ramo [Databases] [2], selecione o driver JDBC MySQL [3],
- em seguida, selecione a opção [4] “Connect Using”, que permite criar uma conexão com um banco de dados MySQL,
- em [5], forneça as informações solicitadas. Em [6], o nome do banco de dados; em [7], o usuário do banco de dados e sua senha;
- em [8], é possível testar as informações fornecidas,
- em [9], a mensagem esperada quando as informações estiverem corretas,
![]() |
- em [10], a conexão é estabelecida. Nela, podem-se ver as quatro tabelas do banco de dados conectado.
4.4.10. Criação de uma unidade de persistência
Voltemos à arquitetura em construção:
![]() |
Estamos construindo a camada [JPA]. Sua configuração é feita em um arquivo [persistence.xml], no qual definimos as unidades de persistência. Cada uma delas requer as seguintes informações:
- as características JDBC de acesso ao banco de dados (URL, usuário, senha),
- as classes que servirão de representação das tabelas do banco de dados,
- a implementação JPA utilizada. Na verdade, JPA é uma especificação implementada por diversos produtos. Aqui, utilizaremos o Hibernate.
O NetBeans pode gerar esse arquivo de persistência por meio de um assistente.
![]() |
- clique com o botão direito do mouse no projeto e escolha a criação de uma unidade de persistência [1],
- em [2], criar uma unidade de persistência,
![]() |
- em [3], nomeie a unidade de persistência que está sendo criada,
- em [4], selecionar a implementação JPA do Hibernate (JPA 2.0),
- em [5], indicar que as tabelas de BD já foram criadas e, portanto, não serão criadas novamente. Confirmamos o assistente,
- no [6], o novo projeto,
- no [7], o arquivo [persistence.xml] foi gerado na pasta [META-INF],
- em [8], novas dependências foram adicionadas ao projeto Maven.
O arquivo [META-INF/persistence.xml] gerado é o seguinte:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.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_2_0.xsd">
<persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
</properties>
</persistence-unit>
</persistence>
Ele contém as informações fornecidas no assistente:
- linha 3: o nome da unidade de persistência,
- linha 3: o tipo de transações com o banco de dados. Aqui, RESOURCE_LOCAL indica que o aplicativo irá gerenciar suas próprias transações,
- linhas 6 a 9: as propriedades JDBC da fonte de dados.
Na guia [Design], é possível ter uma visão geral do arquivo [persistence.xml]:
![]() |
Para obter os logs do Hibernate, preenchemos o arquivo [persistence.xml] da seguinte maneira:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.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_2_0.xsd">
<persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
- linha 11: solicitamos a exibição dos comandos SQL emitidos pelo Hibernate,
- linha 12: essa propriedade permite exibi-las em um formato adequado.
Foram adicionadas dependências ao projeto. O arquivo [pom.xml] é o seguinte:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-rdvmedecins-jpql-hibernate</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-rdvmedecins-jpql-hibernate</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.transaction</groupId>
<artifactId>jboss-transaction-api_1.1_spec</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.15.0-GA</version>
</dependency>
<dependency>
<groupId>org.hibernate.common</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>4.0.1.Final</version>
</dependency>
</dependencies>
</project>
Todas as dependências adicionadas dizem respeito ao Hibernate ORM. Adicionaremos a dependência do driver JDBC do MySQL:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
4.4.11. Geração das entidades JPA
As entidades JPA podem ser geradas por um assistente do NetBeans:
![]() |
- no [1], criam-se entidades JPA a partir de um banco de dados,
![]() |
- em [2], seleciona-se a conexão [dbrdvmedecins2] criada anteriormente,
- em [3], seleciona-se todas as tabelas do banco de dados associado,
![]() |
- em [4], atribui-se um nome às classes Java associadas às quatro tabelas,
- bem como um nome de pacote [5],
- em [6], JPA reúne as linhas das tabelas de BD em coleções. Escolhemos a lista como coleção,
![]() |
- em [7], as classes Java criadas pelo assistente.
4.4.12. As entidades JPA geradas
A entidade [Medecin] é a representação da tabela [medecins]. A classe Java está repleta de anotações que tornam o código pouco legível à primeira vista. Se mantivermos apenas o que é essencial para a compreensão da função da entidade, obtemos o seguinte código:
package rdvmedecins.jpa;
...
@Entity
@Table(name = "medecins")
public class Medecin implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;
// construtores
....
// getters e setters
....
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- na linha 4, a anotação @Entity define a classe [Medecin] como uma entidade JPA, c.a.d. uma classe vinculada a uma tabela de BD por meio de API e JPA,
- na linha 5, o nome da tabela BD associada à entidade JPA. Cada campo da tabela corresponde a um campo na classe Java,
- linha 6, a classe implementa a interface Serializable. Isso é necessário em aplicações cliente/servidor, nas quais as entidades são serializadas entre o cliente e o servidor.
- linhas 10-11: o campo id da classe [Medecin] corresponde ao campo [ID] (linha 10) da tabela [medecins],
- linhas 13-14: o campo título da classe [Medecin] corresponde ao campo [TITRE] (linha 13) da tabela [medecins],
- linhas 16-17: o campo “nome” da classe [Medecin] corresponde ao campo [NOM] (linha 16) da tabela [medecins],
- linhas 19-20: o campo “versão” da classe [Medecin] corresponde ao campo [VERSION] (linha 19) da tabela [medecins]. Aqui, o assistente não reconhece que a coluna é, na verdade, uma coluna de versão que deve ser incrementada a cada modificação da linha à qual pertence. Para atribuir essa função a ela, é preciso adicionar a anotação @Version. Faremos isso em uma etapa posterior,
- linhas 22-23: o campo prenom da classe [Medecin] corresponde ao campo [PRENOM] da tabela [medecins],
- linhas 10-11: o campo id corresponde à chave primária [ID] da tabela. As anotações nas linhas 8-9 esclarecem esse ponto,
- linha 8: a anotação @Id indica que o campo anotado está associado à chave primária da tabela,
- linha 9: a camada [JPA] irá gerar a chave primária das linhas que ela inserirá na tabela [Medecins]. Existem várias estratégias possíveis. Aqui, a estratégia GenerationType.IDENTITY indica que a camada JPA utilizará o modo auto_increment da tabela MySQL,
- linhas 25-26: a tabela [creneaux] possui uma chave estrangeira na tabela [medecins]. Um horário pertence a um médico. Por outro lado, um médico tem vários horários associados a ele. Portanto, temos uma relação de um (médico) para vários (horários), uma relação qualificada pela anotação @OneToMany por JPA (linha 25). O campo da linha 26 conterá todos os horários do médico. Isso sem necessidade de programação. Para compreender totalmente a linha 25, precisamos apresentar a classe [Creneau].
Ela é a seguinte:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "MDEBUT")
private int mdebut;
@Column(name = "HFIN")
private int hfin;
@Column(name = "HDEBUT")
private int hdebut;
@Column(name = "MFIN")
private int mfin;
@Column(name = "VERSION")
private int version;
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin idMedecin;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idCreneau")
private List<Rv> rvList;
// construtores
...
// getters e setters
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
Comentaremos apenas as novas anotações:
- já mencionamos que a tabela [creneaux] possui uma chave estrangeira para a tabela [medecins]: um horário está associado a um médico. Vários horários podem estar associados ao mesmo médico. Temos uma relação da tabela [creneaux] para a tabela [medecins], que é qualificada como muitos (horários) para um (médico). É a anotação @ManyToOne da linha 32 que serve para definir a chave estrangeira,
- A linha 31, com a anotação @JoinColumn, especifica a relação de chave estrangeira: a coluna [ID_MEDECIN] da tabela [creneaux] é uma chave estrangeira na coluna [ID] da tabela [medecins],
- linha 33: uma referência ao médico responsável pelo horário. Isso também é obtido sem programação.
A relação de chave estrangeira entre a entidade [Creneau] e a entidade [Medecin] é, portanto, representada por duas anotações:
- na entidade [Creneau]:
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin idMedecin;
- na entidade [Medecin]:
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;
As duas anotações refletem a mesma relação: a da chave estrangeira da tabela [creneaux] para a tabela [medecins]. Diz-se que elas são inversas uma da outra. Apenas a relação @ManyToOne é indispensável. Ela define, sem ambiguidade, a relação de chave estrangeira. A relação @OneToMany é opcional. Se estiver presente, ela se limita a referenciar a relação @ManyToOne à qual está associada. Esse é o significado do atributo mappedBy da linha 1 da entidade [Medecin]. O valor desse atributo é o nome do campo da entidade [Creneau] que possui a anotação @ManyToOne, a qual especifica a chave estrangeira. Ainda nessa mesma linha 1 da entidade [Medecin], o atributo cascade=CascadeType.ALL define o comportamento da entidade [Medecin] em relação à entidade [Creneau]:
- se for inserida uma nova entidade [Medecin] no banco de dados, então as entidades [Creneau] do campo da linha 2 também devem ser inseridas;
- se for modificada uma entidade [Medecin] no banco de dados, então as entidades [Creneau] do campo da linha 2 também devem ser modificadas,
- se for excluída uma entidade [Medecin] do banco de dados, então as entidades [Creneau] do campo da linha 2 também devem ser excluídas.
Apresentamos o código das outras duas entidades sem comentários específicos, uma vez que elas não introduzem novas notações.
A entidade [Client]
package rdvmedecins.jpa;
...
@Entity
@Table(name = "clients")
public class Client implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idClient")
private List<Rv> rvList;
// construtores
...
// getters e setters
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- as linhas 24-25 refletem a relação de chave estrangeira entre a tabela [rv] e a tabela [clients].
A entidade [Rv]:
package rdvmedecins.jpa;
...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "JOUR")
@Temporal(TemporalType.DATE)
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau idCreneau;
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Client idClient;
// construtores
...
// getters e setters
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- a linha 13 define o campo “dia” do tipo Java Date. Indica-se que, na tabela [rv], a coluna [JOUR] (linha 12) é do tipo data (sem hora),
- linhas 16-18: definem a relação de chave estrangeira da tabela [rv] com a tabela [creneaux],
- linhas 20-22: definem a relação de chave estrangeira da tabela [rv] com a tabela [clients].
A geração automática das entidades JPA nos permite obter uma base de trabalho. Às vezes ela é suficiente, outras vezes não. É o caso aqui:
- é preciso adicionar a anotação @Version aos diversos campos de versão das entidades,
- é preciso escrever métodos toString mais explícitos do que os gerados,
- as entidades [Medecin] e [Client] são análogas. Vamos fazê-las derivar de uma classe [Personne],
- vamos remover as relações @OneToMany inversas às relações @ManyToOne. Elas não são indispensáveis e trazem complicações à programação,
- eliminamos a validação @NotNull nas chaves primárias. Quando persistimos uma entidade JPA com MySQL, a entidade inicial possui uma chave primária null. Somente após a persistência no banco de dados é que a chave primária do elemento persistido passa a ter um valor.
Com essas especificações, as diferentes classes passam a ser as seguintes:
A classe Pessoa é usada para representar médicos e clientes:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@MappedSuperclass
public class Personne implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "TITRE")
private String titre;
@Basic(optional = false)
@Column(name = "NOM")
private String nom;
@Basic(optional = false)
@Column(name = "VERSION")
@Version
private int version;
@Basic(optional = false)
@Column(name = "PRENOM")
private String prenom;
// construtores
...
// getters e setters
...
@Override
public String toString() {
return String.format("[%s,%s,%s,%s,%s]", id, version, titre, prenom, nom);
}
}
- linha 6: observe-se que a classe [Personne] não é, por si só, uma entidade (@Entity). Ela será a classe pai das entidades. A anotação @MappedSuperClass indica essa situação.
A entidade [Client] encapsula as linhas da tabela [clients]. Ela deriva da classe anterior [Personne]:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "clients")
public class Client extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
// construtores
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Client[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
}
}
- linha 6: a classe [Client] é uma entidade JPA,
- linha 7: ela está associada à tabela [clients],
- linha 8: ela deriva da classe [Personne].
A entidade [Medecin], que encapsula as linhas da tabela [medecins], segue o mesmo modelo:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "medecins")
public class Medecin extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
// construtores
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Médecin[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
}
}
A entidade [Creneau] encapsula as linhas da tabela [creneaux]:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "MDEBUT")
private int mdebut;
@Basic(optional = false)
@Column(name = "HFIN")
private int hfin;
@Basic(optional = false)
@NotNull
@Column(name = "HDEBUT")
private int hdebut;
@Basic(optional = false)
@Column(name = "MFIN")
private int mfin;
@Basic(optional = false)
@Column(name = "VERSION")
@Version
private int version;
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin medecin;
// construtores
...
// getters e setters
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
// TODO: Aviso — este método não funcionará caso os campos de ID não estejam definidos
...
}
@Override
public String toString() {
return String.format("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut, hfin, mfin, medecin);
}
}
- as linhas 40-42 modelam a relação “muitos para um” que existe entre a tabela [creneaux] e a tabela [medecins] do banco de dados: um médico tem vários horários, e um horário pertence a um único médico.
A entidade [Rv] encapsula as linhas da tabela [rv]:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "JOUR")
@Temporal(TemporalType.DATE)
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau creneau;
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Client client;
// construtores
...
// getters e setters
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Rv[%s, %s, %s]", id, creneau, client);
}
}
- as linhas 27-29 modelam a relação “muitos para um” que existe entre a tabela [rv] e a tabela [clients] (um cliente pode aparecer em vários Rv) do banco de dados, e as linhas 23-25 modelam a relação “muitos para um” que existe entre a tabela [rv] e a tabela [creneaux] (um horário pode aparecer em vários Rv).
4.4.13. O código de acesso aos dados
Vamos agora adicionar ao projeto o código de acesso aos dados por meio da camada JPA:
![]() |
![]() |
A classe [MainJpql] é a seguinte:
package rdvmedecins.console;
import java.util.Scanner;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class MainJpql {
public static void main(String[] args) {
// EntityManagerFactory
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-rdvmedecins-jpql-hibernatePU");
// entityManager
EntityManager em = emf.createEntityManager();
// leitor de teclado
Scanner clavier = new Scanner(System.in);
// ciclo de entrada de consultas JPQL
System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
String requete = clavier.nextLine();
while (!requete.trim().equals("*")) {
try {
// exibição do resultado da consulta
for (Object o : em.createQuery(requete).getResultList()) {
System.out.println(o);
}
} catch (Exception e) {
System.out.println("L'exception suivante s'est produite : " + e);
}
// limpa-se o contexto de persistência
em.clear();
// nova consulta
System.out.println("---------------------------------------------");
System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
requete = clavier.nextLine();
}
// fechamento dos recursos
em.close();
emf.close();
}
}
- linha 12: criação do EntityManagerFactory associado à unidade de persistência que criamos anteriormente. O parâmetro do método createEntityManagerFactory é o nome dessa unidade de persistência:
<persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
...
</persistence-unit>
- linha 14: criação do EntityManager, que gerencia a camada de persistência,
- linha 19: inserção de uma consulta JPQL select,
- linhas 23-28: exibição do resultado da consulta,
- linha 20: a entrada é interrompida quando o usuário digita *.
Pergunta: indique as consultas JPQL que permitem obter as seguintes informações:
- lista dos médicos em ordem decrescente de seus nomes
- lista dos médicos cujo título = 'Sr.'
- lista dos horários disponíveis da Sra. Pelissier
- lista das consultas marcadas em ordem crescente de dias
- lista de clientes (nome) que marcaram RV com a Sra. PELISSIER em 24/08/2006
- número de clientes da Sra. PELISSIER em 24/08/2006
- clientes que não marcaram consulta
- os médicos que não têm consulta marcada
Vamos nos basear no exemplo do parágrafo 2.7 de [ref1]. Aqui está um exemplo de execução:
- linha 2: a consulta JPQL,
- linhas 3 a 11: a consulta SQL correspondente,
- linhas 12 a 15: o resultado da consulta JPQL.
4.5. Relações entre o contexto de persistência e SGBD
4.5.1. A classe Pessoa
4.5.2. O programa de teste
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
4.5.3. A configuração do Hibernate
4.5.4. A configuração do log4j.properties
4.5.5. Os resultados
Pergunta: relacione o código Java com os resultados exibidos.





































