Figura Q1. Estrutura do projeto.
32 Java Magazine•Edição 39
O mapeamento das propriedades nome eendereco é muito simples. Ambas as pro- priedades possuem a anotação @Column, contendo dois atributos:name, que define a coluna correspondente no banco; enullable,
que define se a propriedade em questão pode ser nula:
@Column(name = “DS_NOME”, nullable = false) private String nome;
A propriedadereservas é um pouco mais complexa, pois ela determina um rela- cionamento um-para-muitos. No nosso modelo, um cliente pode estar relacionado a diversas reservas, sendo que cada reserva relaciona-se a um único cliente.
A anotação @OneToMany é usada para indicar o relacionamento entre as enti- dades (um cliente para muitas reservas). As reservas do cliente são armazenadas na propriedade reservas(uma lista de instâncias da entidadeReserva), que é po- pulada automaticamente pelo TopLink. Dois atributos da anotação são utilizados nesse mapeamento:cascade = CascadeType.ALL indica que alterações na entidade Cliente serão refletidas automaticamente nas entidadesReserva relacionadas. O atributo mappedBy = “cliente”indica que na classe Reserva existe uma propriedade denomi- nadacliente, que mapeia o cliente do rela- cionamento (veremos mais sobre a classe Reserva adiante).
@OneToMany(cascade =
CascadeType.ALL, mappedBy = “cliente”) private List<Reserva> reservas;
Mapeamento do veículo
Na modelagem OO da aplicação decidi- mos especializar a classeVeiculo, guardan- do as particularidades de cada veículo em classes específicas (veja aFigura 1).Veiculo é uma classe abstrata, que possui duas subclasses:Esportivo eUtilitario.
No modelo relacional a abordagem foi distinta. Para evitar o custo de joins, to-
dos os veículos foram mapeados em uma única tabela, que contém propriedades tanto genéricas quanto específicas. Como veremos a seguir, o nosso mapeamento deve contemplar essa decisão arquitetural e permitir que os objetos sejam instan- ciados corretamente (de acordo com sua classe), mesmo com todos residindo em uma tabela genérica.
Vamos analisar o código da entidade Veiculo (Listagem 2). Como já conhecemos as anotações@Entity,@Id e@Column, veremos apenas as novas construções.
O mapeamento da herança de entidades é descrito através da anotação@Inheritance, que define a estratégia usada no mape- amento. De forma semelhante a outros Figura 2. Modelo Entidade-Relacionamento para o sistema de locação de veículos.
Listagem 1.Cliente.java
package br.com.jm.locadora.model; import java.util.List;
import javax.persistence.*; @Entity
public class Cliente { @Id
@Column(nam e = “CD_CLIENTE”)
@GeneratedValue(strategy = GenerationType.IDENTITY) private Integer codigo;
@Column(nam e = “DS_NOME”, nullable = false) private String nome;
@Column(nam e = “DS_ENDERECO” , nullable = false) private String endereco;
@OneToMany( cascade = CascadeType .ALL, mappedBy = “cliente”) private List<Reserva> reservas;
// ... gets e sets omitidos }
Figura 1. Diagrama de classes do sistema de locação de veículos.
Veiculo
Reserva Cliente
Esportivo Utilitario Locacao
{ From model }
{ From locadora }
Attributes
Attributes Attributes private String placa
private String modelo private Integer ano private Double diaria private String cor
private String codigo private Date inicio private Date fim
private String codigo private String nome private String endereco
private Integer velocidade private Integer passageiros private Integer codigo
{ From model }
{ From model } { From model } { From model } Attributes Attributes Attributes
veiculo cliente reserva
Edição 39• Java Magazine 33
frameworks O/R, a JPA oferece três tipos de mapeamento de herança. Estes são descritos na enumeração InheritanceType: SINGLE_TABLE,TABLE_PER_CLASS e JOINED.
A estratégiaSINGLE_TABLE é a mais comum, sendo a opção default da JPA. Nessa es- tratégia todas as entidades da hierarquia são persistidas em uma única tabela, que deve conter todos os campos necessários. Na estratégiaTABLE_PER_CLASS cada entidade é mapeada em uma tabela específica, en- quanto na estratégia JOINED uma associação de tabelas é usada para persistência de cada entidade.
A estratégia de mapeamento deve ser especificada na entidade raiz da hierar- quia, através do atributostrategy. Seguindo o nosso modelo, a hierarquia de veículos seguirá a estratégiaSINGLE_TABLE:
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
O mesmo mapeamento pode ser de- finido de forma simplificada, visto que SINGLE_TABLE é a estratégia de mapeamento default da JPA:
@Inheritance
Como todas as entidades da hierarquia são armazenadas em uma única tabela, é necessário determinar a correspondência entre registros e entidades. A anotação @DiscriminatorColumn define a regra utilizada pela JPA para “reconhecer” cada registro e instanciar a entidade correspondente. O atributoname especifica a coluna que armazena o descritor da entidade, enquan- to o atributodiscriminatorType define o tipo dessa coluna:
@DiscriminatorColumn(name = “CD_TIPO”, discriminatorType = DiscriminatorType.STRING )
Durante a persistência, a JPA lê o valor armazenado na colunaCD_TIPO (uma string) e escolhe a entidade com a qual irá traba- lhar. Essa escolha é feita a partir de uma outra anotação, especificada nas entidades descendentes:@DiscriminatorValue.
NasListagens 3 e4 são exibidos os có- digos das entidades Esportivoe Utilitario. A anotação@DiscriminatorValue(“E”), contida na entidadeEsportivo, especifica que sempre
que o valor “E” for encontrado no campo CD_TIPO da tabela VEICULO, uma entidade Esportivo será criada. Quando o campo CD_TIPO contiver o texto “U” uma entidade Utilitario será criada, como especificado
pela anotação @DiscriminatorValue(“U”) da etintidade Utilitario.
É importante notar que, como as entida- desEsportivo eUtilitario derivam deVeiculo, só é necessário mapear as novas propriedades
Listagem 2.Veiculo.java package br.com.jm.locadora.model; import java.util.List; import javax.persistence.*; @Entity @Table(name = “VEICULO”) @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = “CD_TIPO”, discriminatorType = DiscriminatorType.STRING) public abstract class Veiculo {
@Id
@Column(name = “DS_PLACA”, nullable = false) private String placa;
@Column(name = “DS_MODELO”, nullable = false) private String modelo;
@Column(name = “NR_ANO”, nullable = false) private Integer ano;
@Column(name = “DS_COR”, nullable = false) private String cor;
@Column(name = “VL_DIARIA”, nullable = false) private Double diaria;
@OneToMany(c ascade = CascadeType. ALL, mappedBy = “veiculo”) private List<Reserva> reservas;
@Column(name = “CD_TIPO”, nullable = false) public abstract String getTipo();
// ... gets e sets omitidos } Listagem 3.Esportivo.java package br.com.jm.locadora.model; import javax.persistence.*; @Entity @DiscriminatorValue(“E”)
public class Esportivo extends Veiculo {
@Column(name = “VL_VELOCIDADE”, nullable = false) private Integer velocidade;
// ... gets e sets omitidos } Listagem 4.Utilitario.java package br.com.jm.locadora.model; import javax.persistence.*; @Entity @DiscriminatorValue(“U”)
public class Utilitario extends Veiculo {
@Column(name = “NR_PASSAGEIROS”, nullable = false) private Integer passageiros;
// ... gets e sets omitidos }
34 Java Magazine•Edição 39 Listagem 5.Reserva.java package br.com.jm.locadora.model; import java.util.Date; import javax.persistence.*; @Entity @NamedQueries({
@NamedQuery( name = “Reserva.lis tarPorPeriodo ”,
query = “SELECT r FROM Reserva r WHERE “ +
“r.inicio >= :inicio AND r.fim <= :fim”), @NamedQuery(name = “Reserva.ultima”,
query = “SELECT r FROM Reserva r WHERE r.codigo = “ + “(SELECT MAX(r1.codigo ) FROM Reserva r1)”) })
public class Reserva { @Id
@Column(name = “CD_RESERVA”)
@GeneratedVa lue(strategy = GenerationTy pe.IDENTITY) private Integer codigo;
@ManyToOne
@JoinColumn( name = “CD_CLIENTE” , nullable = false) private Cliente cliente;
@ManyToOne
@JoinColumn( name = “DS_PLACA”, nullable = false) private Veiculo veiculo;
@Column(name = “DT_INICIO”, nullable = false) @Temporal(TemporalType.DATE)
private Date inicio;
@Column(name = “DT_FIM”, nullable = false) @Temporal(TemporalType.DATE)
private Date fim;
// ... gets e sets omitidos } Listagem 6.Locacao.java package br.com.jm.locadora.model; import java.util.Date; import javax.persistence.*; @Entity
public class Locacao { @Id
@Column(name = “CD_LOCACAO”)
@GeneratedVa lue(strategy = GenerationTy pe.IDENTITY) private Integer codigo;
@OneToOne
@JoinColumn( name = “CD_RESERVA” ) private Reserva reserva;
// ... gets e sets omitidos }
Mapeamento da reserva
Já apre se nt am os a entidade Reserva (Listagem 5), que descreve a reserva de um veículo, relacionando-se com as entidades Veiculoe Cliente. Reserva representa o lado “filho” dos relacionamentosveiculo-reserva
ecliente-reserva, e armazena o cliente e o ve-
ículo nas propriedadescliente eveiculo.Para
especificadas nessas entidades (velocidade epassageiros). Além disso, é possível espe- cificar a obrigatoriedade dos campos das entidades descendentes (nullable = false) mesmo com todas compartilhando uma só tabela. A obrigatoriedade só será avaliada para registros correspondentes à entidade em questão.
Edição 39• Java Magazine 35
mapear o relacionamento entre as entida- des são usadas anotações@ManyToOne, que definem que “muitas reservas estão rela- cionadas a um cliente” e “muitas reservas estão relacionadas a um veículo”. A ano- tação@JoinColumn é usada para especificar a coluna que contém a chave estrangeira de cada relação. O relacionamento com a entidadeCliente é exibido a seguir:
@ManyToOne
@JoinColumn(name = “CD_CLIENTE”, nullable = false) private Cliente cliente;
Vale lembrar que as propriedadescliente eveiculo são referenciadas nas entidades “pai” da relação (Cliente e Veiculo), atra- vés dos atributos mappedBy = “cliente” e mappedBy = “veiculo”, como é possível ver nas Listagens 1 e2.
A entidade Reserva possui duas proprie- dades de data/hora ou “temporais”: fim e inicio. Na JPA, todas as propriedades tempo- rais (do tipo java.util.Date ou java.util.Calendar) devem ser marcadas com a anotação @Temporal, que especifica o tipo no banco de dados usado para persistência.
@Column(name = “DT_INICIO”, nullable = false) @Temporal(TemporalType.DATE)
private Date inicio;
A entidade Reserva também apresenta uma particularidade em relação às ou- tras entidades descritas anteriormente. Em Reserva estão codificadas algumas consultas estáticas, que podem ser exe- cutadas sobre a entidade. A anotação @NamedQueries é usada para agrupar todas as consultas estáticas da entidade, que são descritas individualmente em anotações @NamedQuery.
Mapeamento da locação
O mapeamento da entidade Locacao (Listagem 6) é simples. Além das ano- tações @Id e @GeneratedValue abordadas anteriormente, essa entidade apresenta um relacionamento um-para-um com a entidade Reserva, pois uma locação só pode existir para uma reserva criada anteriormente.
O relacionamento é descrito pela anotação @OneToOne, e possui uma sintaxe semelhante à de um mapeamento um-para-muitos:
@OneToOne
@JoinColumn(name = “CD_RESERVA”) private Reserva reserva;
O mapeamento está especificado em apenas um lado da relação (na entidade Locacao) e, por isso, é dito unidirecional. Isso significa que a locação “conhece” a reserva, mas a reserva “não conhece” a locação.
Persistindo dados e executando consultas
Como explicado anteriormente, a inter- face EntityManager é a responsável pelas operações de persistência. A estratégia de criação da instância desse gerenciador vai variar de acordo com o padrão de arquite- tura escolhido. Se a implementação de JPA for executada a partir de um servidor de aplicações Java EE, oEntityManager deve ser declarado pela anotação@PersistenceContext e será criado automaticamente pelo contai- ner (via injeção de dependência). Já no Java SE oEntityManager deve ser instanciado pela aplicação, o que é feito através da fábrica abstrataEntityManagerFactory:
EntityManagerFactory factory =
Persistence.createEntityManagerFactory(“locadora”); EntityManager manager =
factory.createEntityManager();
A classe EntityManagerFactory é criada pela classe Persistence, que lê as confi-
gurações da persistence unit do arquivo
persistence.xml (Listagem 7). Esse arquivo
possui um significado especial, arma- zenando as configurações de conexão e a descrição das entidades. O arquivo de configuração deve residir numa pasta com nome META-INF, na raiz do projeto
(veja oquadro “Configurando o ambiente” para conhecer a estrutura do projeto de exemplo).
A classeEntityManager define os principais métodos para incluir, localizar e salvar entidades. Os métodos de persistência não declaram exceções checadas, portan- to não é obrigatório declarar exceções no código.
Com o objeto EntityManager criado, basta executar o método correspondente sobre a entidade. Por exemplo, para localizar um veículo através da sua placa (chave primária), basta chamar o método find() passando como parâmetros a classe da entidade e sua chave:
Veiculo veiculo = manager.find(Veiculo.class, “ABC1234”);
A inserção e atualização da entidade são feitas através do métodopersist(). A primei- ra chamada ao método torna a entidade persistente, associando-a ao contexto de persistência:
Listagem 7.persistence.xml
<?xml version=”1.0” encoding=”UTF-8”?>
<persistence xmlns=”http://java.sun.com/xml/ns/persistence”
xmlns:xsi=”http ://www.w3.or g/2001/XMLSch ema-instance ” version=”1.0” xsi:schemaLocation=”http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd”> <persistence-unit name=”locadora”> <provider> oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider </provider> <class>br.com.jm.locadora.model.Cliente</class> <class>br.com.jm.locadora.model.Reserva</class> <class>br.com.jm.locadora.model.Locacao</class> <class>br.com.jm.locadora.model.Veiculo</class> <class>br.com.jm.locadora.model.Utilitario</class> <class>br.com.jm.locadora.model.Esportivo</class> <properties>
<property name=”topli nk.logging.le vel” value=”INFO ” />
<property name=”topli nk.jdbc.drive r” value=”com. mysql.jdbc.Dr iver” /> <property name=”topli nk.jdbc.url”
value=”jdbc:m ysql://127.0 .0.1:3306/loc adora” /> <property name=”topli nk.jdbc.user” value=”root ” /> <property name=”toplink.jdbc.password” value=”” /> </properties>
</persistence-unit> </persistence>
36 Java Magazine•Edição 39
O
Dali JPA Tools é um plug-in da Fundação Eclipse que oferece suporte ao JPA. Além deviews que auxiliam no mapeamento entre propriedades e colunas, o plug-in também fornece ferramen- tas para a geração das entidades a partir do modelo relacional e para geração do modelo relacional a partir das entidades. Aqui demonstraremos como instalar o plug-in e gerar as entidades a partir do banco de dados.Supondo que o ambiente já está configurado (veja o quadro
“Configurando o ambiente”), o primeiro passo consiste em efetuar o download do Dali de eclipse.org/dali . A instalação é simples, bastando extrair o arquivo compactado para o diretório raiz do Eclipse e reiniciar o IDE. Após a instalação, a perspectiva de per- sistência estará disponível.
Em seguida é necessário criar um novo proj eto Java e adicionar
os JARs do Toplink Essentials e do driver do MySQL. Após a criação do projeto, deve-se torná-lo “persistível”. Para isso clique com o botão direito sobre o projeto e selecione Java Perspective>Add Java Persistence. Uma caixa de diálogo semelhante à daFigura Q2
será exibida.
Preencha os dados conforme a figura e clique em Add Connec- tions para criar uma nova conexão ao MySQL. A tela de configu- ração de conexão será exibida (Figura Q3). Preencha os dados conforme ilustrado e clique emFinish.
Clique novamente emFinish para finalizar a configuração. Com a conexão funcionado, a última etapa é gerar as entidades. Para isso, clique com o botão direito sobre o projeto e selecione Java Perspective>Generate Entities. A tela de geração de entidades será exibida (Figura Q4) listando todas as entidades encontradas no banco. Selecione as entidades que deseja gerar e clique em
Finish.
Todas as entidades e mapeamentos serão gerados automatica- mente, assim como o arquivo persistence.xml . Para mapeamentos mais complexos é provável que algum ajuste manual ainda seja necessário.