390 likes | 495 Views
Hibernate – componentes, herança, e associações. Jobson Ronan {jrjs@cin.ufpe.br}. Objetivos. Aprender como realizar o mapeamento de modelos entidades que possuem associações, composições e herança com o hibernate. Granularidade alta (fina). Fine-grained == mais classes que tabelas
E N D
Hibernate – componentes, herança, e associações Jobson Ronan {jrjs@cin.ufpe.br}
Objetivos • Aprender como realizar o mapeamento de modelos entidades que possuem associações, composições e herança com o hibernate
Granularidade alta (fina) • Fine-grained == mais classes que tabelas • Mais coesão, mais reuso, modelo fácil de entender • Freqüentemente o melhor modelo de dados para uma tabela traduz-se em mais de uma classe • Considere, por exemplo, uma tabela cliente com dois endereços: endereco_fatura e endereco_entrega, cidade_fatura, cidade_entrega, etc. • Uma classe Endereco, agruparia campos relativos ao endereço de um Cliente, que teria propriedades enderecoFatura e enderecoEntrega como referências para dois objetos da classe Endereco • Uma coluna email poderia ser expandida em uma classe Email, para detalhar um tipo de dados e encapsular validação de e-mail em vez de ser uma mera propriedade String do Cliente
Tipos de objetos: entidade ou valor • Um objeto do tipo entidade tem sua própria identidade de registro de banco de dados • Uma referência para uma entidade é tornada persistente como uma referência no banco de dados (foreign key) • Uma entidade pode existir independentemente de outra entidade • Um objeto do tipo valor não tem identidade de banco de dados • Pertence a uma entidade • Seu estado persistente é parte do registro da entidade que a possui • Não têm identificadores ou propriedades de identificação • O tempo de vida é limitado pelo tempo de vida da entidade que o possui • Exemplos nativos: Strings, Integers, etc. • Um dos objetos de um registro tem uma identidade própria; os outros são dependentes dele • No exemplo anterior: objetos do tipo Cliente são entidades, objetos do tipo Endereco e Email são valores dependentes de um Cliente
Componentes • Um componente em Hibernate é a parte dependente de um objeto composto entidade-valor • Uma composição ocorre quando uma entidade, que tem identidade na tabela, está associada a objetos de valor que não têm identidade na tabela (seus dados são parte da tabela) • A remoção do registro no banco remove a entidade e seus componentes derivados (não confunda com cascade-delete) • Um componente é uma propriedade • Meio termo entre uma propriedade de campo de dados (field) e uma propriedade que é referência em relacionamento • Componentes só existem como objetos, mas não como tabelas • Não confunda com componente de software (arquitetura)
Componentes e tabelas • Os atributos do componente estão mapeados à mesma tabela que a classe composta
Mapeamento de componentes <class name="User" table="USER"> <id name="id“ column="USER_ID“ type="long"> <generator class="native"/> </id> <property name="username" column="USERNAME" type="string"/> <component name="homeAddress“ class="Address"> <property name="street" type="string" column="HOME_STREET" not^-null="true"/> <property name="city" type="string" column="HOME_CITY" not-null="true"/> <property name="zipcode" type="short" column="HOME_ZIPCODE" not-null="true"/> </component> <component name="billingAddress“ class="Address"> <property name="street" type="string" column="BILLING_STREET" not-null="true"/> <property name="city" type="string" column="BILLING_CITY" not-null="true"/> <property name="zipcode" type="short" column="BILLING_ZIPCODE" not-null="true"/> </component> ... </class>
Limitações de componentes • Classes mapeadas como componentes têm várias limitações • Não podem ter seus objetos compartilhados (não há como referir-se a eles pois não têm identidade própria) • Não existe maneira elegante de representar uma referência nula para um componente (é representado com valores nulos em todas as colunas do componente): se você gravar um componente não nulo com apenas valores nulos, Hibernate retorna null!
Mapeamento de herança • Herança é o descasamento mais visível entre os mundos relacional e orientado a objetos • Mundo OO possui relacionamento “é um” e “tem um” • Mundo relacional apenas possui relacionamento “tem um” • Há várias estratégias [Ambler 2002]* • Uma tabela por classe concreta: modelo relacional ignora herança e polimorfismo • Uma tabela por hierarquia de classes: permite polimorfismo com tabelas de-normalizadas mais uma coluna extra contendo informação de tipo • Uma tabela por subclasse: representa relacionamentos “é um” através de relacionamentos “tem um” (chave estrangeira)
Uma tabela por classe concreta • Ideal para classes que não fazem parte de uma hierarquia ou que estão na raiz de uma hierarquia (nível mais alto) • Essas classes não devem ser usadas em polimorfismo • Uma declaração <class> para cada classe concreta; um atributo table diferente para cada uma (igual a mapeamento simples) • Desvantagens • Pouco suporte para associações polimórficas • Queries polimórficos, executados em superclasses das classes usadas causam múltiplos queries nas tabelas mapeadas às classes concretas • Dificulta evolução do esquema (mudanças semânticas em propriedades da superclasse afetam colunas de várias tabelas)
Queries gerados • Os queries abaixo são conceituais (o SQL real gerado pelo Hibernate pode ser diferente) • Dois queries para fazer uma pesquisa na superclasse BillingDetails (ineficiente!) select CREDIT_CARD_ID, OWNER, NUMBER, CREATED, TYPE, ... from CREDIT_CARD where CREATED = ? select BANK_ACCOUNT_ID, OWNER, NUMBER, CREATED, BANK_NAME, ... from BANK_ACCOUNT where CREATED=? • Para fazer uma pesquisa numa classe concreta select CREDIT_CARD_ID, TYPE, EXP_MONTH, EXP_YEAR from CREDIT_CARD where CREATED = ?
Uma tabela por hierarquia • Mapeia-se a hierarquia toda a uma única tabela • Tabela inclui uma coluna para identificar a classe (tipo); esta coluna (discriminator) não é mapeada a uma propriedade mas usada internamente pelo Hibernate • Há colunas para todas as propriedades de todas as classes da hierarquia • A classe raiz é mapeada da forma convencional <class> • Subclasses são mapeadas dentro de <class> como <subclass> • Vantagens • Forma mais eficiente de implementar polimorfismo • É simples de implementar, entender e evoluir • Desvantagens • Colunas de propriedades declaradas em subclasses precisam aceitar valores nulos (não pode ser declarada not-null)
Queries gerados • Exemplo de query polimórfico (conceitual) na superclasse; um só query recupera dados de todas as subclasses select BILLING_DETAILS_ID, BILLING_DETAILS_TYPE, OWNER, ..., CREDIT_CARD_TYPE, from BILLING_DETAILS where CREATED = ? • Exemplo de um query em uma classe concreta select BILLING_DETAILS_ID, CREDIT_CARD_TYPE, CREDIT_CARD_EXP_MONTH, ...from BILLING_DETAILSwhere BILLING_DETAILS_TYPE='CC' and CREATED = ?
Mapeamento <hibernate-mapping> <class name="BillingDetails" table="BILLING_DETAILS" discriminator-value="BD"> <id name="id" column="BILLING_DETAILS_ID" type="long"> <generator class="native"/> </id> <discriminator column="BILLING_DETAILS_TYPE" type="string"/> <property name="name" column="OWNER" type="string"/> ... <subclass name="CreditCard" discriminator-value="CC"> <property name="type" column="CREDIT_CARD_TYPE"/> ... </subclass> ... </class> </hibernate-mapping>
Uma tabela por subclasse • Representa herança como relacionamentos de chave estrangeira • Cada subclasse que declara propriedades persistentes (inclusive interfaces e classes abstratas) tem sua própria tabela • Cada tabela possui colunas apenas para propriedades não-herdadas, e uma chave primária que é chave estrangeira da superclasse • Criação de uma instância cria registros nas tabelas da superclasse e subclasse • A recuperação dos dados é realizada através de um join das tabelas • <joined-subclass> (que pode conter outros elementos <joined-subclass>) pode ser usada no lugar de ou dentro de <class>
Uma tabela por subclasse • Vantagens • Modelo relacional normalizado • Evolução e restrições de integridade simples • Novas classes/tabelas criadas sem afetar classes/tabelas existentes • Desvantagens • Performance baixa em hierarquias complexas • Mais difícil de codificar a mão (complicado de integrar com JDBC legado)
Queries (conceituais) produzidos • Bem mais complicados... • Query na superclasse select BD.BILLING_DETAILS_ID, BD.CREATED, CC.TYPE, ..., BA.BANK_SWIFT, ... case when CC.CREDIT_CARD_ID is not null then 1 when BA.BANK_ACCOUNT_ID is not null then 2 when BD.BILLING_DETAILS_ID is not null then 0 end as TYPE from BILLING_DETAILS BD left join CREDIT_CARD CC on BD.BILLING_DETAILS_ID = CC.CREDIT_CARD_ID left join BANK_ACCOUNT BA on BD.BILLING_DETAILS_ID = BA.BANK_ACCOUNT_ID where BD.CREATED = ? • Query na subclasse select BD.BILLING_DETAILS_ID, BD.CREATED, CC.TYPE, ... from CREDIT_CARD CC inner join BILLING_DETAILS BD on BD.BILLING_DETAILS_ID = CC.CREDIT_CARD_ID where CC.CREATED = ?
Qual estratégia? • Normalmente, usa-se uma combinação de estratégias • Estratégias assumem desenvolvimento top-down (isto não é integração de um sistema legado) • Se não houver necessidade de queries polimórficos ou associações, prefira Tabela por Classe Concreta • Se houver necessidade de associações polimórficas, use... • ... Tabela por Hierarquia se as classes tiverem poucas propriedades e for uma hierarquia simples • ... Tabela por Subclasse se a hierarquia for mais complicada ou classes tiverem muitas propriedades (ou ainda se as restrições de Tabela por Hierarquia – como nulidade de colunas – forem inaceitáveis no modelo de dados)
Resumo: tags de mapeamento • <component> • Uma composição (objeto associado é dependente) • <subclass> • Usada para implementar a estratégia de mapeamento de herança “tabela por hierarquia de classe” • <joined-subclass> • Usado para implementar a estratégia “table-per-subclass” onde cada subclasse tem sua própria tabela • <discriminator> • Coluna e propriedade usada na estratégia “tabela por hierarquia de classe” para identificar a subclasse
XML de mapeamento <?xml version="1.0"?> <hibernate-mapping> <class name="BillingDetails“ table="BILLING_DETAILS"> <id name="id" column="BILLING_DETAILS_ID" type="long"> <generator class="native"/> </id> <property name="owner" column="OWNER" type="string"/> ... <joined-subclass name="CreditCard" table="CREDIT_CARD"> <key column="CREDIT_CARD_ID"> <property name="type" column="TYPE"/> ... </joined-subclass> ... </class> </hibernate-mapping>
Associações • Associações no Hibernate funcionam da mesma maneira que associações de objetos em Java • Diferente de EJBs CMP, onde relacionamentos são gerenciados pelo container • Associações de objetos em Hibernate são unidirecionais e não são gerenciadas pelo container • Associações sempre são relacionamentos entre entidades • Algumas são implementadas via coleções • Associações um-para-muitos são as mais comuns • Qualquer associação muitos-para-muitos pode ser implementada com um par de associações um-para-muitos: é mais simples! • Dá para realizar quase tudo apenas com <many-to-one>
Multiplicidade de associações • A primeira classificação que fazemos de uma associação é sua multiplicidade • Exemplo • Há mais de um Lance (Bid) para um certo Item? • Há mais de um Item para um certo Lance? • Conclusão • Existe uma associação de muitos para um de Bid para Item • Por ser bidirecional, podemos dizer que também existe uma associação de um para muitos de Item para Bid: o item conhece todos os lances feitos para ele.
Uma simples associação • A associação unidirecional, muitos para um de Bid para Item é a mais simples possível • A referência retornada por Bid.getItem() é mapeada à coluna ITEM_ID na tabela BID, que é chave estrangeira da chave primária da tabela ITEM <class name="Bid" table="BID"> ... <many-to-one name="item" column="ITEM_ID" class="Item" not-null="true"/> </class> public class Bid { ... private Item item; public void setItem(Item item) { this.item = item; } public Item getItem() { return item; } ... } Se houver um lance (bid) o item tem que existir
<many-to-one> • Many-to-one é uma associação comum • O modelo relacional é many-to-one • Uma referência de objeto é many-to-one • Alguns atributos: • name e column: nome da propriedade e coluna do banco • cascade: pode ter save-update, delete, all (informa que tipo de operação será repetida no objeto da associação) • outer-join: se true, objeto associado é carregado junto com o objeto pai; se ausente, o comportamento ocorre se objeto associado não estiver definido como um proxy.
Tornando-a bidirecional • Como encontrar todos os lances para um dado item? • Associação bidirecional um para muitos: use um set! public class Item { ... private Set bids = new HashSet(); public void setBids(Set bids) { this.bids = bids; } public Set getBids() { return bids; } public void addBid(Bid bid) { bid.setItem(this); bids.add(bid); } ... } <class name="Item" table="ITEM"> ... <set name="bids“> <key column="ITEM_ID"/> <one-to-many class="Bid"/> </set> </class>
<one-to-many> • <set> • Este <set> contém <one-to-many>: uma coleção de registros – mapeado diretamente à tabela! • Representa uma relação um para muitos no modelo relacional • Mapeamento direto: atributo class informa a classe (não é preciso informar colunas nem nome da tabela – o mapeamento da classe referida já tem essa informação!)
Um para um • Uma associação um para um pode ser declarada com o elemento <one-to-one> e/ou <many-to-one> • Há dois tipos de relacionamentos um-para-um • Associações de chave estrangeira unívocas: a chave nunca se repete na classe associada – usa elemento <many-to-one>; usa também <one-to-one> se for bidirecional. • Associações de chave primária: os dois objetos compartilham a mesma chave primária – usa apenas elementos <one-to-one>
Associação muitos para muitos unidirecional <class name=“Category” ...> ... <set name="items" table="CATEGORY_ITEM" lazy="true" cascade="save-update"> <key column="CATEGORY_ID"/> <many-to-many class="Item" column="ITEM_ID"/> </set> Transaction tx = session.beginTransaction(); Category cat = (Category) session.get(Category.class, categoryId); Item item = (Item) session.get(Item.class, itemId); cat.getItems().add(item); tx.commit();
Muitos para muitos bidirecional <class name="org.hibernate.auction.Category"> <id name="id" column="ID"/> ... <set name="items" table="CATEGORY_ITEM"> <key column="CATEGORY_ID"/> <many-to-many class="org.hibernate.auction.Item" column="ITEM_ID"/> </set> </class> <class name="org.hibernate.auction.Item"> <id name="id" column="ID"/> ... <!-- inverse end --> <set name="categories" table="CATEGORY_ITEM"> <key column="ITEM_ID"/> <many-to-many class="org.hibernate.auction.Category“ column="CATEGORY_ID"/> </set> </class>
Uso da associação bidirecional • É preciso modificar os dois lados! Transaction tx = session.beginTransaction(); Category cat = (Category) session.get(Category.class, categoryId); Item item = (Item) session.get(Item.class, itemId); cat.getItems().add(item); item.getCategories().add(category); tx.commit();
Associações polimórficas • Todas as associações mostradas até agora suportam polimorfismo • Não é preciso fazer nada de especial para ter polimorfismo no Hibernate
Coleções polimórficas: exemplo <class name=“User” > ... <set name="billingDetails" lazy="true" cascade="save-update" inverse="true"> <key column="USER_ID"/> <one-to-many class="BillingDetails"/> </set> <class name=“BillingDetails”> <many-to-one name="user" class="User" column="USER_ID"/> CreditCard cc = new CreditCard(); cc.setNumber(ccNumber); cc.setType(ccType); cc.setExpiryDate(ccExpiryDate); Session s = f.openSession(); Transaction tx = s.beginTransaction(); User user = (User) s.get(User.class, uid); // Call convenience method user.addBillingDetails(cc); tx.commit(); s.close(); Session s = f.openSession(); Transaction tx = s.beginTransaction(); User user = (User) s.get(User.class, uid); Iterator i = user.getBillingDetails().iterator(); while ( i.hasNext() ) { BillingDetails bd = (BillingDetails) i.next(); // CreditCard.pay() or BankAccount.pay() bd.pay(ccPaymentAmount); } tx.commit(); s.close();
Conclusões • Hibernate não ajuda só na implementação de modelos simples • Hibernate implementa facilmente coisas que levariam semanas • Associações, composições e heranças não são mais problemas para a camada de persistência
Referências • Hibernate in Action • Hibernate reference manual • caveatemptor.hibernate.org
Exercício 1 • Implementar no Hibernate o seguinte modelo
Exercício 2 (casa) • Implementar no Hibernate o seguinte modelo