// Slide 1: Como definir uma entidade: @Entity public class Funcionario implements Serializable { @Id private int id; private String nome; @Temporal(TemporalType.DATE) private Date nascimento; public Funcionario() { } public int getId() { return id; } public void setId(int id) { this.id = id; } public void setNome(String nome) { this.nome = nome; } public String getNome() { return nome; } public void setNascimento(Date nascimento) { this.nascimento = nascimento; } public Date getNascimento() { return nascimento; } } // Slide 2: Como definir uma entidade: @Entity public class Funcionario implements Serializable { private int id; private String nome; private String nascimento; public Funcionario() { } @Id public int getId() { return id; } public void setId(int id) { this.id = id; } public void setNome(String nome) { this.nome = nome; } public String getNome() { return nome; } public void setNascimento(Date nascimento) { this.nascimento = nascimento; } @Temporal(TemporalType.DATE) public Date getNascimento() { return nascimento; } } // Slide 3: Entidades são anotadas com @Entity. Não podem ser classes do tipo "final". Não podem ter atributos persistentes "final". Deve ter um construtor sem argumentos público ou protegido, no entanto ela pode ter outros construtores além deste. Pode herdar de outras classes, sejam entidades ou não. Outras classes, sejam entidades ou não, podem herdar de entidades. Os atributos persistentes não podem ser públicos e só devem ser acessados pela própria classe. As annotations são colocadas ou apenas nos atributos, ou apenas nos getters, não se pode misturar. Se forem colocadas nos atributos, a classe não é obrigada a ter getters e setters para os atributos. Se forem colocadas nos getters, a classe é obrigada a ter getters e setters para os atributos. Se puderem ser usadas desacopladas (detached), devem implementar Serializable. // Slide 4: @Id - Define qual campo que é a chave primária. @Id private int id; @GeneratedValue - Define como que o JPA gera os valores para a chave primária. Pode ser de quatro tipos: AUTO (o default), IDENTITY, SEQUENCE e TABLE. @Id @GeneratedValue private int id; // Slide 5: @Id @GeneratedValue(strategy=GenerationType.AUTO) private int id; strategy=GenerationType.AUTO - O JPA tenta descobrir qual é a melhor estratégia. // Slide 6: @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; strategy=GenerationType.IDENTITY - O banco de dados usa uma coluna de autoincremento. // Slide 7: @Id @SequenceGenerator(name="Funcionario_Seq", sequenceName="Func_Gen") @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="Funcionario_Seq") private int id; strategy=GenerationType.SEQUENCE - Uma sequence é usada para gerar a chave primária. Essa sequence é descrita em uma annotation @SequenceGenerator. // Slide 8: @TableGenerator( name="Funcionario_Seq", table="COntadores", pkColumnName="Nome", valueColumnName="Proximo", pkColumnValue="Funcionario" ) @Id @GeneratedValue(strategy=GenerationType.TABLE, generator="Funcionario_Seq") private int id; strategy=GenerationType.TABLE - Uma outra tabela é usada para gerar a chave primária. Essa tabela é especificada em uma annotation @TableGenerator. // Slide 9: Para tipos de data, hora e data/hora: - java.sql.Date mapeia para "data". - java.sql.Timestamp mapeia para "data/hora". - java.sqlTime mapeia para "hora". - java.util.Date e java.util.Calendar mapeiam de acordo com a annotation @Temporal. @Temporal(TemporalType.DATE) private Date dataNascimento; TemporalType pode ser DATE, TIME ou TIMESTAMP. Campos ou getters que devem ser ignorados pelo JPA devem ser marcados como "transient" ou ter a annotation @Transient. Campos ou getters de tipos enum devem ter a anotação @Enumerated. @Enumerated tem dois valores: ORDINAL, que é o padráo e diz que o método ordinal() do enum será usado para persistí-lo, ou STRING que diz que o método name() será usado para persistí-lo. @Enumerated(EnumType.STRING) private Estado estado; // Slide 10: @Lob @Basic(optional=true, fetch=FetchType.LAZY) private char[] fotografia; @Lob é usado para mapear campos dos tipos CLOB e BLOB. Normalmente é usado junto com @Basic(fetch=FetchType.LAZY). @Basic é usado para se definir o tipo do "fetch" de uma coluna e se o JPA aceita o valor null em um atributo ou getter (de acordo com propriedade "optional"). FetchType.EAGER - Indica que o campo sempre é carregado para a memória junto com a entidade. FetchType.LAZY - Indica que o campo só é carregado para a memória quando esse é referenciado. O padrão de @Basic é optional=true e fetch=FetchType.EAGER. // Slide 11: Em certas ocasiões, o nome da tabela não corresponde aos da entidade, o nome dos campos não corresponde aos nomes dos atributos ou setters/getters da entidade ou o nome da entidade desejado não é o nome da classe. Para resolver isso: @Entity tem uma propriedade name, que é o nome que será usado em consultas JPQL. Se omitido o JPA usa o nome da classe. @Table contém as propriedades name, catalog e schema. Se omitidos o JPA tenta deduzir. @Table também tem um atributo uniqueConstraints, utilizado apenas por ferramentas que geram o banco de dados automaticamente. @Entity(name="truck") @Table(name="tb_caminhoes") public class Caminhao implements Serializable { // Slide 12: @Column É usado para definir características da coluna no banco de dados. Os atributos importantes são: name - É o nome da coluna. Se ausente o JPA assume que o nome da coluna é o nome do atributo ou getter. nullable - Indica se a coluna aceita valores nulos ou não. O padrão é true. length, scale, precision, unique, insertable, updatable - Autoexplicativos. table - Indica em qual tabela está a coluna (útil em casos onde @SecondaryTable(s) é utilizado). columnDefinition - Usado por ferramentas que geram o banco de dados automaticamente. @Entity public class Carro implements Serializable { @Id @Column(name="carro_placa") private String placa; @Column(unique=true, nullable=true) private String renavam; // Slide 13: Por vezes, principalmente em bancos de dados ligados, precisamos definir uma entidade que representa duas tabelas ligadas por um relacionamento 1-1. Para isso temos @SecondaryTable. No caso de entidades que representam três ou mais tabelas, usamos @SecondaryTables. @Entity @SecondaryTable(name="carro_detran") public class Carro implements Serializable { @Id private String placa; @Column(table="carro_detran") private String renavam; // Slide 14: Às vezes, um subgrupo de atributos de alguma(s) tabela(s) correspondem a algum tipo de objeto importante. Como por exemplo o endereço. @Entity public class Empresa implements Serializable { ... blablabla ... private String rua; private String cep; private String bairro; private String cidade; ... blablabla ... } @Entity public class Funcionario implements Serializable { ... blablabla ... private String rua; private String cep; private String bairro; private String cidade; ... blablabla ... } Usar campos repetitivos é ruim. Além disso, e se quisessemos trabalhar com o endereço como se este fosse um objeto? ... Endereco e = empresa.getEndereco(); validarCep(e.getCep()); ... Como fazer? // Slide 15: @Embeddable e @Embedded ao resgate! @Embeddable public class Endereco { private String rua; private String cep; private String bairro; private String cidade; ... blablabla ... } @Entity public class Empresa implements Serializable { @Embedded private Endereco endereco; ... blablabla ... } @Entity public class Funcionario implements Serializable { @Embedded private Endereco endereco; ... blablabla ... } // Slide 16: Mas, e se o nome das colunas não coincidir? @Entity public class Empresa implements Serializable { ... blablabla ... @Column(name="endr_rua") private String rua; private String cep; @Column(name="empresa_bairro") private String bairro; private String cidade; ... blablabla ... } @Entity public class Funcionario implements Serializable { ... blablabla ... @Column(name="func_endr") private String rua; @Column(name="func_cep") private String cep; private String bairro; @Column(name="fcidade") private String cidade; ... blablabla ... } E agora? // Slide 17: @AttributeOverride(s) ao resgate! @Entity public class Empresa implements Serializable { @AttributeOverrides({ @AttributeOverride(name="rua", column=@Column(name="endr_rua")), @AttributeOverride(name="bairro", column=@Column(name="empresa_bairro")) }) @Embedded private Endereco endereco; ... blablabla ... } @Entity public class Funcionario implements Serializable { @AttributeOverrides({ @AttributeOverride(name="rua", column=@Column(name="func_endr")), @AttributeOverride(name="cep", column=@Column(name="func_cep")), @AttributeOverride(name="cidade", column=@Column(name="fcidade")) }) @Embedded private Endereco endereco; ... blablabla ... } // Slide 18: Para uma chave primária composta podemos usar @IdClass: public class DataComLocal implements Serializable { private Date dia; private String lugar; public DataComLocal() { } // Getters, setters e outros métodos. @Override public boolean equals(Object oj) { ... } @Override public int hashCode() { ... } } @Entity @IdClass(DataComLocal.class) public class Congresso implements Serializable { @Id @Temporal(TemporalType=DATE) private Date dia; @Id private String lugar; ... blablabla ... } Importante: A classe da chave primária deve implementar Serializable, ter um construtor público sem argumentos e implementar equals e hashCode de forma consistente. É possível utilizar-se @AttributeOverride(s) caso seja necessário renomear-se campos ou de alguma outra forma mudar as suas propriedades. // Slide 18: Outra possibilidade para chaves primárias composta é usar @EmbeddedId: public class DataComLocal implements Serializable { @Temporal(TemporalType=DATE) private Date dia; private String lugar; public DataComLocal() { } // Getters, setters e outros métodos. @Override public boolean equals(Object oj) { ... } @Override public int hashCode() { ... } } @Entity public class Congresso implements Serializable { @EmbeddedId private DataComLocal dia; ... blablabla ... } // Slide 19: E como fazer os relacionamentos entre classes? @OneToOne - Cada elemento desta classe se relaciona com um elemento da outra classe. @OneToMany - Cada elemento desta classe se relaciona com vários elementos da outra classe. @ManyToOne - Vários elementos desta classe se relaciona com um mesmo elemento da outra classe. @ManyToMany - Vários elementos desta classe se relaciona com vários elementos da outra classe. @OneToMany e @ManyToMany deve ser usado em campos do tipo java.util.Collection, java.util.Set, java.util.List e java.util.Map. @Entity public class Empresa implements Serializable { @OneToMany private Collection funcionarios; @ManyToMany private Collection clientes; ... blablabla ... } // Slide 20: @OneToOne, @OneToMany, @ManyToOne e @ManyToMany: Todos eles têm os atributos "cascade", "fetch" e "targetEntity". - "cascade", que indica quais operações realizadas na entidade vão cascater através do relacionamento. Por padrão nada é cascateado. - "fetch" que indica como as entidades relacionadas são carregadas. @OneToOne e @ManyToOne usam FetchType.EAGER por padrão. @OneToMany e @ManyToMany usam FetchType.LAZY por padrão. - "targetEntity" define qual é o tipo da entidade relacionada. Necessário em casos onde haja herança entre entidades ou onde o tipo do atributo ou getter for uma coleção não-genérica. @OneToOne e @ManyToOne têm também o atributo "optional", para indicar se a associação com a outra entidade é obrigatória ou não. Por padrão é true. // Slide 21: @OneToOne, @OneToMany e @ManyToMany têm ainda o atributo "mappedBy", para resolver casos de relacionamentos bidirecionais. Um dos lados é tido como dono da relação. E o lado oposto usa mappedBy para indicar em qual atributo ou getter do dono da relação ocorre a junção. @Entity public class Veiculo implements Serializable { @ManyToOne private Pessoa proprietario; ... blablabla ... } @Entity public class Pessoa implements Serializable { @OneToMany(mappedBy="proprietario") private Collection veiculos; @ManyToOne(fetch=FetchType.LAZY) private Pessoa mae; @OneToMany(mappedBy="mae") private Collection filhos; ... blablabla ... } IMPORTANTE: Ao modificar um relacionamento bidirecional no seu programa, lemre-se de fazê-los dos dois lados da relação. // Slide 22: Problema: não é possível usar @Column em atributos ou getters que se refiram a outras outras entidades com @OneToOne, @OneToMany, @ManyToOne e @ManyToMany. Isso ocorre porque esses objetos não correspondem a uma coluna, e sim a um grupos de colunas existente em uma outra tabela. @Entity public class Veiculo implements Serializable { @ManyToOne @Column(name="prop") // NÃO FUNCIONA! private Pessoa proprietario; ... blablabla ... } Como resolver isso? // Slide 23: @JoinColumn ao resgate! @Entity public class Veiculo implements Serializable { @ManyToOne @JoinColumn(name="cod_pessoa") private Pessoa proprietario; ... blablabla ... } A annotation @JoinColumn é bem parecida com a annotation @Column, porém tem alguns campos a menos. Ela especifica as propriedades da chave estrangeira. Quando a tabela relacionada tiver uma chave composta, use @JoinColumns. @JoinColumn pode ser aplicado em @ManyToOne, especificando qual coluna desta entidade referencia a outra entidade, ou em @OneToMany, especificand qual coluna da outra entidade referencia esta entidade. // Slide 24: Às vezes, em um relacionamento 1-1, não há chave estrangeira, e sim ocorre das tuplas relacionadas nas duas tabelas simplesmente terem a mesma chave primária. Este seria um caso típico de se usar @SecondaryTable. Mas, se for importante manter as entidades separadas, use @PrimaryKeyJoinColumn, ou @PrimaryKeyJoinColumns para chaves compostas: @Entity public class Pessoa implements Serializable { @Id private long cpf; @OneToOne @PrimaryKeyJoinColumn private RegistroCivil registro; ... blablabla ... } @Entity public class RegistroCivil implements Serializable { @Id private long cpf; @OneToOne(mappedBy="registro") private Pessoa pessoa; ... blablabla ... } Se for preciso especificar o nome das colunas da chave primária (ou seja, o nome padrão deduzido pelo JPA não é o correto), use os atributos "name" e "referencedColumnName" de @PrimaryKeyJoinColumn. // Slide 25: Relacionamentos N-N usam uma tabela intermediária. Quando @ManyToMany é usado, o JPA tenta deduzir o nome da tabela intermediária e dos respectivos campos. Se for preciso especificar o nome da tabela intermediária e/ou dos seus campos, use @JoinTable. @JoinTable é quase igual a @Table, no entanto tem os campos joinColumn e inverseJoinColumns a mais: @Entity public class Pessoa implements Serializable { @Id private long cpf; @ManyToMany @JoinTable(name="visitas", joinColumns={@JoinColumn(name="cpf_visitante")}, inverseJoinColumns={@JoinColumn(name="cod_museu")} ) private Collection museusVisitados; ... blablabla ... } // Slide 26: Ao utilizar um java.util.List para especificar algum relacionamento baseado em coleção, temos um problema: Como manter a lista ordenada? Solução: Usar @OrderBy, com um pequeno trecho de JPQL: @Entity public class Empresa implements Serializable { @OneToMany @OrderBy("nascimento, nome") private List funcionarios; ... blablabla ... } // Slide 27: Ao utilizar um java.util.Map para especificar algum relacionamento baseado em coleção, também temos um problema: Qual é a chave? Solução: Especificar o campo da tabela relacionada a ser usada como chave com a annotation @MapKey: @Entity public class Empresa implements Serializable { @OneToMany @MapKey(name="cpf") private Map funcionarios; ... blablabla ... } // Slide 28: Como modelar herança de entidades? O JPA tem três formas de fazer isso, por meio da annotation @Inheritance: - Tabela única. @Inheritance(strategy=InheritanceType.SINGLE_TABLE) - Tabela por classe concreta. @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) - Tabela por subclasse. @Inheritance(strategy=InheritanceType.JOINED) Quando a superclasse não é uma entidade, usa-se a annotation @MappedSuperclass. É possível usar-se as annotations @AttributeOverride e @AttributeOverrides para redefinir-se campos herdados (tais annotations são colocadas na classe). Para relacionamentos do tipo @OneToOne, @OneToMany, @ManyToOne e @ManyToMany, é possível redefinir-se colunas de junção por meio das annotations @AssociationOverride e @AssociationOverrides. // Slide 29: Tabela única: Se baseia na existência de uma coluna na tabela que identifique o tipo de entidade. Essa coluna é especificada com a annotation @DiscriminatorColumn. O valor da coluna que identifica a entidade é especificado com @DiscriminatorValue. @Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="tipo", discriminatorType=DiscriminatorType.STRING) @DiscriminatorValue("Emp") public class Empresa implements Serializable { ... blablabla ... } @Entity @DiscriminatorValue("Farma") public class Farmacia extends Empresa { ... blablabla ... } A estratégia de tabela única é a mais rápida das três em termos de desempenho, pois não há junções e nem uniões e uma única tabela precisa ser administrada. Mas não há normalização e todos os campos das subclasses devem ser marcados como "nullable". // Slide 30: Tabela por classe concreta: Cria uma tabela para cada classe concreta e copia-e-cola os campos da superclasse lá: @Entity @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) public class Empresa implements Serializable { ... blablabla ... } @Entity public class Farmacia extends Empresa { ... blablabla ... } A estratégia de tabela por classe concreta tende a ser mais fácil de modelar e de usar a partir de um banco de dados legado. Além disso ela dispensa a existência de uma coluna discriminadora. Mas, há várias colunas redundantes em tabelas distintas e o modelo não é normalizado. Buscas na superclasse provavelmente exigirão vários UNIONs e o desempenho será ruim. // Slide 31: Tabela por subclasse: Cria uma tabela para a superclasse e uma para cada subclasse, e realiza junções para uní-las. A união ocorre por meio da annotation @PrimaryJoinColumn(s), nas subclasses. @Entity @Inheritance(strategy=InheritanceType.JOINED) public class Empresa implements Serializable { ... blablabla ... } @Entity @PrimaryKeyJoinColumn(name="id_empresa") public class Farmacia extends Empresa { ... blablabla ... } A estratégia de tabela por subclasse deixa o modelo normalizado e sem restrições, tendo um desempenho apenas um pouco inferior a estratégia de tabela única. // Slide 32: Quando a superclasse de uma entidade não é uma entidade, usa-se a annotation @MappedSuperclass: @MappedSuperclass public class Empresa implements Serializable { ... blablabla ... } @Entity @Inheritance(strategy=InheritanceType.JOINED) public class Farmacia extends Empresa { ... blablabla ... } Os campos da superclasse serão copiados-e-colados nas subclasses.