Para integrar Aplicações Orientadas por Objetos com ...mouta/EAPLI-2016-2017-2Sem/JPA.pdf · dos...

61
1 Para integrar Aplicações Orientadas por Objetos com Bases de Dados Relacionais podemos encapsular a parte relacional usando Frameworks ORM ( Object Relational Mapping). JPA (Java PersistenceAPI – Interface para Programação de Aplicações de Persistência em Java) é uma especificação para persistência em Java, contendo uma série de interfaces. EclipseLink é a implementação que vamos utilizar, constituindo assim um JPA Provider. A vantagem de usar a especificação JPA é que podemos trocar de provider alterando apenas as configurações na nossa aplicação. O objetivo da utilização de um framework ORM é fazer o mapeamento entre o estado dos objetos na aplicação OO e os dados na base de dados relacional. Este mapeamento pode ser definido através de anotações colocadas nas classes do programa Java, ou através de ficheiros de configuração xml. Vamos utilizar anotações.

Transcript of Para integrar Aplicações Orientadas por Objetos com ...mouta/EAPLI-2016-2017-2Sem/JPA.pdf · dos...

1

Para integrar Aplicações Orientadas por Objetos com Bases de Dados Relacionais podemos encapsular a parte relacional usando Frameworks ORM ( Object RelationalMapping).

JPA (Java Persistence API – Interface para Programação de Aplicações de Persistência em Java) é uma especificação para persistência em Java, contendo uma série de interfaces.

EclipseLink é a implementação que vamos utilizar, constituindo assim um JPA Provider.A vantagem de usar a especificação JPA é que podemos trocar de provider alterando apenas as configurações na nossa aplicação.

O objetivo da utilização de um framework ORM é fazer o mapeamento entre o estado dos objetos na aplicação OO e os dados na base de dados relacional.Este mapeamento pode ser definido através de anotações colocadas nas classes do programa Java, ou através de ficheiros de configuração xml.Vamos utilizar anotações.

2

Principais anotações JPA

@Entity – indica as classes que vão ser mapeadas em tabelas. Associa uma classe a uma tabela na base de dados. Por omissão, o nome da tabela é igual ao nome da classe.

@Table(name=”Nome-da-tabela”) – usada quando os nomes da classe e da tabela são diferentes.

@Id – indica o atributo da classe que será mapeado para a chave primária da tabela.

@GeneratedValue – indica que o valor do atributo que constitui a chave primária é gerado pela base de dados no momento em que um novo registo é inserido.

@Column(name=”Nome-da-coluna”) – usada quando o nome do atributo da classe é diferente do da coluna da tabela.

3

Classes Entidade

Classes Entidade são classes Java definidas pelo utilizador cujas instâncias podem ser guardadas na base de dados.

Para que uma classe Java possa constituir uma entidade tem de:• ter um construtor sem argumentos.• ter uma chave primária.• não pode ser final, nem ter métodos final ou variáveis de instância final.• se tiver coleções devem ser declaradas através de interfaces.

A chave primária pode ser simples ou composta. Uma chave primária composta consiste em mais que um atributo. Uma chave primária composta corresponde a um conjunto de campos ou propriedades persistentes simples, e deve ser definida numa classe chave primária.

Se uma instância de uma entidade é passada por valor como um objecto detached, a classe deve implementar a interface Serializable;

As variáveis de instância persistentes devem ser declaradas com modificador de acesso private, package, ou protected, e só podem ser acedidas diretamente pelos métodos da classe entidade.

O estado persistente de uma entidade pode ser acedido através das variáveis de instância da entidade ou através das suas propriedades.

@Entitypublic class Pessoa implements Serializable {

@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String nome;

private Pessoa() {}

public Pessoa(String nome) {this.nome = nome;

}

public Long getId() {return id;

}

public String getNome() {return nome;

}

@Overridepublic String toString() {

return "pessoas.Pessoa[ id=" + id + " ]";}

}4

5

Ficheiro de configuração persistence.xml

Uma aplicação JPA tem de ter um ficheiro de configuração persistence.xml criado dentro do diretório

META-INF. Este ficheiro pode ter definições para um conjunto de unidades de persistência.

Cada unidade de persistência, elemento <persistence-unit>, contém:

O nome da unidade de persistência que é necessário fornecer para criar a instância

EntityManagerFactory

a informação de configuração de uma fonte de dados

define uma ou mais entidades managed que a instância Entity Manager de uma aplicação pode gerir.

<?xml version="1.0" encoding="UTF-8"?>

<persistence version="2.1" xmlns= . . . >

<persistence-unit name="PessoasPU" transaction-type="RESOURCE_LOCAL">

<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

<class>pessoas.Pessoa</class>

<properties>

<property name="javax.persistence.jdbc.url" value="jdbc:h2:./bd/Pessoas"/>

<property name="javax.persistence.jdbc.user" value=""/>

<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>

<property name="javax.persistence.jdbc.password" value=""/>

<property name="javax.persistence.schema-generation.database.action"

value="create"/>

</properties>

</persistence-unit>

</persistence>

6

EntityManagerFactory e EntityManager

Para construir uma aplicação de persistência necessitámos de criar um objeto EntityManagerFactory, o qual lê as configurações existentes no ficheiro persistence.xml, e as anotações colocadas nas classes, e cria ligações à base de dados.

Para obter uma instância EntityManagerFactory usámos o método estático factory da classe JPA Persistence que toma como argumento o nome da persistence unit.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPA2PU");

A instância EntityManagerFactory abre a base de dados. Se a base de dados não existir é criada.

Quando a aplicação termina devemos fechar o EntityManagerFactory para fechar o ficheiro base de dados.

emf.close();

7

A instanciação do EntityManagerFactory é uma operação pesada e por isso deve ser realizada uma única vez por aplicação.É normal usar uma classe utilitária JPAUtil, que usa o padrão Singleton para garantir a existência de apenas uma única instância da classe.Em seguida através do EntityManagerFactory criámos um objeto EntityManager que usámos para gerir o estado dos objectos.Um EntityManager representa uma ligação à base de dados e também fornece funcionalidades para realizar operações na base de dados.

public class JPAUtil {

private static EntityManagerFactory factory =

Persistence.createEntityManagerFactory("PersistenceUnitName");

public static EntityManager getEntityManager() {

return factory.createEntityManager();

}

}

Algumas aplicações, tais como aplicações Web, necessitam de múltiplas ligações à base de dados. Numa aplicação Web é normal estabelecer uma ligação separada à base de dados para cada pedido http, usando uma instância EntityManager separada. A principal função de um objecto EntityManagerFactory é suportar a criação de instâncias EntityManager para uma dada base de dados.

8

Sempre que se cria um EntityManager também é criado um objeto EntityTransaction associado ao EntityManager que deve ser usad0 para sincronizar o estado dos objetos com os dados da base de dados.Operações que afetam o conteúdo da base de dados (insert, update, e delete) devem ser realizadas dentro de uma transação.É importante efetuar o close do EntityManager para libertar recursos que ficam disponíveis para o EntityManagerFactory.

public class Teste {

public static void main(String[] args) {

Pessoa p1 = new Pessoa("Manuel");

System.out.println("ID de Manuel: " + p1.getId()); // null

EntityManagerFactory emf =

Persistence.createEntityManagerFactory("PersistenceUnitName");

EntityManager em = emf.createEntityManager();

em.getTransaction().begin();

em.persist(p1);

em.getTransaction().commit();

em.close();

emf.close();

System.out.println("ID gerado: " + p1.getId()); // 1

}

}

9

Persistence

EntityManager

EntityManagerFactory

PersistenceContext

PersistenceUnit

cria

cria

configurado por

gere

10

JPA é usado para acesso à base de dados e por isso não devemos espalhar o código JPA por toda a aplicação.Para cada entidade devemos criar na camada de persistência uma classe DAO (Data Access Object) encapsulando o acesso à base de dados.

Para a classe Pessoa a classe utilitária para acesso a dados seria PessoaDAO:

public class PessoaDAO {

public void save(Pessoa pessoa) {

EntityManager em = JPAUtil.getEntityManager();

em.getTransaction().begin();

em.persist(pessoa);

em.getTransaction().commit();

em.close();

}

public void delete(Pessoa pessoa) {

EntityManager em = JPAUtil.getEntityManager();

em.getTransaction().begin();

em.remove(pessoa);

em.getTransaction().commit();

em.close();

}

// ...

}

11

Na classe apresentada a realização de qualquer operação abre uma ligação à base de dados (criação do EntityManager), inicia a transação, executa a operação e fecha a transação e a ligação.O uso desta classe utilitária fica mais flexível se retirarmos a abertura e fecho das ligações à base de dados e o tratamento de transações:

public class PessoaDAO {

private final EntityManager em;

public PessoaDAO(EntityManager em) {

this.em = em;

}

public void save(Pessoa pessoa) {

em.persist(pessoa);

}

public void delete(Pessoa pessoa) {

em.remove(pessoa);

}

public Pessoa findById(Long id) {

return em.getReference(Pessoa.class, id);

}

public List<Pessoa> getAll() {

return em.createQuery("from Pessoa p").getResultList();

}

// ...

}

12

Para cada entidade temos de repetir um código semelhante. Podemos criar uma classe DAO genérica que sirva de base a qualquer entidade:

public class GenericDAO<T> {private final EntityManager em;private final Class<T> classe;

public GenericDAO(EntityManager em, Class<T> classe) {this.em = em;this.classe = classe;

}

public void save(T t) {em.persist(t);

}public void delete(T t) {

em.remove(t);}public T findById(Long id) {

return em.getReference(classe, id);}public List<T> getAll() {

return em.createQuery("from " + classe. getName() + " p").getResultList();}// ...

}

13

Agora as classes DAO das diferentes entidades não precisam implementar estes métodos, pois podem usar os métodos definidos na classe GenericDAO:

public class PessoaDAO {

private final GenericDAO<Pessoa> dao;

public PessoaDAO(EntityManager em) {

dao = new GenericDAO<Pessoa>(em, Pessoa.class);

}

public void save(Pessoa t) {

dao.save(t);

}

public void delete(Pessoa t) {

dao.delete(t);

}

public Pessoa findById(Long id) {

return dao.findById(id);

}

public List<Pessoa> getAll() {

return dao.getAll();;

}

// ...

}

A execução de cada operação é delegada no objecto dao.

14

Outra abordagem seria usar herança para definir cada uma das classes DAO das diferentes entidades.

public class PessoaDAO extends GenericDAO<Pessoa> {public PessoaDAO(EntityManager em) {

super(em, Pessoa.class);}

}

public class Teste {public static void main(String[] args) {

EntityManager em = JPAUtil.getEntityManager();Pessoa p1 = new Pessoa("Manuel");PessoaDAO dao = new PessoaDAO(em);em.getTransaction().begin();dao.save(p1);em.getTransaction().commit();System.out.println("ID gerado: " + p1.getId());em.close();

}}

Mas deste modo herdamos todos os métodos definidos na classe genérica, o que poderia contrariar os requisitos da nossa aplicação (por exemplo, não permitir apagar objetos Pessoa).

15

@Entitypublic class Pessoa {

@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id; private String primeiroNome;private String ultimoNome;private int idade;@Temporal(TemporalType.DATE)private Date dataNascimento;private Genero genero;. . .

}

Tabela criada:PESSOA(ID, PRIMEIRONOME, ULTIMONOME, IDADE, DATANASCIMENTO, GENERO)

1 José Manuel 0 1995-01-09 0

public enum Genero {MASCULINO, FEMININO

}

Anotações mínimas necessárias

16

public static void main(String[] args) {

EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAPessoaPU");

EntityManager em = emf.createEntityManager();

Date dataNascimento = new GregorianCalendar(1995, Calendar.JANUARY, 9).getTime();

Pessoa p1 = new Pessoa("José", "Manuel", dataNascimento, Genero.MASCULINO);

em.getTransaction().begin();

em.persist(p1);

em.getTransaction().commit();

System.out.println("ID gerado: " + p1.getId());

em.close();

emf.close();

}

Tabela criada:PESSOA(ID, PRIMEIRONOME, ULTIMONOME, IDADE, DATANASCIMENTO, GENERO)

1 José Manuel 0 1995-01-09 0

public enum Genero {MASCULINO, FEMININO

}

17

Mapeamento entre tipos de dados Java e tipos de dados SQL

Os tipos de dados seguintes são mapeados automaticamente pelo JPA (sem necessidade de anotações):

• byte, short, int, long, float, double, char, boolean

• Byte, Short, Integer, Float, Long, Double, Character, Boolean

• String, Number, BigInteger, BigDecimal

• byte[], char[], Byte[], Character[]

• Collection, Set, List, Map

• java.sql.Date, java.sql.Time, java.sql.Timestamp

• Enum

• Objetos Embeddable

Para os tipos de dados java.util.Date e java.util.Calendar temos de usar a anotação @Temporal para especificar o tipo de dados da base de dados:• @Temporal(TemporalType.DATE) – equivalente a java.sql.Date

• @Temporal(TemporalType.TIME)– equivalente a java.sql.Time

• @Temporal(TemporalType.TIMESTAMP) – equivalente a java.sql.Timestamp

Por omissão, JPA assume que todos os campos de dados de uma entidade são persistentes. Para que JPA não persista um campo de dados ou propriedade temos de usar a anotação @Transient

Por omissão, JPA guarda os atributos do tipo enum como inteiros. Para guardar como valores String temos de usar a anotação @Enumerated(EnumType.String).

18

@Entity@Table (name="T_Pessoa")public class Pessoa {

@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column (name="P_Nome", length=50)private String primeiroNome;@Column (name="U_Nome", nullable=false)private String ultimoNome;@Transientprivate int idade;@Temporal(TemporalType.DATE)private Date dataNascimento;@Enumerated(EnumType.STRING)private Genero genero;

. . . }

public enum Genero {MASCULINO, FEMININO

}

Tabela criada:T_PESSOA(ID, P_NOME, U_NOME, DATANASCIMENTO, GENERO)

1 José Manuel 1995-01-09 MASCULINO

Anotações para configurar mapeamento

19

Classe Java quando se usa um ficheiro de mapeamento xml

public class Pessoa {private Long id;private String primeiroNome;private String ultimoNome;private String morada;private int idade;private Date dataNascimento;private Genero genero;

private Pessoa() { }

public Pessoa(String primeiroNome, String ultimoNome, String morada, Date dataNascimento, Genero genero) {

this.primeiroNome = primeiroNome;this.ultimoNome = ultimoNome;this.morada = morada;this.dataNascimento = dataNascimento;this.genero = genero;

}

. . .}

20

<?xml version="1.0" encoding="UTF-8"?><entity-mappings xmlns=http://www.eclipse.org/eclipselink/xsds/persistence/orm xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation=http://www.eclipse.org/eclipselink/xsds/persistence/orm http://www.eclipse.org/eclipselink/xsds/eclipselink_orm_2_1.xsd version="2.1">

<entity class="model.Pessoa"><table name="T_Pessoa"/><attributes>

<id name="id"><generated-value strategy="IDENTITY"/>

</id><basic name="primeiroNome">

<column name="P_Nome" length="50"/> </basic><basic name="ultimoNome">

<column name="U_Nome" nullable="false"/> </basic> <transient name="idade"/><basic name="dataNascimento">

<temporal>DATE</temporal> </basic><basic name="genero">

<enumerated>STRING</enumerated> </basic>

</attributes> </entity>

</entity-mappings>

Tabela criada (igual à versão anterior com anotações):T_PESSOA(ID, P_NOME, U_NOME, DATANASCIMENTO, GENERO)

1 José Manuel 1995-01-09 MASCULINO

21

Associações

Em Java temos associações entre classes enquanto que numa base de dados temos referências entre tabelas.

Referências entre tabelas podem ser modeladas de dois modos:• usando uma Join Column• usando uma Join Table

O mapeamento efetuado pelo JPA por omissão (na ausência de anotações e de ficheiro de mapeamento xml) é o seguinte:

Associação SGBDR

OneToOne => Join ColumnManyToOne => Join ColumnOneToMany => Join TableManyToMany => Join Table

22

Um-para-um unidirecional – com coluna de junção

Exemplo: Associação unidirecional de País para Cidade. Um objeto País tem uma referência para a sua capital (objeto da classe Cidade). Por omissão, JPA cria uma coluna de junção na tabela País.

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;private Cidade capital;. . .

}

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;. . .

}

Tabelas criadas:

PAÍS(ID, NOME, CAPITAL_ID)

CIDADE(ID, NOME)

O nome da coluna de junção é CAPITAL_ID que é a concatenação do nome do atributo da entidade referenciada, capital, com sublinhado, com o nome da coluna chave primária da entidade referenciada (Cidade), id.

23

Um-para-um unidirecional – com coluna de junção

Se quisermos dar um nome diferente à coluna de junção temos de usar a anotação JoinColumn.Também uma relação um-para-um tem, por omissão, uma política de fetching eager.Para mudar a política de fetching para lazy temos de usara a anotação OneToOne.

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;@OneToOne(fetch = FetchType.LAZY)@JoinColumn(name = "CAPITAL")private Cidade capital;. . .

}

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;. . .

}

Tabelas criadas:

País(ID, NOME, CAPITAL)

Cidade(ID, NOME)

24

Um-para-um unidirecional – com tabela de junção

Se quisermos ter na base de dados uma tabela de junção em vez de uma coluna de junção (situação menos interessante) temos de usar a anotação JoinTable. JPA criará uma tabela usando valores por omissão para o nome da tabela e das duas colunas de junção: PAIS_CIDADE(PAIS_ID, CAPITAL_ID).Se quisermos mudar esses nomes temos de os especificar na anotação JoinTable.

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;@OneToOne(fetch = FetchType.LAZY)@JoinTable(name = "PAIS_CAPITAL",

joinColumns = @JoinColumn(name = "País_fk"), inverseJoinColumns = @JoinColumn(name ="Cidade_fk"))

private Cidade capital;. . .

}Tabelas criadas:

País(ID, NOME)

Cidade(ID, NOME)

PAIS_CAPITAL(PAIS_FK, CIDADE_FK)

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;. . .

}

25

Um-para-um bidirecional – com duas coluna de junção

Exemplo: Relação bidirecional entre País e Cidade.Uma associação bidirecional é aquela em que há uma referência em ambos os lados.Problema: como manter os dois lados da associação consistentes, quando se fazem alterações. É da responsabilidade do programador assegurar essa consistência.

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;private Cidade capital;. . .

}

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;private País país;. . .

}

Tabelas criadas:

País(ID, NOME, CAPITAL_ID)

Cidade(ID, NOME, PAÍS_ID)

26

Um-para-um bidirecional – com apenas uma coluna de junção

Em JPA um dos lados designa-se lado primário ou controlador e o outro lado secundário ou mapeado. O framework garante manter consistência se alterámos o lado primário.Se apenas alterámos o lado inverso da associação não há garantia de manter a consistência.Numa associação bidirecional o lado secundário da relação deve usar o elemento mappedBy.

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;private Cidade capital;. . .

}

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;@OneToOne(mappedBy = "capital")private País país;. . .

}

Tabelas criadas:

País(ID, NOME, CAPITAL_ID)

Cidade(ID, NOME)

O lado não proprietário da associação deve usar o elemento mappedBy da respetiva anotação (OneToOne) para especificar o atributo ou propriedade do lado proprietário da associação.

27

Um-para-um bidirecional – alteração do nome da coluna de junção

Para alterar o nome da coluna de junção temos de usara a anotação JoinColumn

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;@JoinColumn(name="CAPITAL")private Cidade capital;. . .

}

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;@OneToOne(mappedBy = "capital")private País país;. . .

}

Tabelas criadas:

País(ID, NOME, CAPITAL)

Cidade(ID, NOME)

28

Muitos-para-um unidirecional - JPA cria uma coluna de junção

Exemplo: Associação unidirecional entre Cidade e País.Um objeto Cidade tem uma referência para o objeto País a que pertence.

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;. . .

}

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;private País país;. . .

}

Tabelas criadas:

PAÍS (ID, NOME)

CIDADE (ID, NOME, PAÍS_ID)

29

Um-para-muitos unidirecional - JPA cria uma tabela de junção.

Exemplo: Associação unidirecional entre País e Cidade.Um objeto País tem uma referência para uma coleção de objetos Cidade.

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;private Set<Cidade> cidades = new HashSet<>();. . .

}

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;. . .

}

Tabelas criadas:

PAÍS (ID, NOME)

CIDADE (ID, NOME)

PAÍS_CIDADE(PAÍS_ID, CIDADES_ID)

30

Um-para-muitos unidirecional – com coluna de junção

Se pretendermos mapear esta associação através de uma coluna de junção em vez de uma tabela de junção temos apenas de acrescentar a anotação @JoinColumn.A coluna de junção é criada na tabela Cidade.

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;@JoinColumnprivate Set<Cidade> cidades = new HashSet<>();. . .

}

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;. . .

}

Tabelas criadas:

PAÍS (ID, NOME)

CIDADE (ID, NOME, CIDADES_ID)

31

Um-para-muitos unidirecional – alteração do nome da coluna de junção

A coluna de junção fica na tabela Cidade e contém uma chave estrangeira para o País a que pertence. Um nome mais adequado para esta coluna será País, o qual pode ser definido na anotação @JoinColumn.

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;@JoinColumn(name="País")private Set<Cidade> cidades = new HashSet<>();. . .

}

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;. . .

}

Tabelas criadas:

PAÍS (ID, NOME)

CIDADE (ID, NOME, PAÍS)

32

Muitos-para-muitos bidirecional - JPA cria duas tabelas de junção.

Associação bidirecional entre Aluno e Disciplina.Um Aluno tem uma referência para uma coleção de objetos Disciplina.Uma Disciplina tem uma referência para uma coleção de objetos Aluno.

@Entitypublic class Aluno {

@Id @GeneratedValueprivate Long id;private String nome;private Set<Disciplina> disciplinas = new HashSet<>();. . .

}

@Entitypublic class Disciplina {

@Id @GeneratedValueprivate Long id;private String nome;private Set<Aluno> alunos = new HashSet<>();. . .

}

Tabelas criadas (4 tabelas!):

ALUNO (ID, NOME)

ALUNO_DISCIPLINA (ALUNO_ID, DISCIPLINAS_ID)

DSICIPLINA (ID, NOME)

DISCIPLINA_ALUNO (DISCIPLINA_ID, ALUNOS_ID)

33

Muitos-para-muitos bidirecional – apenas uma tabela de junção.

Devemos usar o elemento mappeBy da anotação ManyToMany no lado não proprietário da associação para especificar o atributo ou propriedade do lado proprietário da associação.Consideremos Aluno o lado proprietário da associação.

@Entitypublic class Aluno {

@Id @GeneratedValueprivate Long id;private String nome;private Set<Disciplina> disciplinas = new HashSet<>();. . .

}

@Entitypublic class Disciplina {

@Id @GeneratedValueprivate Long id;private String nome;@ManyToMany(mappedBy = "disciplinas")private Set<Aluno> alunos = new HashSet<>();. . .

}

Tabelas criadas:

ALUNO (ID, NOME)

ALUNO_DISCIPLINA (ALUNOS_ID, DISCIPLINAS_ID)

DSICIPLINA (ID, NOME)

34

Associações bidirecionais

Numa associação bidirecional um-para-um, muitos-para-um, um-para-muitos, ou muitos-para-muitos, o lado não proprietário da associação deve usar o elemento mappedBy da respetiva anotação (OneToOne, ManyToOne, OneToMany, ManyToMany) para especificar o atributo ou propriedade do lado proprietário da associação.

Lado proprietário de uma associação

Consideremos a associação entre Departamento e Empregado.Em Java (OO) um departamento tem vários empregados, enquanto que um empregado pertence a um departamento.Mas em sql, um registo contém um apontador para outro. Como há 1 departamento para N empregados, cada empregado contém uma chave estrangeira para o departamento a que pertence. Na base de dados esta é a “ligação”, o que significa que empregado é o proprietário da associação, porque cada registo empregado contém a ligação para um registo departamento.

Outro exemplo: consideremos a associação entre Cliente e Encomenda.Em Java (OO) um cliente possui várias encomendas, enquanto que uma encomenda pertence a um cliente.Em sql há 1 cliente para N encomendas, cada encomenda contém uma chave estrangeira para o cliente a que pertence. Na base de dados encomenda é proprietária da relação, porque cada registo encomenda contém a ligação para um registo cliente.

O lado proprietário de uma associação é o lado "muitos " dessa associação.

35

Associações bidirecionais

As associações bidirecionais devem ser mantidas consistentes em memória.Consideremos uma associação bidirecional entre duas entidades, Pessoa e Telefone, e que estamos interessados em navegar da instância Telefone para a instância Pessoa e vice-versa.

@Entitypublic class Pessoa {

@Id @GeneratedValueprivate int id;private String nome;@OneToMany(mappedBy = "dono")private List<Telefone> telefones;...

}

@Entitypublic class Telefone {

@Id @GeneratedValueprivate int id;private String numTelefone;private Pessoa dono;...

}

Tabelas criadas:

PESSOA (ID, NOME)

TELEFONE (ID, NUMTELEFONE, DONO_ID)

36

Como a associação é bidirecional, sempre que a aplicação atualiza um lado da associação, o outro lado também deve ser atualizado. É da responsabilidade da aplicação manter a associação consistente.

O modelo de objetos através de métodos set ou add deve tratar ambos os lados da relação.Mas há duas alternativas:

1. Só adicionar código de manutenção a um lado da relação e só usar esse código para manter a relação;

2. Adicionar código de manutenção a ambos os lados, evitando um ciclo infinito.

public class Pessoa { private List<Telefone> telefones = new ArrayList<>(); ... public void addTelefone(Telefone telefone) {

telefones.add(telefone); if (telefone.getDono() != this) {

telefone.setDono(this); }

} ...

}

public class Telefone { private Pessoa dono; ... public void setDono(Pessoa pessoa) {

dono = pessoa; if (!pessoa.getTelefones().contains(this)) {

pessoa.getTelefones().add(this); }

} ...

}

37

Por omissão JPA usa as seguintes políticas de fetching:• Campos de dados ou propriedades: Eager• Associações um-para-um e muitos-para-um: Eager• Associações um-para-muitos e muitos-para-muitos: Lazy

Exemplo de fetching numa associação um-para-muitos: Lazy

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;@JoinColumnprivate Set<Cidade> cidades = new HashSet<>();. . .

}

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;

. . .}

Tabelas criadas:

PAÍS (ID, NOME)1, Portugal

CIDADE (ID, NOME, CIDADES_ID)1, Porto, 12, Lisboa, 13, Braga, 1

País p1 = em.find(País.class, 1L);System.out.println("Atributo cidades=" + p1.getCidades());String listaCidades = "";for (Cidade cidade : p1.getCidades()) { listaCidades += " " + cidade.getNome(); }System.out.println("Atributo cidades=" + p1.getCidades());

Saída produzida:Atributo cidades={IndirectSet: not instantiated}Atributo cidades={[Porto, Lisboa, Braga]}

38

Associação um-para-muitos: alteração do fetching para eager

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;@OneToMany(fetch = FetchType.EAGER)@JoinColumnprivate Set<Cidade> cidades = new HashSet<>();. . .

}

@Entitypublic class Cidade {

@Id @GeneratedValueprivate Long id;private String nome;

. . .}

Tabelas criadas:

PAÍS (ID, NOME)1, Portugal

CIDADE (ID, NOME, CIDADES_ID)1, Porto, 12, Lisboa, 13, Braga, 1

País p1 = em.find(País.class, 1L);System.out.println("Atributo cidades=" + p1.getCidades());String listaCidades = "";for (Cidade cidade : p1.getCidades()) { listaCidades += " " + cidade.getNome(); }System.out.println("Atributo cidades=" + p1.getCidades());

Saída produzida:Atributo cidades=[Porto, Braga, Lisboa]Atributo cidades=[Porto, Braga, Lisboa]

39

• Cascade especifica operações em associações: All, PERSIST, MERGE, REMOVE, REFRESH• Por omissão JPA não efetua operações em cascata

Exemplo de Cascade numa associação um-para-muitos

No programa anteriormente apresentado relativo à associação um-para-muitos entre a entidade País e a entidade Cidade, a execução do método main seguinte lança uma exceção.

public static void main(String[] args) {EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAPU");EntityManager em = emf.createEntityManager();País p1 = new País("Portugal");Cidade c1 = new Cidade("Lisboa"); Cidade c2 = new Cidade("Porto"); Cidade c3 = new Cidade("Braga");p1.addCidade(c1); p1.addCidade(c2); p1.addCidade(c3);em.getTransaction().begin();

// em.persist(c1);// em.persist(c2);// em.persist(c3);

em.persist(p1);em.getTransaction().commit();System.out.println("ID gerado: " + p1.getId()); em.close();emf.close();

}

Exception in thread "main" javax.persistence.RollbackException: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: Cidade[id=null].

at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:157)

40

Associação um-para-muitos: alteração do cascade para CascadeType.PERSIST

@Entitypublic class País {

@Id @GeneratedValueprivate Long id;private String nome;@OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.PERSIST)@JoinColumnprivate Set<Cidade> cidades = new HashSet<>();. . .

}

A execução do método main resulta na execução dos seguintes comandos sql:

CREATE TABLE PAÍS (ID BIGINT IDENTITY NOT NULL, NOME VARCHAR, PRIMARY KEY (ID))CREATE TABLE CIDADE (ID BIGINT IDENTITY NOT NULL, NOME VARCHAR, CIDADES_ID BIGINT, PRIMARY KEY (ID))ALTER TABLE CIDADE ADD CONSTRAINT FK_CIDADE_CIDADES_ID FOREIGN KEY (CIDADES_ID) REFERENCES PAÍS (ID)INSERT INTO PAÍS (NOME) VALUES (?) bind => [Portugal]INSERT INTO CIDADE (NOME) VALUES (?) bind => [Porto]INSERT INTO CIDADE (NOME) VALUES (?) bind => [Braga]INSERT INTO CIDADE (NOME) VALUES (?) bind => [Lisboa]UPDATE CIDADE SET CIDADES_ID = ? WHERE (ID = ?) bind => [1, 3]UPDATE CIDADE SET CIDADES_ID = ? WHERE (ID = ?) bind => [1, 2]UPDATE CIDADE SET CIDADES_ID = ? WHERE (ID = ?) bind => [1, 1]

41

O mapeamento efetuado pelo JPA por omissão (na ausência de anotações e de ficheiro de mapeamento xml) é o seguinte:

Associação SGBDR Fetching Cascade

OneToOneJoin Column Eager

NoneManyToOne

OneToManyJoin Table Lazy

ManyToMany

42

Objetos Embeddable:• Úteis para representar classes não-entidade• Os campos de dados são embebidos dentro da tabela da entidade a que pertencem• Objetos Embeddable não podem ser persistidos sozinhos• Suportam o conceito de Value Object de Domain Driven Design

Exemplo:Uma Pessoa tem um Telefone

@Entitypublic class Pessoa {

@Id @GeneratedValueprivate Long id;private String nome;private Telefone telefone;. . .

}

@Embeddablepublic class Telefone {

private String numero;private String indicativoPaís;. . .

}

public static void main(String[] args) {EntityManagerFactory emf =

Persistence.createEntityManagerFactory("JPAPU");EntityManager em = emf.createEntityManager();Pessoa p1 = new Pessoa("Manuel");Pessoa p2 = new Pessoa("José");Telefone t1 = new Telefone("123456789", "+351");Telefone t2 = new Telefone("234234234", null);p1.setTelefone(t1); p2.setTelefone(t2);em.getTransaction().begin();em.persist(p1); em.persist(p2);em.getTransaction().commit(); em.close();emf.close();

}

Tabela criada:

PESSOA(ID, NOME, INDICATIVOPAÍS, NUMERO)1, Manuel, +351, 1234567892, José, <NULL>, 234234234

Anotação @Embedded no atributo telefone não é necessária.

43

Exemplo: Uma Pessoa tem vários objetos Telefone

A anotação ElementCollection no atributo telefones é necessária porque o objeto embedded é uma coleção.

@Entitypublic class Pessoa {

@Id @GeneratedValueprivate Long id;private String nome;@ElementCollectionprivate Set<Telefone> telefones = new HashSet<>();. . .

}

@Embeddablepublic class Telefone {

private String numero;private String indicativoPaís;. . .

}

public static void main(String[] args) {EntityManagerFactory emf =

Persistence.createEntityManagerFactory("JPAPU");EntityManager em = emf.createEntityManager();Pessoa p1 = new Pessoa(“Manuel");Telefone t1 = new Telefone("123456789", "+351");Telefone t2 = new Telefone("987654321", "+49");Telefone t3 = new Telefone("234234234", null);p1.addTelefone(t1); p1.addTelefone(t2); p1.addTelefone(t3);em.getTransaction().begin();em.persist(p1);em.getTransaction().commit(); em.close();emf.close();

}

Tabelas criadas: PESSOA(ID, NOME)

1, ManuelPESSOA_TELEFONES(INDICATIVOPAÍS, NUMERO, PESSOA_ID)

+351, 123456789, 1<NULL>, 234234234, 1

+49, 987654321, 1

44

Por omissão, JPA define automaticamente o mapeamento correto para associações entre entidades não sendo necessário colocar anotação ou configuração em ficheiro de mapeamento xml.

No entanto é necessário usar anotação ou configuração xml para:

• Configurar o tipo de fetch para Lazy ou Eager, se diferente do implícito.• Configurar a entidade associada quando se usa uma coleção não definida com

genéricos através do elemento targetEntity. Quando usámos genéricos JPA infere o tipo da entidade associada que é o tipo parametrizado da coleção.

• Configurar as operações que devem ser realizadas em cascata na entidade associada.

• Nas associações bidirecionais para definir o lado inverso da associação através do elemento mappedBy.

• Nas associações unidirecionais um-para-muitos para usar uma coluna de junção em vez de tabela de junção.

45

Mapeamento da herança em JPA

Impedance mismatch é o termo usado para descrever as dificuldades em mapear o estado de um objecto num registo de uma tabela de uma base de dados relacional. Na modelação orientada a objectos, a herança é talvez a característica onde é maior a impedancemismatch, porque não há nenhum modo natural e eficiente de representar uma relação de herança numa base de dados relacional.

JPA permite escolher uma de entre 3 estratégias para a herança. Na classe da entidade base define-se a estratégia de herança para a hierarquia com a anotação Inheritance:

@Inheritance(strategy=InheritanceType.SINGLE_TABLE)@Inheritance(strategy=InheritanceType.JOINED)@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)

O valor por omissão é InheritanceType.SINGLE_TABLE.

Nas estratégias de mapeamento Single-table e Joined, na tabela correspondente à classe base é criada uma coluna discriminadora (nome por omissão “DTYPE”), tendo como valor para cada registo o nome da classe a que o registo pertence (uma única coluna para distinguir a classe a que pertence cada registo).

46

Single Table

A estratégia Single Table mapeia todas as classes da hierarquia na tabela da classe base.A anotação que define a estratégia de mapeamento só se coloca na superclasse, mas como a estratégia por omissão é single table, para usar esta estratégia podemos omitir a anotação @Inheritance.

Na tabela de mapeamento é criada uma coluna para cada campo de dados de cada classe da hierarquia. Quanto maior for a hierarquia (larga ou profunda), mais larga será a tabela mapeada, o que poderá ter consequências no tamanho da base de dados com muitas colunas bastante vazias. No entanto o mapeamento single table é o mais rápido de entre todos os modelos de mapeamento de herança porque para retribuir uma instância nunca necessita de efetuar um join e para persistir ou atualizar uma instância necessita apenas de um insert ou um update.

Esta opção fornece o melhor suporte quer para relações polimórficas entre entidades, quer para queries abrangendo toda a hierarquia.

Este mapeamento em que todos os objetos da hierarquia são mapeados na tabela da superclasse designa-se por mapeamento da hierarquia plano.

47

Exemplo Single Table

@Entitypublic abstract class Movement implements Serializable {

@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String description;private BigDecimal amount;@Temporal(TemporalType.DATE)private Date dateOccurred;. . .

}

@Entitypublic class Expense extends Movement {

private String expenseType;. . .

}

@Entitypublic class Income extends Movement {

private String incomeType;. . .

}

ID DTYPE AMOUNT DATEOCCURRED DESCRIPTION EXPENSETYPE INCOMETYPE

1 Expense 66 2015-03-05 camisa Roupa <NULL>

2 Expense 150 2015-02-15 calças Roupa <NULL>

3 Expense 35 2015-03-01 passe metro Transportes <NULL>

4 Income 200 2015-04-01 venc março <NULL> Vencimento

Tabela MOVEMENT:

48

Exemplo Single TableCaso particular: campos de dados das classes Expense e Income com nomes iguais

@Entitypublic abstract class Movement implements Serializable {

@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String description;private BigDecimal amount;@Temporal(TemporalType.DATE)private Date dateOccurred;. . .

}

@Entitypublic class Expense extends Movement {

private String type;. . .

}

@Entitypublic class Income extends Movement {

private String type;. . .

}

ID DTYPE AMOUNT DATEOCCURRED DESCRIPTION TYPE

1 Expense 66 2015-03-05 camisa Roupa

2 Expense 150 2015-02-15 calças Roupa

3 Expense 35 2015-03-01 passe metro Transportes

4 Income 200 2015-04-01 venc março Vencimento

Tabela MOVEMENT:

49

Joined

A estratégia Joined usa uma tabela diferente para cada classe da hierarquia.Cada tabela só inclui o estado declarado na própria classe.A raiz da hierarquia de classes é representada por uma única tabela e cada subclasse é representada por uma tabela separada.

Cada tabela de uma subclasse contém apenas os campos definidos na subclasse (não contém os herdados das superclasses) e colunas chaves primárias que servem como chaves estrangeiras para as chaves primárias da tabela da superclasse.

Assim para retribuir uma instância de uma subclasse, JPA tem de ler da tabela da subclasse assim como das tabelas das superclasses até à classe entidade base.

O mapeamento Joined é o mais lento de entre todos os modelos de mapeamento de herança porque para retribuir uma instância necessita de efetuar um ou mais joins e para persistir ou atualizar uma instância de uma subclasse necessita de vários inserts ou updates.No entanto resulta no esquema de base de dados mais normalizado de entre todos os modelos de mapeamento de herança.

Este mapeamento designa-se por mapeamento da hierarquia vertical.

50

Exemplo Joined

@Entity@inheritance(strategy=InheritanceType.JOINED)public abstract class Movement implements Serializable {

@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String description;private BigDecimal amount;@Temporal(TemporalType.DATE)private Date dateOccurred;. . .

}

@Entitypublic class Expense extends Movement {

private String expenseType;. . .

}

@Entitypublic class Income extends Movement {

private String incomeType;. . .

}

ID DTYPE AMOUNT DATEOCCURRED DESCRIPTION

1 Expense 66 2015-03-05 camisa

2 Expense 150 2015-02-15 calças

3 Expense 35 2015-03-01 passe metro

4 Income 200 2015-04-01 venc março

Tabela MOVEMENT:

ID ExPENSETYPE

1 Roupa

2 Roupa

3 Transportes

ID INCOMETYPE

4 Vencimento

Tabela EXPENSE: Tabela INCOME:

51

Table Per Class

A estratégia table-per-class usa uma tabela diferente para cada classe concreta da hierarquia, de um modo semelhante à estratégia joined.Mas, ao contrário da estratégia joined, cada tabela inclui todo o estado de uma instância da correspondente classe.Todas as propriedades das instâncias de uma classe, incluindo propriedades herdadas, são mapeadas para colunas da tabela correspondente a essa classe.

Assim para retribuir uma instância de uma subclasse, JPA tem de ler apenas da tabela da subclasse. Não necessita de efetuar join com as tabelas das superclasses.

Esta estratégia table-per-class é muito eficiente para todas as operações sobre instâncias de classes conhecidas, isto é na ausência de comportamento polimórfico.Relações polimórficas têm muitas limitações.

52

Exemplo Table-Per-Class

@Entity@inheritance(strategy=InheritanceType.TABLE_PER_CLASS)public abstract class Movement implements Serializable {

@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String description;private BigDecimal amount;@Temporal(TemporalType.DATE)private Date dateOccurred;. . .

}

@Entitypublic class Expense extends Movement {

private String expenseType;. . .

}

@Entitypublic class Income extends Movement {

private String incomeType;. . .

}

ID AMOUNT DATEOCCURRED DESCRIPTION EXPENSETYPE

1 66 2015-03-05 camisa Roupa

2 150 2015-02-15 calças Roupa

3 35 2015-03-01 passe metro Transportes

Tabela EXPENSE: Tabela INCOME:

ID AMOUNT DATEOCCURRED DESCRIPTION INCOMETYPE

1 200 2015-04-01 venc março Vencimento

Quando uma instância de uma entidade é inicialmente criada o seu estado é New. O estado da instância ainda não está associado com um EntityManager nem tem representação na base de dados.A instância passa ao estado Managed quando é persistida na base de dados através do método persistdo EntityManager, o qual deve ser invocado dentro de uma transação. No commit da transação o objeto é gravado na base de dados. Também objetos retribuídos da base de dados pelo EntityManagerficam no estado Managed. Se um objeto no estado Managed é modificado dentro de uma transação, é marcado pelo EntityManager como dirty, para ser atualizado na base de dados no commit da transação.Um objeto Managed pode passar para o estado Detached através do método detach do

EntityManager, ficando desligado do EntityManager. Também fica no estado Detached quando

o EntityManger é fechado.

Um objeto Managed passa para o estado Removed através do método remove do EntityManager, sendo apagado da base de dados no commit da transação.

53

O ciclo de vida das entidades tem 4 estados: New, Managed, Detached, e Removed.

O ciclo de vida das entidades tem 4 estados: New, Managed, Detached, e Removed.

Consideremos a seguinte entidade:

@Entitypublic class Pessoa {

@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private int id;private String nome;private String morada;

private Pessoa() {}

public Pessoa(String nome, String morada) {this.nome = nome;this.morada = morada;

}

public int getId() {return id;

}

public void alterarMorada(String morada) {this.morada = morada;

}}

54

55

1. Estado Transient ou New Objeto não tem id nem representação na base de dados

Quando um objeto entidade é criado o seu estado é Transient ou New. Neste estado o objeto existe em memória mas não está associado com um EntityManager nem tem representação na base de dados.

public static void main(String[] args) {EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAPessoaPU");EntityManager em = emf.createEntityManager();Pessoa p1 = new Pessoa("José Manuel", "Porto");em.getTransaction().begin();p1.alterarMorada("Vila Nova de Gaia");em.getTransaction().commit();System.out.println("ID gerado: " + p1.getId()); em.close();emf.close();

}

Tabela criada vazia: PESSOA(ID, MORADA , NOME)Saída produzida pelo programa: ID gerado: null

A entidade Pessoa p1 não possui Id, nem tem representação na base de dados.

SELECT ID FROM PESSOA WHERE ID <> IDCREATE TABLE PESSOA (ID INTEGER IDENTITY NOT NULL, MORADA VARCHAR, NOME VARCHAR, PRIMARY KEY (ID))

56

2. Estado Managed (persist)

Objeto tem id e representação na base de dados

Um objeto entidade passa para o estado Managed quando é persistido na base de dados (através do método persist de um EntityManger) ou quando é carregado da base de dados (através do método find de um EntityManger, ou da execução de uma query).

public static void main(String[] args) {EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAPessoaPU");EntityManager em = emf.createEntityManager();Pessoa p1 = new Pessoa("José Manuel", "Porto");em.getTransaction().begin();em.persist(p1);p1.alterarMorada("Vila Nova de Gaia");em.getTransaction().commit();System.out.println("ID gerado: " + p1.getId());em.close();emf.close();

}

SELECT ID FROM PESSOA WHERE ID <> IDINSERT INTO PESSOA (MORADA, NOME) VALUES (?, ?)bind => [Vila Nova de Gaia, José Manuel]CALL IDENTITY()

Saída produzida pelo programa: ID gerado: 1

A entidade Pessoa p1 é persistida na base de dados apenas quando é executado o commit, e passa a ter Id.

ID MORADA NOME

1 Vila Nova de Gaia José Manuel

57

3. Estado Managed (find)

No estado managed as entidades têm uma identidade persistente, uma chave que identifica univocamente cada instância. Se uma entidade managed é modificada dentro de uma transação, é marcada pelo EntityManager como dirty, e as modificações são atualizadas na base de dados no commit da transação.

public static void main(String[] args) {EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAPessoaPU");EntityManager em = emf.createEntityManager();em.getTransaction().begin();Pessoa p1 = em.find(Pessoa.class, 1);p1.alterarMorada("Matosinhos");em.getTransaction().commit();System.out.println("ID gerado: " + p1.getId()); em.close();emf.close();

}

SELECT ID FROM PESSOA WHERE ID <> IDSELECT ID, MORADA, NOME FROM PESSOA WHERE (ID = ?)bind => [1]UPDATE PESSOA SET MORADA = ? WHERE (ID = ?)bind => [Matosinhos, 1]

Qualquer alteração no estado do objeto é persistida na base de dados

ID MORADA NOME

1 Matosinhos José Manuel

58

4. Estado Detached (detach)

Para evitar a execução de updates quando efetuamos várias alterações, não seguidas, ao estado de um objeto, podemos passar a entidade para o estado detached (não gerido). No fim das alterações devemos passar a entidade para o estado managed usando o método merge.

public static void main(String[] args) {EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAPessoaPU");EntityManager em = emf.createEntityManager();em.getTransaction().begin();Pessoa p1 = em.find(Pessoa.class, 1);em.detach(p1);p1.alterarMorada("Gondomar");em.getTransaction().commit();System.out.println("ID gerado: " + p1.getId()); em.close();emf.close();

}

SELECT ID FROM PESSOA WHERE ID <> IDSELECT ID, MORADA, NOME FROM PESSOA WHERE (ID = ?)bind => [1]

ID MORADA NOME

1 Matosinhos José Manuel

A alteração de morada não é persistida na base de dados

59

5. Estado Managed (merge) – funcionamento incorreto

O método merge leva uma instância de uma entidade (i1) como parâmetro, cria uma nova instância dessa entidade (i2) com os valores da base de dados, coloca a instância criada (i2) no estado managed, copia o estado da entidade fornecida (i1) para a instância criada (i2), e retorna-a.

public static void main(String[] args) {EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAPessoaPU");EntityManager em = emf.createEntityManager();em.getTransaction().begin();Pessoa p1 = em.find(Pessoa.class, 1);em.detach(p1);em.merge(p1);p1.alterarMorada("Gondomar");em.getTransaction().commit();System.out.println("ID gerado: " + p1.getId()); em.close();emf.close();

}

SELECT ID FROM PESSOA WHERE ID <> IDSELECT ID, MORADA, NOME FROM PESSOA WHERE (ID = ?)bind => [1]

ID MORADA NOME

1 Matosinhos José Manuel

O objeto criado pelo método merge é retornado pelo mesmo método merge, mas neste programa não é aproveitado.Após o merge a entidade p1 continua no estado detached e por isso alterações no seu estado não são persistidas na base de dados.

60

6. Estado Managed (merge) – funcionamento correto

A entidade criada pelo método merge é colocada no estado managed, em seguida o seu estado é alterado sendo marcada como dirty, e as modificações são atualizadas na base de dados no commit da transação.

public static void main(String[] args) {EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAPessoaPU");EntityManager em = emf.createEntityManager();em.getTransaction().begin();Pessoa p1 = em.find(Pessoa.class, 1);em.detach(p1);p1 = em.merge(p1);p1.alterarMorada("Gondomar");em.getTransaction().commit();System.out.println("ID gerado: " + p1.getId()); em.close();emf.close();

}

SELECT ID FROM PESSOA WHERE ID <> IDSELECT ID, MORADA, NOME FROM PESSOA WHERE (ID = ?)bind => [1]UPDATE PESSOA SET MORADA = ? WHERE (ID = ?)bind => [Gondomar, 1]

ID MORADA NOME

1 Gondomar José Manuel

Após o merge p1 passa a referenciar a instância criada e colocada no estado managed.Assim alterações no seu estado são persistidas na base de dados.

61

O Persistence Context é a coleção de entidades geridas pelo Entity Manager. Ao carregar um objeto que já exista no Persistence Context, o objeto existente é retornado sem aceder à base de dados, exceto se é pedido pelo método refresh, o qual acede sempre à base de dados.