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 impedance mismatch, 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: SINGLE_TABLE, JOINED e TABLE_PER_CLASS. Na classe da entidade base define-se a estratégia de herança para a hierarquia com uma anotação Inheritance: @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @Inheritance(strategy=InheritanceType.JOINED) @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) O enum InheritanceType define 3 valores para declarar a estratégia da herança para a hierarquia: InheritanceType.SINGLE_TABLE InheritanceType.JOINED InheritanceType.TABLE_PER_CLASS O valor por omissão é InheritanceType.SINGLE_TABLE. Usando as estratégias SINGLE_TABLE ou 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 que classe cada registo pertence). Single Table A estratégia Single_Table mapeia todas as classes da hierarquia na tabela da classe base. Para qualquer estratégia, 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 efectuar 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. 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 dessa 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 efectuar 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. Table per class A estratégia Table_per_Class usa uma tabela diferente para cada classe concreta da hierarquia, 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, e só cria tabelas para as classes concretas. 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 dessa subclasse. Não necessita de efetuar join com as tabelas das superclasses. Esta estratégia tableper-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.
Consideremos uma hierarquia para representar Movimentos (classe Movement) de contas bancárias: Despesas (classe Expense) e Receitas (classe Income). Estratégia Single Table Tabela MOVEMENT: ID DTYPE AMOUNT DATEOCURRED DESCRIPTION EXPENSETYPE INCOMETYPE 1 Expense 150 2016-02-05 calças Clothing <NULL> 2 Expense 66 2016-02-15 camisa Clothing <NULL> 3 Expense 35 2016-02-01 Passe Metro Transports <NULL> 4 Income 200 2016-01-31 Venc Janeiro <NULL> Salary
Estratégia JOINED @Inheritance(strategy=InheritanceType.JOINED) Tabela MOVEMENT: ID DTYPE AMOUNT DATEOCURRED DESCRIPTION 1 Expense 150 2016-02-05 calças 2 Expense 66 2016-02-15 camisa 3 Expense 35 2016-02-01 Passe Metro 4 Income 200 2016-01-31 Venc Janeiro Tabela EXPENSE: ID EXPENSETYPE 1 Clothing 2 Clothing 3 Transports Foreign Keys: FK_EXPENSE_ID ID -> MOVEMENT.ID Tabela INCOME: ID INCOMETYPE 4 Salary Foreign Keys: FK_INCOME_ID ID -> MOVEMENT.ID
Estratégia TABLE_PER_CLASS @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) Tabela EXPENSE: ID AMOUNT DATEOCURRED DESCRIPTION EXPENSETYPE 1 150 2016-02-05 calças Clothing 2 66 2016-02-15 camisa Clothing 3 35 2016-02-01 Passe Metro Transports Tabela INCOME: ID AMOUNT DATEOCURRED DESCRIPTION INCOMETYPE 1 200 2016-01-31 Venc Janeiro Salary Se a classe Movement não tivesse sido declarada como abstract esta estratégia criaria também uma tabela MOVEMENT. Caso não se criasse nenhum objeto Movement essa tabela ficaria vazia.
Programa completo: public Movement() { public Movement(String description, BigDecimal amount, Date dateocurred) { this.description = description; this.amount = amount; this.dateocurred = dateocurred; public Expense() { public Expense(String description, BigDecimal amount, Date dateocurred, String expensetype) { super(description, amount, dateocurred); this.expensetype = expensetype; public Income() { public Income(String description, BigDecimal amount, Date dateocurred, String incometype) { super(description, amount, dateocurred); this.incometype = incometype; public class JPA { public static void main(string[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPA2PU"); EntityManager em = emf.createentitymanager(); Expense e1 = new Expense( "Passe Metro", new BigDecimal(35), newdate(2016, 2, 1), "Transports"); Expense e2 = new Expense( "camisa", new BigDecimal(66), newdate(2016, 2, 15), "Clothing"); Expense e3 = new Expense( "calças", new BigDecimal(150), newdate(201, 2, 2), "Clothing"); Income i1 = new Income( "Venc Janeiro", new BigDecimal(200), newdate(2016, 1, 31), "Salary"); em.gettransaction().begin(); em.persist(e1); em.persist(e2); em.persist(e3); em.persist(i1); em.gettransaction().commit(); em.close(); emf.close(); public static Date newdate(int year, int month, int day) { Calendar c = Calendar.getInstance(); c.set(year, month - 1, day); return c.gettime();