Skip to content

Latest commit

 

History

History
623 lines (429 loc) · 17 KB

README.adoc

File metadata and controls

623 lines (429 loc) · 17 KB

Programação para Web II

Fagno Alves Fonseca <[email protected]> Mestre em Modelagem Computacional de Sistemas – UFT.

1. Java Persistence API e Frameworks ORM

A Java Persistence API (JPA) é uma especificação para persistência de dados em Java, que oferece uma API de mapeamento objeto-relacional e soluções para integrar persistência em sistemas corporativos. Ela foi criada com o intuito de ter uma padronização no desenvolvimento de soluções com ORM em Java.

Mapeamento objeto relacional (object-relational mapping, ORM) é uma técnica de programação para conversão de dados entre banco de dados relacionais e linguagens de programação orientada a objetos.

1.1. Hibernate

Em nossos projetos estaremos utilizando a JPA com Hibernate. O Hibernate é um framework ORM para persistência de dados, sendo uma das referências entre os desenvolvedores.

1.2. Criando um projeto web e definindo o arquivo persistence.xml

No vídeo é apresentado como criar um novo projeto web com maven e também como criar o arquivo persistence.xml que é detalhado no tópico 1.5.

1.3. Dependências

Após criar o projeto web com maven, adicione ao arquivo pom.xml as dependências necessárias do Mysql e Hibernate.

Dependências do Mysql e Hibernate
<dependency>
    <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
</dependency>

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>4.3.1.Final</version>
</dependency>

1.4. Mapeamento básico

Para efetuar o mapeamento objeto/relacional de uma classe, precisamos inserir a anotação @Entity em uma classe.

Criando uma entidade
import javax.persistence.Entity;

@Entity
public class Pessoa{
}

A anotação @Entity diz que a classe é uma entidade, que representa uma tabela do banco de dados.

Precisamos inserir também as anotações @Id, que declara o identificador no banco de dados e a anotação @GeneratedValue, que determina esse identificador sendo do tipo auto-incremento, neste caso, o atributo 'id'.

Entidade Pessoa
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "tb_pessoa")
public class Pessoa implements Serializable{

    @Id
    @GeneratedValue
    private int id;
    private String nome;
    private int idade;

    //getters e setters

}

A classe possui a anotação @Table(name = "tb_pessoa"), que define o nome da tabela ao persistir os dados no banco. Caso você não utilize a anotação a tabela terá o mesmo nome da classe, ou seja, 'Pessoa'.

Em JPA é uma boa pratica sempre implementar a interface Serializable. Quando o objeto é serializado, todas as variáveis de instância referentes a classe deste objeto serão serializadas. A serialização significa salvar o estado atual dos objetos em arquivos em formato binário para o seu computador, sendo assim esse estado poderá ser recuperado posteriormente recriando o objeto em memória assim como ele estava no momento da sua serialização.

1.5. O arquivo persistence.xml

O persistence.xml é um arquivo de configuração padrão da JPA. Ele deve ser criado no diretório src/main/resources/META-INF da aplicação.

Persistence.xml
link:../codigo/persistence.xml[role=include]

No arquivo 'persistence.xml' é indicado o nome da unidade de persistência e o o provider diz qual é a implementação que será usada como provedor de persistência.

As informações de conexão com o banco de dados são definidas utilizando as principais propriedades abaixo:

  • javax.persistence.jdbc.url: descrição da URL de conexão com o banco de dados

  • javax.persistence.jdbc.driver: nome completo da classe do driver JDBC

  • javax.persistence.jdbc.user: nome do usuário do banco de dados senha do usuário do banco de dados

Outras propriedades do Hibernate são inseridas no arquivos conforme descrito abaixo.

  • hibernate.dialect: dialeto a ser usado na construção de comandos SQL

  • hibernate.show_sql: informa se os comandos SQL devem ser exibidos na console (importante para debug, mas deve ser desabilitado em ambiente de produção)

  • hibernate.format_sql: indica se os comandos SQL exibidos na console devem ser formatados (facilita a compreensão, mas pode gerar textos longos na saída)

  • hibernate.hbm2ddl.auto: cria ou atualiza automaticamente a estrutura das tabelas no banco de dados.

1.6. Gerando a tabela no banco de dados

Para gerar a tabela no banco de dados, basta apenas criar um EntityManagerFactory, que todas as tabelas mapeadas serão criadas ou atualizadas.

Abaixo, é apresentado o método main() que foi definido para carregar o arquivo de persistence. O método createEntityManagerFactory() da classe Persistence retorna uma instância de EntityManagerFactory.

Gerando Tabelas
import javax.persistence.Persistence;

public class CriaTabelas {
    public static void main(String[] args) {
        Persistence.createEntityManagerFactory("aulawebii");
    }
}
Important
No parâmetro do método createEntityManagerFactory("aulawebii") foi informado o name definido no arquivo 'persistence.xml'.

2. EntityManager

Um EntityManager é responsável por gerenciar entidades no contexto de persistência. Através dos métodos dessa interface, é possível persistir, pesquisar e excluir objetos do banco de dados.

Qualquer projeto web que usa JPA necessita de apenas uma instância de EntityManagerFactory, que pode ser criada durante a inicialização da aplicação. Esta única instância será utilizada para criar um EntityManager através do método criarEntityManager().

A classe JpaUtil foi definida para complementar o exemplo anterior e substituir a classe 'CriaTabelas'.

Classe que cria um EntityManagerFactory e um EntityManager
link:../codigo/JpaUtil.java[role=include]

2.1. Definindo o DAO

Criaremos a classe PessoaDAO para acesso aos dados da entidade Pessoa. Observe que no exemplo a seguir, a classe utilizar a classe 'JpaUtil' para criar um EntityManager.

Classe PessoaDao
link:../codigo/PessoaDao.java[role=include]

Coomplemente o projeto anterior do tópico sobre Vraptor, e faça as adequações necessária para utilzação da JPA.

3. Mapeamento com associações

3.1. um-para-um

O relacionamento um-para-um, também conhecido como one-to-one. Neste relacionamento teremos os atributos das entidades relacionadas que serão persistidas na mesma tabela. Como exemplo, iremos utilizar uma classe Pessoa que tem um relacionamento One-to-One com a classe endereço.

one to one
Figura 1. @OneToOne

A implementação do exemplo acima é descrito a seguir. Na classe Pessoa, adicionamos o atributo endereco e e mapeamos com @OneToOne. Adicionamos também a anotação @JoinColumn para definir o nome da coluna que faz referência ao id da tabela tb_endereco na tabela tb_pessoa.

Por padrão, o nome da coluna é definido com o nome do atributo da associação, mais underscore, mais o nome do atributo do identificador da entidade destino caso você não utilize o @JoinColumn.

Classe Pessoa
...
@Entity
@Table(name = "tb_pessoa")
public class Pessoa{

    @Id
    @GeneratedValue
    private int id;

    private String nome;

    private int idade;

    @OneToOne
    @JoinColumn(name = "id_endereco")
    private Endereco endereco;

    //getters e setters

}
Classe Endereco
...
@Entity
@Table(name = "tb_endereco")
public class Endereco{

    @Id
    @GeneratedValue
    private int id;

    private String logradouro;

    private String bairro;

    private String cep;

    //getters e setters

}
Important
Não esqueça que precisamos de uma instância persistida de Endereço para atribuir a Pessoa.

3.1.1. Associação bidirecional

A associação do exemplo entre pessoa e endereço é unidirecional, ou seja, podemos obter o endereço a partir de uma pessoa, mas não conseguimos obter a pessoa a partir de um endereco.

one to one bi
Figura 2. @OneToOne bidirecional

Para tornar a associação um-para-um bidirecional, precisamos apenas incluir o atributo de Pessoa na classe Endereco e mapearmos com @OneToOne usando o atributo mappedBy.

Classe Endereco
...
@Entity
@Table(name = "tb_endereco")
public class Endereco{

    @Id
    @GeneratedValue
    private int id;

    private String logradouro;

    private String bairro;

    private String cep;

    @OneToOne(mappedBy="endereco")
    private Pessoa pessoa;

    //getters e setters

}
Important
O valor do mappedBy deve ser igual ao nome do atributo de endereço definido na classe Pessoa.

Em relacionamentos OneToOne, qualquer um dos lados pode ser o dominante. O mappedBy deve ser colocado na classe que não é dona do relacionamento, ou seja, o lado fraco. Neste exemplo, definimos Pessoa como classe dominante. Na tabela tb_endereco não terá id da tabela tb_pessoa.

3.2. um-para-muitos

Neste exemplo, vamos alterar nosso relacionamento para que uma pessoa tenha vários endereços e um endereço só tenha uma pessoa. A anotação @OneToMany deve ser utilizada para mapear coleções, neste caso, inserida do lado não dominante (fraco).

one to many bi
Figura 3. @OneToMany bidirecional
Classe Pessoa
...
@Entity
@Table(name = "tb_pessoa")
public class Pessoa{

    @Id
    @GeneratedValue
    private int id;

    private String nome;

    private int idade;

    @OneToMany(mappedBy = "pessoa")
    private List<Endereco> enderecos;

    //getters e setters

}
Classe Endereco
...
@Entity
@Table(name = "tb_endereco")
public class Endereco{

    @Id
    @GeneratedValue
    private int id;

    private String logradouro;

    private String bairro;

    private String cep;

    @ManyToOne
    @JoinColumn(name = "id_pessoa")
    private Pessoa pessoa;

    //getters e setters

}

3.3. muitos-para-muitos

Neste exemplo, vamos alterar nosso relacionamento para que uma pessoa tenha vários endereços e um endereço tenha várias pessoas. Usamos a anotação @ManyToMany para mapear a propriedade de coleção.

No relacionamento muitos para muitos é criada uma tabela de associação com os nomes das entidades relacionadas, separados por underscore, com duas colunas, com nomes dos identificadores gerados automaticamente.

Podemos customizar o nome da tabela de associação e das colunas com a anotação @JoinTable, conforme exeplo a seguir.

many to many
Figura 4. @OneToMany bidirecional
Classe Pessoa
...
@Entity
public class Pessoa implements Serializable{

    @Id
    @GeneratedValue
    private int id;

    private String nome;

    private int idade;

    @ManyToMany
    @JoinTable(name = "pessoas_enderecos",
    joinColumns = @JoinColumn(name = "id_pessoa"),
    inverseJoinColumns = @JoinColumn(name = "id_endereco"))
    private List<Endereco> enderecos=new ArrayList();

    //getters e setters

...

No exemplo acima, foi definido com a anotação @JoinTable o nome da tabela que será gerada e também com a anotação @JoinColumn o nome das colunas que representam os identificadores da associação entre pessoa e endereço.

Na classe endereço vamos utilizar a anotação @ManyToMany, porém apontando apenas o "mappedby" conforme a seguir.

Classe Endereco
...
@Entity
public class Endereco implements Serializable{

    @Id
    @GeneratedValue
    private int id;

    private String logradouro;

    private String bairro;

    private String cep;

    @ManyToMany(mappedBy = "enderecos")
    private List<Pessoa> pessoas=new ArrayList();

    //getters e setters

...

Um nova tabela será gerada de acordo a nomeclatura definida nas anotações e apresentada na figura a seguir.

tabela n to n
Figura 5. tabela pessoas_enderecos

4. Mapeamento com Herança

A JPA define 3 formas de se fazer o mapeamento de herança:

  • Tabela Única por Hierarquia de Classes (single table)

  • Uma tabela para cada classe da hierarquia (joined)

  • Uma tabela para cada classe concreta (table per class)

4.1. Tabela Única por Hierarquia de Classes

Essa estratégia de mapeamento define uma unica tabela para toda a hierarquia de classes. Por exemplo, considere a Classe Pessoa como Superclasse, e outras 2 classes PessoaFisica e PessoaJuridica estendendo de Pessoa, teremos uma tabela com os dados de toda a hierarquia.

Pontos importantes:

  • As classes filhas precisam aceitar valores nulos. A falta da constraint NOT NULL pode ser um problema.

A identificaçao de quem é Pessoa Física ou Jurídica é feita por um atributo chamado discriminator, ou seja, um campo que identifica PessoaFisica(PF) e PessoaJuridica (PJ).

Classe Pessoa
...
@Entity
@Table(name = "tb_pessoa")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "tipo")
public abstract class Pessoa {

    @Id
    @GeneratedValue
    private Long id;

    private String nome;

    //getters e setters

...

A estratégia é definida com a anotação @Inheritance, neste caso SINGLE_TABLE. A anotação @DiscriminatorColumn foi usada para definir o nome da coluna do discriminator e identificar se é PF ou PJ.

A seguir vamos definir a classe PessoaFisica com a anotação @DiscriminatorValue indicando o discriminador como "F".

Classe PessoaFisica
...
@Entity
@DiscriminatorValue("F")
public class PessoaFisica extends Pessoa{

    private String cpf;

    //getters e setters

...

A seguir vamos definir a classe PessoaJuridica com a anotação @DiscriminatorValue indicando o discriminador como "J".

Classe PessoaJuridica
...
@Entity
@DiscriminatorValue("J")
public class PessoaJuridica extends Pessoa{

    private String cnpj;

    //getters e setters

...

4.2. Uma tabela para cada classe da hierarquia

Essa estratégia de mapeamento define uma tabela para cada classe da hierarquia. No nosso exemplo, teremos uma tabelas para a classe Pessoa, uma tabela para PessoaFisica e uma para PessoaJuridica.

Alteramos o exemplo anterior, definir a estratégia de herança para JOINED na entidade Pessoa.

Classe Pessoa
...
@Entity
@Table(name = "tbpessoa")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Pessoa {

    @Id
    @GeneratedValue
    private Long id;

    private String nome;

    //getters e setters

...

Nas subclasses, vamos adicionar a anotação @PrimaryKeyJoinColumn para informar o nome da coluna que faz referência à superclasse.

Classe PessoaFisica
...
@Entity
@Table(name = "tb_pessoafisica")
@PrimaryKeyJoinColumn(name = "id_pessoa")
public class PessoaFisica extends Pessoa{

    private String cpf;

    //getters e setters

...
Classe PessoaJuridica
...
@Entity
@Table(name = "tb_pessoajuridica")
@PrimaryKeyJoinColumn(name = "id_pessoa")
public class PessoaJuridica extends Pessoa{

    private String cnpj;

    //getters e setters

...

4.3. Uma tabela para cada classe concreta

Essa estratégia de mapeamento define tabelas apenas para classes concretas (subclasses). Cada tabela deve possuir as propriedades da subclasse incluindo as da superclasse.

Para utilizar essa forma de mapeamento, devemos anotar a classe conforme a seguir.

Classe Pessoa
...
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Pessoa {

    @Id
    @GeneratedValue(generator = "inc")
    @GenericGenerator(name = "inc", strategy = "increment")
    private Long id;

    private String nome;

    //getters e setters

...

Tivemos que mudar a estratégia de geração de identificadores. Não podemos usar a geração automática de chaves nativa do banco de dados.

Nas subclasses, definimos conforme a seguir.

Classe PessoaFisica
...
@Entity
@Table(name = "tb_pessoafisica")
public class PessoaFisica extends Pessoa{

    private String cpf;

    //getters e setters

...
Classe PessoaJuridica
...
@Entity
@Table(name = "tb_pessoajuridica")
public class PessoaJuridica extends Pessoa{

    private String cnpj;

    //getters e setters

...