Sunteți pe pagina 1din 23

JPA – Java Pessitence API

Implementado modelo de domínio

@Entity - javax.persistence.Entity;

Marca um POJO como objeto de domínio.

@Entity
public class Category {
...
public Category() { ... }
public Category(String name) { ... }
...
}

@Id - javax.persistence.Id

Marca o atributo com identificador. Pode ser usada tanto baseada em um campo como
em uma propriedade. Em um objeto de domínio pode ter somente uma anotação @Id.

@Entity @Entity
public class Category { public class Category {
@Id ...
public Long id; protected Long id;
public String name; ...
public Date modificationDate;
public Category() {} @Id
} public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
...
}

@Transient - javax.persistence.Transient

Define um campo como transitório, isto é, caso necessário você pode impedir uma
propriedade da entidade de ser persistida, marcando o getter com a anotação
@Transient.

@Entity
public class Category {
...
@Transient
protected Long activeUserCount;

1
transient public String generatedName
...
public Long getActiveUserCount() {
return activeUserCount;
}
public void setActiveUserCount(Long activeUserCount) {
this.activeUserCount = activeUserCount;
}
...
}

Persistindo tipos de dados

Campos e propriedades pesistidos terminam em uma tabela de banco de dados


relacional e têm que passar por uma API poderosa para chegar lá. Por causa desse
fato, há algumas restrições sobre quais tipos de dados podem ser persistidos.

Types Examples Exemplo


Java primitives Int, double, long
Java primitives wrappers java.lang.Integer, java.lang.Double
String type java.lang.String
Java API Serializable types java.math.BigInteger, java.sql.Date
User defined Serializable types Class that implements java.io.Serializable
Array types byte[], char[]
Enumerated type {SELLER, BIDDER, CSR, ADMIN}
Collection of Entity types Set<Category>
Embeddable class Classes that are defined @Embeddable

@IdClass - javax.persistence.IdClass;

Permite o uso de mais de uma anotação @Id de uma maneira lógica. Esta anotação é
utilizada em caso de entidades com campos compostos, isto é, entidade que possuem
mais de um campo como chave primária.

public class CategoryPK implements Serializable {


String name;
Date createDate;
public CategoryPK() {} |#2
public boolean equals(Object other) { |#3
if (other instanceof CategoryPK) {
final CategoryPK otherCategoryPK = (CategoryPK)other;
return (otherCategory.name.equals(name) &&
otherCategoryPK.createDate.equals(createDate));
}
return false;
}
public int hashCode() { |#4
return super.hashCode();
}
}

2
@Entity
@IdClass(CategoryPK.class) |#5
public class Category {
public Category() {}
@Id |#6
protected String name; |#6
@Id |#6
protected Date createDate; |#6
...
}

@EmbeddedId - javax.persistence.EmbeddedId

Usar a anotação @EmbeddedId é como mover a IdClass para a sua entidade e suar o
campos de identidade aninhado para armazenar dados da entidade. O obejto
@Embedded não precisa ser serializable. A anotação @Id e @IdClass não tem
permissão para se usada em conjunto com @Embedded.

@Embeddable |#1
public class CategoryPK {
String name;
Date createDate;
public CategoryPK() {}
public boolean equals(Object other) { |#2
if (other instanceof CategoryPK) {
final CategoryPK otherCategoryPK = (CategoryPK)other;
return (otherCategory.name.equals(name) &&
otherCategoryPK.createDate.equals(createDate));
}
return false;
}
public int hashCode() { |#2
return super.hashCode();
}
}

@Entity
public class Category {
public Category() {}
@EmbeddedId |#3
protected CategoryPK categoryPK;
...
}

@Embeddable - javax.persistence.Embeddable

Esta anotação é usada para designar objetos persistentes que não precisam de uma
identidade própria, um exemplo seria um objeto Address usado dentro do objeto User
como uma alternativa elegante para listar endereço, cidade, estado e etc.

@Embeddable |#1
public class Address {
protected String streetLine1;
protected String streetLine2;
protected String city;

3
protected String state;
protected String zipCode;
protected String country;
...
}

@Entity
public class User {
@Id
protected Long id; |#2
protected String username;
protected String firstName;
protected String lastName;
@Embedded
protected Address address; |#3
protected String email;
protected String phone;
...
}

Relações de Entidades

Uma relação significa que uma entidade possui uma referência de objeto à outra. Cada
tipo de relação é expresso em JPA por meio de uma anotação.

Tipo de relacionamento Anotação


Um-para-uma (One-to-one) @OneToOne
Um-para-vários (One-to-many) @OneToMany
Vários-para-um (Many-to-one) @ManyToOne
Vários-para-vários (Many-to-many) @ManyToMany

@OneToOne

É usada para marcar relações um-para-um uni e bidirecionais. Esta anotação pode ser
usada tanto para campo quanto para propriedade.

@Entity
public class User {
@Id
protected String userId;
protected String email;
@OneToOne
protected BillingInfo billingInfo;
}
@Entity
public class BillingInfo {
@Id
protected Long billingId;
protected String creditCardType;
protected String creditCardNumber;
protected String nameOnCreditCard;
protected Date creditCardExpiration;
protected String bankAccountNumber;
protected String bankName;
protected String routingNumber;
}

4
Bidirecional um-para-um

@Entity
public class BillingInfo {
@Id
protected Long billingId;
protected String creditCardType;
...
protected String routingNumber;
@OneToOne(mappedBy="billingInfo", optional="false");
protected User user;
}

@OneToMany e @ManyToOne

Estas relações são as mais comuns em sistemas empresariais. No mundo Java


significa que uma entidade tem campos do tipo coleção, como java.util.Set ou
java.util.List, armazenando múltiplas instâncias de outra entidade.

@Entity
public class Item {
@Id
protected Long itemId;
protected String title;
protected String description;
protected Date postdate;
...
@OneToMany(mappedBy="item")
protected Set<Bid> bids;
...
}
@Entity
public class Bid {
@Id
protected Long bidId;
protected Double amount;
protected Date timestamp;
...
@ManyToOne
protected Item item;
...
}

@ManyToMany

Neste tipo de relação, ambos os lados têm múltiplas referências às entidades


relacionadas, com exemplo, Categorias e Itens.
Apesar das relações várias-para-várias poderem ser unidirecionais, elas são, muitas
vezes, bidirecionais por causa da sua natureza de conexões cruzadas e independência
mútua. Deve-se usar o atributo targetEntity se não estiver usando genéricos.
A anotação @ManyToMany não possui o tributo opcional, significando que em uma
entidade pode existir mesmo se associações não existirem.

@Entity
public class Category {

5
@Id
protected Long categoryId;
protected String name;
...
@ManyToMany
protected Set<Item> items;
...
}
@Entity
public class Item {
@Id
protected Long itemId;
protected String title;
...
@ManyToMany(mappedBy="items")
protected Set<Category> categories;
...
}

Atributos disponíveis para cada anotação

6
Mapeamento Objeto-Relacional (ORM)

Em suma, o mapeamento de objetos-relacionais (ORM) especifica como sets de


objetos Java, incluindo referências entre eles, são mapeados em linhas e colunas em
tabelas de banco de dados.

O Problema de Impedância

O termo problema de impedância se refere às diferenças entre os paradigmas OO e


relacional e às dificuldades no desenvolvimento e aplicação oriundos dessas
diferenças. A camada de persistência onde o modelo de domínio se encontra é onde o
problema de impedância é geralmente mais aparente. A raiz do problema está em
diferir os objetos fundamentais das duas tecnologias.

Mapeando as entidades

@Entity
@Table(name="USERS")
@SecondaryTable(name="USER_PICTURES",
pkJoinColumns=@PrimaryKeyJoinColumn(name="USER_ID"))
public class User implements Serializable {
@Id
@Column(name="USER_ID", nullable=false)
protected Long userId;

@Column(name="USER_NAME", nullable=false)
protected String username;

@Column(name="FIRST_NAME", nullable=false, length=1)


protected String firstName;

@Column(name="LAST_NAME", nullable=false)
protected String lastName;

@Enumerated(EnumType.ORDINAL)
@Column(name="USER_TYPE", nullable=false)
protected UserType userType;

7
@Column(name="PICTURE", table="USER_PICTURES")
@Lob
@Basic(fetch=FetchType.LAZY)
protected byte[] picture;

@Column(name="CREATION_DATE", nullable=false)
@Temporal(TemporalType.DATE)
protected Date creationDate;

@Embedded
protected Address address;
public User() {}
}

@Embeddable
public class Address implements Serializable {
@Column(name="STREET", nullable=false)
protected String street;

@Column(name="CITY", nullable=false)
protected String city;

@Column(name="STATE", nullable=false)
protected String state;

@Column(name="ZIP_CODE", nullable=false)
protected String zipCode;

@Column(name="COUNTRY", nullable=false)
protected String country;
}

1.1.1. Especificando a tabela

@Table especifica a tabela contendo as colunas e torna disponível para o ORM. Todo
dado de persistência para entidade é mapeado para a tabela especifica pelo parâmetro
da anotação NAME.

@Target(TYPE)
@Retention(RUNTIME)
public @interface Table {
String name() default "";
String catalog() default "";
String schema() default "";
UniqueConstraint[] uniqueConstraints() default {};
}

A anotação @table é opcional por si mesma. Se ela for omitida, a entidade é assumida
para ser mapeada para uma tabela no esquema padrão com o mesmo nome como uma
classe de entidade. Se o parâmetro name for omitido, o nome da tabela é assumido
para ser o mesmo da entidade.

8
1.1.2. Mapeando as colunas

A anotação @Column mapeia um campo ou propriedade persistidos para uma coluna


da tabela.

@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Column {
String name() default "";
boolean unique() default false;
boolean nullable() default true;
boolean insertable() default true;
boolean updatable() default true;
String columnDefinition() default "";
String table() default "";
int length() default 255;
int precision() default 0;
int scale() default 0;
}

Os parâmetros insetable e update são usados para o comportamento do controle da


persistência. Se o parâmetro estiver marcado como false, o campo ou propriedade não
será incluído ou atualizado pelo provedor de persistência. Esses dois parâmetros são
geralmente úteis quando se trata de dados somente leitura, como chave primárias
geradas pelo banco de dados.

Eles podem ser definidos da seguinte forma:


@Column(name="USER_ID", insertable=false, updatable=false)
protected Long userId;

A anotação @Column é opcional. Se omitida, o campo ou propriedade de persistência


será salvo para a coluna da tabela que combine com o nome do campo ou
propriedade.

1.1.3. Usando a anotação @Enumerated

Na listagem anterior, o campo ‘tipo de usuário’ tem um tipo de UserType, que é uma
enumeração (enumeration) Java definida assim:
public enum UserType {SELLER, BIDDER, CSR, ADMIN};

Significando que um tipo de dado definido como UserType pode ter apenas os quatros
valores. Como uma série, cada elemento da enumeração é associado com um índice
chamado de ordinal. Por exemplo, o valor UserType.SELLER tem um ordinal 0
(zero), o valor UserType.BIDDER tem um ordinal 1 (um), e assim por diante.
Apersistência Java suporta duas opções por meio de anotação @Enumerated:

@Enumerated(EnumType.ORDINAL) @Enumerated(EnumType.STRING)
... ...
protected UserType userType; protected UserType userType;
Isto siguinifica se o campo for marcado Neste caso um valor UserType.ADMIN
para o UserType.SELLER, o valor 0 seria salvo dentro do banco de dados com
(zero) será armazenado na base de dados. “ADMIN”.

9
Por padrão, um campo ou propriedade enumerada é salva como um ordinal. Esse caso
ocorreria se a anotação @Enumerated fosse omitida ou se nenhum parâmetro de
anotação fosse especificado.

1.1.4. Mapeando CLOBs e BLOBs

Um recurso extremante poderoso de bando de dados relacionais é a habilidade de


armazenar dados muito grandes como objetos binários grandes (BLOB) e objetos
grandes caracteres (CLOB). Eles correspondem aos objetos JDBC java.sql.Blob e
java.sql.Clob. A anotação @Lob designa uma propriedade ou campo como um CLOB
ou BLOB.

@Lob
@Basic(fetch=FetchType.LAZY)
protected byte[] picture;

Se um campo ou propriedade designada pela anotação @Lob é um CLOB ou BLOG,


é determinada pelo seu tipo. Se o dado é do tipo cha[] ou String, o provedor de
persistência mapeia os dados para uma coluna CLOB, caso não seja, a coluna é
mapeada para BLOG.
Outra anotação extremamente útil para usar em conjunto com a anotação @Lob é a
@Basic. Esta anotação pode ser marcada em qualquer atributo com mapeamento
direto no campo, causando o carregamento dos dados apenas quando ela é acessada
primeiro.

O adiamento do carregamento dos dados da entidade a partir de um banco de dados é


conhecido como carregamento lento. Esse é um grande recurso uma vez que os dados
geralmente têm memória muito intensa e devem ser carregados apenas quando
necessário.

1.1.5. Mapeando Tipos Temporais

A anotação @Temporal especifica qual tipos de dados queremos mapear; um tipo


persistente java.util.Date ou java.util.Calendar.

@Temporal(TemporalType.DATE)
protected Date creationDate;

Este parâmetro é reduntante quando se usa os tipos java.sql.Date, java.sql.Time ou


java.sql.Timestamp. Caso não seja especificado um parâmetro para a anotação, o
provedor de persistência assumirá o mapeamento do tipo de dados como Timestamp.

1.1.6. Mapeando uma entidade para muitas tabelas

Alguma vez um dado de entidade deve vir de duas tabelas diferentes. A anotação
@SecondaryTable nos permite extrair dados da entidade de mais de uam tabela, é
definido como segue:

@Target({TYPE}) @Retention(RUNTIME)
public @interface SecondaryTable {
String name();
String catalog() default "";

10
String schema() default "";
PrimaryKeyJoinColumn[] pkJoinColumns() default {};
UniqueConstraint[] uniqueConstraints() default {};
}

Note que além do elemento pkJoinColumn, a definição da anotação é idêntica à


definição da anotação @Table.

@Entity
@Table(name="USERS")
@SecondaryTable(name="USER_PICTURES",
pkJoinColumns=@PrimaryKeyJoinColumn(nam
e="USER_ID"))
public class User implements
Serializable {
..}

Duas tabelas nas anotações @Table e @SecondaryTable são relacionadas de alguma


forma e ligadas para criar a entidade. Esse tipo de relação é implementada pela
criação de uma chave estrangeira na tabela secundária que também pode ser uma
chave primária da tabela. A anotação @SecondaryTable pode ser usada mais de uma
vez para a mesma entidade no caso de uma tabela possuir colunas em mais de uma
tabela.

1.1.7. Geração de chaves primárias

Há três formas populares de valores de geração de chaves primárias: identidades


(identities), seqüências (sequences) e tabelas (tables). Essas três estratégias são
suportadas via anotação @GeneratedValue.

Colunas de Identidades como geradores

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="USER_ID")
protected Long userId;

Esse código assume que a restrição de identidade existe na coluna USER.USER_ID.


Quando a estratégia IDENTITY é usada como tipo gerador, o valor para o campo por
não ser habilitado antes de os dados da entidade serem salvos porque é tipicamente
gerado quando é executado o commit.

Seqüências de banco de dados como geradores

Para usar geradores de seqüências, primeiro defina a seqüência no banco de dados.


@SequenceGenerator(name="USER_SEQUENCE_GENERATOR",
sequenceName="USER_SEQUENCE",
initialValue=1, allocationSize=10)

11
A anotação @SequenceGerator cria um gerador de seguencia chamado
USER_SEQUENCI_GENERATOR como referencia à seqüência Oracle.
O atributo allocantionSize especifica especifica em quanto a seqüência é
incrementada a cada vez que um valor é gerado. Os valores padrão para initialValue e
allocationSize são 0 e 50, respectivamente.
Vale ressaltar que qualquer gerador é compartilhado por todas as entidades no módulo
de persistência e cada gerador deve ser unicamente nomeado. Finalmente,
reimplementaremos a chave gerada para a coluna USER_ID, como segue:

@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE,
generator="USER_SEQUENCE_GENERATOR")
@Column(name="USER_ID")
protected Long userId;

Tabelas de seqüência como geradores

O primeiro passo é criar uma tabela para usar na geração de valore seguindo um
formato geral como o seguinte criado para o Oracle:

CREATE TABLE SEQUENCE_GENERATOR_TABLE INSERT INTO


(SEQUENCE_NAME VARCHAR2(80) NOT NULL, SEQUENCE_GENERATOR_TABLE
SEQUENCE_VALUE NUMBER(15) NOT NULL, (SEQUENCE_NAME,
PRIMARY KEY (SEQUENCE_NAME)); SEQUENCE_VALUE)
VALUES ('USER_SEQUENCE', 1);

A coluna SEQUENCE_NAME é destinada para armazenar o nome de uma seqüência e


a coluna SEQUENCE_VALUE para armazenar o valor atual da seqüência. Apesar da
óbvia complexidade dessa abordagem, uma vantagem é que a mesma tabela de
seqüência pode ser usada para múltiplas seqüências na aplicação.

@TableGenerator (name="USER_TABLE_GENERATOR",
table="SEQUENCE_GENERATOR_TABLE",
pkColumnName="SEQUENCE_NAME",
valueColumnName="SEQUENCE_VALUE",
pkColumnValue="USER_SEQUENCE")

@Id
@GeneratedValue(strategy=GenerationType.TABLE,
generator="USER_TABLE_GENERATOR")
@Column(name="USER_ID")
protected Long userId;

Estratégia padrão para geração de chaves primárias

A ultima opção para a geração de chaves é deixar o provedor decidir a melhor


estratégia para o banco de dados inferir usando a especificação AUTO, como segue:

12
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="USER_ID")
protected Long userId;

1.1.8. Mapeamento de Classes Embutidas

@Table(name="USERS")
...
public class User implements Serializable {
@Id
@Column(name="USER_ID", nullable=false)
protected Long userId;
...
@Embedded
protected Address address;
...
}

@Embeddable
public class Address implements Serializable {
@Column(name="STREET", nullable=false)
protected String street;
...
@Column(name="ZIP_CODE", nullable=false)
protected String zipCode;
...
}

Note que diferente da entidade User, o objeto embutido Address está perdendo a
anotação @Table. Isto que o EJB3 não permite que objetos embutidos sejam
mapeados para uma tabela diferente da entidade incluída. O mapeamento da anotação
@Column aplicado aos campos do objeto Address, realmente, se refere às colunas na
tabela USERS.
Uns dos recursos mais úteis das classes embutidas é que elas podem ser
compartilhadas entre entidades.

Mapeando das relações das entidades

1.1.9. Mapeando relações um-para-um

Usando a anotação @JoinColumn

Se a tabela inferior para a entidade de referência é a que contém a chave estrangeira


para a tabela em que a referida entidade “child” é mapeada.

@Entity
@Table(name="USERS")
public class User {
@Id
@Column(name="USER_ID")
protected String userId;
...
@OneToOne

13
@JoinColumn(name="USER_BILLING_ID",
referencedColumnName="BILLING_ID", updatable=false)
protected BillingInfo billingInfo;
}

@Entity
@Table(name="BILLING_INFO")
public class BillingInfo {
@Id
@Column(name="BILLING_ID")
protected Long billingId;
...
}

O elemento name da anotação se refere ao nome da chave estrangeira (foreing key) da


tabela User. O elemento refencedColumnName especifica o nome da chave primária
ou uma única chave a que a chave estrangeira se refere. Assim como a anotação
@Column, a anotação @JoinColumn contém os elementos updatable, insertable,
table e unique.
Em nosso caso, o elemento updatable é marcado como falso, o que significa que o
provedor de persistência não atualizaria a chave estrangeira, mesmo se a referencia
billingInf fosse trocada.
Se você tem mais de uma coluna na chave estrangeira, também pode usar a anotação
@JoinColumn. Se você tem uma relação bidirecional, então a entidade no lado oposto
da relação conterá o elemento mappedBy.

@Entity
public class BillingInfo {
@OneToOne(mappedBy="billingInfo")
protected User user;
..
}

O elemento mappedBy identifica o nome do campo de relação no lado detentor da


relação. Em uma relação bidirecional, o lado detentor é a entidade que armazena a
relação na sua tabela inferior. Em nosso exemplo, a tabela USER armazena a relação
no campo USER_BILLING_ID, então, é o detentor da relação.

Usando a anotação @PrimaryKeyJoinColumn

A anotação @PrimaryKeyJoinColumn é usada em relações um-para-um quando


ambas as tabelas, referidas e de referência, compartilham a mesma chave primária da
tabela de referência.

@Entity
@Table(name="USERS")
public class User {
@Id
@Column(name="USER_ID")
protected Long userId;
...
@OneToOne
@PrimaryKeyJoinColumn(name="USER_ID",
referencedColumnName="BILLING_USER_ID")
protected BillingInfo billingInfo;

14
}

@Entity
@Table(name="BILLING_INFO")
public class BillingInfo {
@Id
@Column(name="BILLING_USER_ID")
protected Long userId;
...
}

O elemento name da anotação se refere à coluna da chave primária da tabela que


armazena a entidade atual. Por outro lado, o elemento refencedColumnName se refere
à chave estrangeira na tabela que detém a entidade referida. Em nosso caso, a chave
primária é a coluna BILLING_USER_ID, na tabela BILLING_INFO, e aponta para a
chave primária USER.USER_ID. Se o nome das chaves forem o mesmo, pode-se
omitir o elemento refencedColumnName.
Caso tenha uma chave primária composta na tabela, você deverá usar a anotação
@PrimaryKeyJoinColums.

1.1.10. Mapeando um-para-vários e vários-para-um

A vantagem do mapeamento da persistência EJB3 está no fato de que as mesmas duas


anotações usadas para mapeamento de relações um-para-um também são usadas para
relações um-para-vários. Isto se dá porque ambos os tipos de relações são
implementadas como uma relação primária/chave estrangeira no banco de dados.

@Entity
@Table(name="ITEMS")
public class Item {
@Id
@Column(name="ITEM_ID")
protected Long itemId;
...
@OneToMany(mappedBy="item")
protected Set<Bid> bids;
...
}

@Entity
@Table(name="BIDS")
public class Bid {
@Id
@Column(name="BID_ID")
protected Long bidId;
...
@ManyToOne
@JoinColumn(name="BID_ITEM_ID", referencedColumnName="ITEM_ID")
protected Item item;
...
}

Visto que múltiplas instâncias BIDS fariam referência ao mesmo record na tabela
ITENS, a tabela BIDS deterá uma referência à chave estrangeira para a chave
primária da tabela ITENS. Na @ManyToOne, o elemento name especifica a chave
estrangeira, BID_ITEM_ID, e o elemento referencedColumnName especifica a chave

15
primária ITEM_ID. Da perspectiva da entidade Item, isso significa que o provedor de
persistência descobriria quais instâncias Bid colocar nos Bids carregando as chaves
BID_ITEM_ID que cominam na tabela BIDS.

Ao Invés de repetir a mesma anotação @JoinColumn, usamos o elemento mappedBy.


O provedor de persistência gerará erros no tempo de distribuição se você especificara
@JoinColumn dos dois lados de uma relação bidirecional um-para-vários. Em uma
relação bidirecional um-para-vários, o detentor da relação é o lado da entidade que
armazena a chave estrangeira isto é, o lado vários da relação.
Em geral, deve-se usar o elemento mappedBy sempre que uma relação bidirecional
for necessária. Se você não especificar o elemento mappedBy com uma anotação
@OneToOne, o provedor de persistência tratará a relação como unidirecional. O JPA
não suporta relação unidirecional um-para-vários entre Itens e Bid. A anotação
@ManyToOne não suporta o elemento mappedBy.

O ultimo ponto a ressaltar é o fato de que as chaves estrangeiras podem se referir de


volta à chave primária na mesma tabela em que ela se encontra. Por exemplo, uma
relação vários-para-um entre subcategorias e categoria pai, que podem ser expressas
como na listagem a seguir.

@Entity
@Table(name="CATEGORIES")
public class Category implements Serializable {
@Id
@Column(name="CATEGORY_ID")
protected Long categoryId;
...
@ManyToOne
@JoinColumn(name="PARENT_ID",referencedColumnName="CATEGORY_ID"
)
Category parentCategory; ...

1.1.11. Mapeando vários-para-vários

Uma relação vários-para-vários no mundo dos bancos de dados é implementada


dividindo-a em duas ou mais relações um-para-vários armazenadas em uma tabela de
relação ou junção. Uma tabela de junção nos permite combinar indiretamente chaves
primárias de cada lado da relação armazenando pares arbitrários de chaves
estrangeiras em uma linha.

O mapeamento @JoinTable, no EJB3, modela essa técnica, como mostrado no


exemplo a seguir. Lembre-se que uma entidade Category pode conter muitos itens e
um Item pode pertencer a múltiplas entidades Category.

16
@Entity
@Table(name="CATEGORIES")
public class Category implements Serializable {
@Id
@Column(name="CATEGORY_ID")
protected Long categoryId;

@ManyToMany
@JoinTable(name="CATEGORIES_ITEMS",
joinColumns=
@JoinColumn(name="CI_CATEGORY_ID",
referencedColumnName="CATEGORY_ID"),
inverseJoinColumns=
@JoinColumn(name="CI_ITEM_ID",
referencedColumnName="ITEM_ID"))
protected Set<Item> items;
...
}

@Entity
@Table(name="ITEMS")
public class Item implements Serializable {
@Id
@Column(name="ITEM_ID")
protected Long itemId;
...
@ManyToMany(mappedBy="items")
protected Set<Category> categories;
...
}

O elemento name, da anotação @JoinTable, especifica a relação de uma tabela de


junção, chamada de CATEGORIES_ITEMS. Esta tabela possui apenas duas colunas:
CI_CATEGORY e CI_ITEMS_ID. Este campos são chaves estrangeiras das tabelas
CATEGORIES e ITEMS, como é indicado nos elementos joinColumns e
inverseJoinColumns. O elemento joinColuns descreve a relação “detentor” entre as
entidades e o inverseJoinColumns descreve a relação “subordinada”. A distinção do
lado detentor da relação é puramente arbitrária.

Assim como usamos o elemento mappedBy para reduzi a redundância no


mapeamento de relações um-para-vários, usamos o elemento mappedBy no campo
Item.categories para apontar para a definição @JoinTable em Category.items.
Podemos especificar mais de uma coluna de junção com a anotação @JoinColumn se
tivermos mais de uma chave estrangeira.

Mapeando Herança

Exploraremos o mapeamento de herança usando as três estratégias oferecidas pela


anotação @Inheritance implementando um exemplo familiar. Neste esquema, a
entidade User conteria os dados comportamento comum a todos os usuários.
Enquanto subclasses como Bidder e Seller conteria dados e comportamento específico
para cada tipo de usuário. Será mostrado como as entidades em hierarquia podem ser
mapeadas para tabela usando os tipos diferentes de estratégias suportadas pelo JPA:
Tabela Simple, Tabela Juntas e Tabelas por Classe.

17
1.1.12. Estratégia da tabela simples

Esta estratégia, todas as classes da hierarquia são mapeados para uma tabela simples.
Isto significa que a tabela simples conterá um super set de todos os dados armazenado
na hierarquia. Objetos diferentes na hierarquia OO são identificados usando uma
coluna especial chamada coluna DISCRIMINADORA. A coluna discriminadora no
exemplo é USER_TYPE.

@Entity
@Table(name="USERS")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="USER_TYPE",
discriminatorType=DiscriminatorType.STRING, length=1)
public abstract class User ...

@Entity
@DiscriminatorValue(value="S")
public class Seller extends User ...

@Entity
@DiscriminatorValue(value="B")
public class Bidder extends User

A estratégia de herança e a coluna discriminadora têm que ser especificadas na


entidade raiz da hierarquia OO. Na listagem, especificamos a estratégia para ser
InheritanceType.SIGLE_TABLE na anotação @Inheritance, na entidade USER. A
anotação @Table, na entidade User, especifica o nome da tabela simples usada para o
mapeamento da herança USERS. A notação @DiscriminationColumn especifica os
detalhes da coluna discriminadora.
A tabela simples é a estratégia padrão de herança no EJB 3. Embora essa estratégia
seja simples de ser usada, tem uma grande desvantagem que pode ser aparente. Ela
não utilizada todo seu poder de banco de dados relacional em termos de uso de chaves
primárias/estrangeiras e resulta em um grande número valores e de colunas Null.

18
1.1.13. Estratégia das tabelas juntas

A estratégia de herança das tabelas juntas usa as relações um-para-um para modelar a
herança OO. Em efeito, a estratégia das tabelas juntas envolve a criação de tabelas
separadas para cada entidade na hierarquia OO e relaciona descendentes na hierarquia
com as ralações um-para-um. Na hierarquia, as tabelas filhas contém colunas
específicas para cada subtipo da entidade, como exemplo, as tabelas USERS e
SELLER são relacionadas por meio de chave estrangeira USER_ID na tabela
SELLER apontando para a chave primária da tabela USERS. Uma relação similar
existe entre as tabelas BIDDER e USERS. A coluna discriminadora na tabela USERS
ainda é usada, primeiramente, como uma forma de diferenciar facilmente tipos de
dados na hierarquia.

@Entity
@Table(name="USERS")
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="USER_TYPE",
discriminatorType=STRING, length=1)
public abstract class User ...

@Entity
@Table(name="SELLERS")
@DiscriminatorValue(value="S")
@PrimaryKeyJoinColumn(name="USER_ID")
public class Seller extends User ...

@Entity
@Table(name="BIDDERS")
@DiscriminatorValue(value="B")
@PrimaryKeyJoinColumn(name="USER_ID")
public class Seller extends User ...

A listagem usa as anotações @DiscriminatorColumn e @DiscriminatorValue


exatamente da mesma forma que a estratégia da tabela simples. O elemento strategy
da anotação @Inheritance é especificado como JOINED. Em adição, as relações um-
para-um entre tabelas parent e child são implementados por meio da anotação
@PrimaryKeyJoinColumn em ambas entidades, Seller e Bidder. Em ambos os casos,
o elemento name especifica a chave estrangeira USER_ID. A estratégia das tabelas
juntas é provavelmente a melhor escolha de mapeamento, a partir de uma perspectiva
de desing (projeto). Partindo de uma perspectiva de desempenho, é pior que a
estratégia de tabela simples porque requer a junção de múltiplas tabelas por questões
polimórficas.

19
1.1.14. Estratégia de Tabela por Classe

A estratégia de tabela pro classe é, provavelmente, a mais simples das estratégias de


herança para uma pessoa leiga entender. Entretanto, essa estratégia é a pior sob ambos
os pontos de vista, relacional e OO. Nesta estratégica, ambas as superclasses (classe
concreta) e subclasse são armazenadas em suas próprias tabelas e nenhuma relação
existe entre qualquer uma das tabelas.
Como mostrado nas figuras a baixo, os dados de entidade são armazenados em suas
próprias tabelas mesmo que eles tenham sido herdados de uma superclasse. Isto é
verdadeiro mesmo para a chave primária USER_ID. Como resultado, as chaves
primárias em todas as tabelas devem ser mutuamente exclusivas para este esquema
funcional. Em adição, colunas herdadas são duplicadas ao longo das tabelas, assim
como a coluna USERNAME. Usando essa estratégia de herança, definimos a
estratégia na superclasse e mapeamos as tabelas para todas as classes.

@Entity
@Table(name="USERS")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class User {
...
@Entity
@Table(name="SELLERS")
public class Seller extends User {
...
@Entity
@Table(name="BIDDERS")
public class Bidder extends User {

Como pode ser visto, a estratégia de herança é especificada na entidade superclasse


User. Entretanto, todas as entidades concretas na hierarquia OO são mapeadas para
separar tabelas e cada uma tem uma especificação @Table. A maior desvantagem

20
desse tipo de mapeamento é que ele não tem bom suporte para relações ou questões
polimórficas porque cada subclasse é mapeada para sua própria tabela.

A limitação é que, quando você quer carregar entidade sobre o provedor de


persistência, deve usar SQL UNION ou carregar cada entidade com SQL separado
por cada subclasse na hierarquia. Além de ser enfadonha, essa estratégia é a mais
difícil para um provedor EJB3 implementação corretamente. Como resultado, a
implementação dessa estratégia tem sido opcional para o provedor por uma
especificação EJB3. Recomendamos que você evite esta estratégia quando puder.

Resumo das estratégias

Além dessas estratégias de herança, o EJB3 JPA permite um entidade herdar de uma
classe não-entidade. Tal classe é anotada com @MappedSuperClass. Como um objeto
embutido, uma superclasse mapeada não tem uma tabela associada.

21
Manipulando entidades com o EntityManager

Você viu como os objetos de domínio e relações são mapeadas para o banco de dados.
Embora as anotações ORM exploradas no capítulo 8 indiquem como uma entidade é
persistida, elas mesmas não são persistentes. Isto é realizado pelas aplicações
utilizando a interface EntityManager, que executa operações CRUD (Creat, Read,
Update, Delete) nos objetos de domínio.

Apresentando o EntityManager

A API EntityManager provavelmente é a parte mais importante e interessante e


interssante do JPA, pois ela gerencia o ciclo de vida das entidades.

1.1.15. A interface EntityManager

De certo modo, o EntityManager é a ponte entre os mundos OO e relacionais, como


descrito na imagem abaixo.

Além de fornecer operações CRUD, o EntityManager também tanta manter as


entidades em sincronia com o banco de dados automaticamente enquanto elas estão
dentro da busca do EntityManager.

Assinatura do método Descrição


public void persist(Object entity); Salva (persiste) uma entidade no banco de
dados e também torna e entidade
gerenciada.
public <T> T merge(T entity); Mescla uma entidade ao contexto de
persistência EntityManager e retorna a
entidade mesclada.
public void remove(Object entity); Remove uma entidade do banco de dados
public <T> T find(Class<T> entityClass, Encontra uma instância de entidade pela
Object primaryKey); sua chave primária.
public void flush(); Sincroniza o estado das entidades no
contexto de persistência do EntityManger
com o banco de dados.
public void Altera o modo flush do contexto de
setFlushMode(FlushModeType persistência. O modo flush poder ser
flushMode); AUTO ou COMMIT. O padrão é AUTO,
significando que o EntityManager tenta
sincronizar automaticamente as entidades
com o banco de dados.

22
public FlushModeType getFlushMode(); Recupera o modo flush atual.
public void refresh(Object entity); Atualiza (reinicia) a entidade do banco de
dados
public Query createQuery(String Cria uma consulta dinâmica utilizando
jpqlString); uma instrução JPQL.
public Query createNamedQuery(String Cria uma instância de consulta com base
name); em uma consulta nomeada na instância da
entidade.
Cria uma consulta dinâmica utilizando
public Query createNativeQuery(String uma instrução SQL nativa.
sqlString);
public Query createNativeQuery(String
sqlString, Class result Class);
public Query createNativeQuery(String
sqlString, String resultSetMapping);
public void close(); Finaliza uma aplicação EntityManager
gerenciada.
public boolean isOpen(); Verifica se o EntityManager está aberto.
public EntityTransaction Recupera um objeto de transação que
getTransaction(); pode ser utilizado para iniciar uma
transação manualmente ou finaliza-la.
public void joinTransaction(); Pede para um EntityManager associar-se
a uma transação JTA existente.

1.1.16. O clico de vida de uma entidade

Entender o ciclo de vida é fácil, uma vez que você entendeu um conceito óbvio: o
EntityManager não sabe nada sobre um POJO a não ser a maneira como ele é
anotado. Isso é exatamente o oposto de POJOs anotados para serem beans de sessão
ou MDBs, que são carregados e gerenciados pelo container assim que a aplicação
inicia.
Uma entidade que o EntityManager está observando é considerada agregada ou
gerenciada. Por outro lado, quando um EntityManager pára de gerenciar uma
entidade, a entidade é chamada de separada. Uma entidade que nunca foi gerenciada
em nenhum momento é chamada de temporária ou nova.

23

S-ar putea să vă placă și