Adequar o banco às entidades ou o contrário?

Uma das features interessantes do Hibernate é gerar a Data Definition Language para criação da estrutura de tabelas do banco de dados (schema). Tudo que precisamos fazer é pensar em nosso modelo Orientado a Objeto que o banco é gerado automaticamente a partir das entidades. Aparentemente o processo de pensar na modelagem do banco de dados tornou-se obsoleto: é aí que mora o perigo.

mapeamento de banco de dados a orientacao a objetos

Na orientação a objetos é preferível favorecer especialização através da divisão de responsabilidades. Em vez de criar uma classe gigantesca devemos quebrá-la em pequenas classes.

Quando o assunto é banco de dados a análise é bem diferente. No modelo relacional, a preocupação é normalização dos dados e a modelagem ao mesmo tempo tende a ser pensada de forma a evitar a necessidade de alguns joins que seriam mais caros. Muitas vezes é preferível criar uma tabela com várias colunas com todas as informações pertinentes a criar várias tabelas especializadas. Para ilustrar, considere as entidades:

@Entity
public class Produto {
  @Id
  @GeneratedValue
  private Long id;
  private String nome;
  private BigDecimal preco;
  private Calendar inicioPeriodoPromocional;
  private Calendar fimPeriodoPromocional;
  // outros atributos e metodos
}

@Entity
public class Promocao {
 private Calendar inicio;
 private Calendar fim;
 //outros atributos e metodos
}

Suponha que gostaríamos de saber se uma promoção é aplicável a um produto e por quantos dias podemos aproveitar uma promoção. Colocar esse código dentro de um controller ou managed bean não parece uma boa idéia. Esse trecho rapidamente reaparecerá em outro lugar. Onde então podemos criar métodos para essa análise? Poderíamos começar criando a classe Intervalo:

public class Intervalo {
  private Calendar inicio;
  private Calendar fim;
}

E em vez de Produto e Promocao guardarem referências para duas datas, guardariam para um Intervalo, removendo os respectivos Calendars:

@Entity
public class Produto {
  @Id
  @GeneratedValue
  private Long id;
  private String nome;
  private BigDecimal preco;
  private Intervalo periodoPromocional;
  //...
}

@Entity
public class Promocao {
  private Intervalo vigencia;
  //...
}

A criação da classe Intervalo pode trazer benefícios muito maiores do que a eliminação da repetição de atributos. Adotando a boa prática de unir dados a comportamentos, podemos isolar todo comportamento relacionado a um intervalo de datas nessa nova classe:

public class Intervalo {
  private Calendar inicio;
  private Calendar fim;

  public int getDias() {...}
  public Intervalo interseccaoCom(Intervalo outro) {...}
}

Para saber quantos dias temos para aproveitar a promoção faríamos:

Intervalo janelaDeOportunidade =
  produto.getPeriodoPromocional().interseccaoCom(promocao.getVigencia());

int diasParaAproveitar = janelaDeOportunidade.getDias();

Mas como mapeamos a classe Intervalo? Criar uma tabela e referenciar seus registros através de chaves estrangeiras nas tabelas Produto e Promocao só trará a necessidade de joins. A JPA nos oferece uma alternativa: criar uma classe Embeddable, um dos assuntos básicos abordados no FJ-25. Quando anotamos uma classe com @Embeddable, seus atributos serão colunas na tabela da entidade que tem o atributo anotado com @Embedded.

@Embeddable
public class Intervalo {
  //...
}

@Entity
public class Promocao {
  @Embedded
  private Intervalo vigencia;
  // a tabela Promocao terá uma coluna inicio e outra fim
  // ...
}

A classe Intervalo não precisa de um identificador único: ela é definida pelo seu valor e pode ser encarada como um Value Object. É importante conhecer os diversos recursos do Hibernate que nos dão a possibilidade de não precisar adequar nossas entidades às nossas tabelas, nem de adequar nossas tabelas às nossas entidades. Por exemplo, tabelas secundárias oferecem o serviço inverso: uma entidade mapeada em duas tabelas. O próprio @ManyToMany evita a criação de uma classe a mais apenas para manter referências para as duas classes relacionadas. Há ainda o suporte a chaves compostas, herança, nomenclatura das tabelas, namespaces e colunas, e até mesmo tipos definidos pelo próprio usuário para cada banco de dados diferente.

26 Comentários

  1. Leandro Moreira 09/08/2011 at 11:19 #

    Gostei do post, só fiquei com uma má impressão ao ler eu tive a impressão de que essas características são exclusivas do Hibernate (Embebbed) e não são elas estão presentes no “padrão” JPA e seus implementadores.

  2. erich.egert 09/08/2011 at 11:56 #

    Boa observação Leandro! O recurso descrito no post, a @Embeddable, faz parte da JPA, portanto qualquer implementação dessa spec possui esse feature. O legal é lembrar que a motivação para criar um Embeddable pode ser o mesmo de qualquer outra classe: Dar comportamento para alguns dados. Já fiz a alteração no post! Obrigado!

  3. Alex 09/08/2011 at 13:20 #

    Não é correto usar Calendar nas entidades. Date é um value-object, Calendar não.

  4. Paulo Silveira 09/08/2011 at 13:36 #

    Ola Alex. Você quer dizer, além de considerar que Calendar não é um value object, de que só se pude utilizar value objects dentro de entidades? E relacionamentos então, como ficariam? Não faz muito sentido, além de obviamente esse não ser o ponto do post, tanto que utilizamos datas sempre com Joda Time.

  5. Lucas Murata 09/08/2011 at 13:51 #

    @Paulo, aproveitando. Voces utilizam as datas do Joda Time nas Entidades, por exemplo DateTime ou LocalTime para horas?

  6. Gabriel Bezerra 10/08/2011 at 00:03 #

    “No paradigma entidade-relacional a preocupação é […]”

    Não seria modelo entidades-relacionamentos e/ou modelo relacional?

  7. Paulo Silveira 10/08/2011 at 01:14 #

    Oi Gabriel, fica melhor mesmo. Alterei la pois eu que coloquei os “paradigmas” durante a revisao pro Erich.

  8. Raphael 12/08/2011 at 15:11 #

    excelente post!

    @Alex… pq é incorreto utilizar Calendar?

  9. Paulo Silveira 12/08/2011 at 15:15 #

    Oi Rapha. Ele deve estar dizendo que é incorreto por Calendar nao ser imutavel. O pessoal aconselha que value object seja imutavel (mas nao é algo obrigatório).

    Ele prefere Date, chuto eu, por poder ser considerada imutavel se voce remover os metodos deprecated.

    De qualquer forma, mesmo enxergando assim, nao encaro desse jeito. Como o Erich conversou comigo, se voce utiliza Calendars e sempre devolve copias defensivas, ele esta muito bem funcionando como um Value Object. Além disso, usar Date em vez de Calendar, é muito masoquismo.

  10. Diego Lovison 15/08/2011 at 11:30 #

    Legal o post, minha dúvida é a seguinte:

    public interface Pessoa
    public class PessoaFisica impl Pessoa
    public class PessoaJuridica impl Pessoa

    public class Venda {
    private Pessoa pessoa;
    }

    No Java funciona perfeitamente, o problema é que no banco de dados eu não consigo ter a tabela “venda” com duas fk uma para “pessoa_juridica” outra para “pessoa_fisica”.
    Outro problema é que o relacionamento de uma classe para uma interface, a JPA não consegue resolver por que ela não sabe como resolver.

    Nesse cenário como se livrar da herança e favorecer a implementação.

    Obrigado.

  11. Diego Lovison 15/08/2011 at 11:42 #

    Outro problema é que o relacionamento de uma classe para uma interface, a JPA não consegue resolver por que ela não sabe como resolver.

    Que justificativa bonita
    kkkkk

    “Não sabe como resolver” = não sabe qual implementação da interface retornar. PF ou PJ.

    Agora ficou melhor
    😉

  12. Rafael Duque Estrada 16/08/2011 at 09:32 #

    “O próprio @ManyToMany evita a criação de uma classe a mais apenas para manter referências para as duas classes relacionadas. Há ainda o suporte a chaves compostas, herança, nomenclatura das tabelas, namespaces e colunas, e até mesmo tipos definidos pelo próprio usuário para cada banco de dados diferente.”

    Desse parágrafo podem surgir temas futuros para posts aqui no blog:
    1- @ManyToMany;
    2- Composite Keys;
    3- Setar o nome da coluna do banco de dados (em vez do nome do atributo) para entidades @Embedded.

    Apenas sugestões…

  13. Rafael Duque Estrada 16/08/2011 at 09:33 #

    PS: Coloquei 2 posts pois no primeiro eu escrevi aqui no FORUM e no segundo eu corrigi para BLOG, ou seja, favor só considerar o segundo.

    Grato!

  14. Erich Egert 16/08/2011 at 14:40 #

    Oi Rafael!

    Boas sugestões! Um detalhe legal sobre o uso do Embedded é que somos obrigados a customizar o nome das colunas no caso de usarmos o mesmo Embeddable como atributo 2 vezes na mesma classe. Aí é usar o @AttributeOverrides (o que é pouco prático quando o Embedded tem muitos atributos). Podemos tb usar uma estratégia de naming não padrão configurando o persistence.xml com algo como:

  15. Otávio 26/08/2011 at 10:43 #

    Muito bom o artigo.

    Lembro que em meados de 2003 conheci o Middlegen. Montavamos todo o mando de dados, e com ele geravamos os HBMs. E depois de alguns ajustes nos HBMs finalmente geravamos os .java. Dava um trabalhão, felizmente o middlegen fazia o trabalho chato.

    Quando saiu o JPA e as vantagens de não usar HBM fiz um teste: escrevi todo um sistema começando pelas entidades e deixando o provider gerar as entidades. E ficou muito bom.

    Nesse caso deixei o provider gerar os nomes das tabelas/colunas assim como no Java, e ficou bacana ver tabelas como CustomerAccount.

    Quando ao assunto do Date/Calendar, eu particularmente uso Date por ser um objeto mais leve e simples. Quando preciso fazer alguma coisa nesse Date faço os calculos via Calendar e vira um Date de novo. Mas creio que isso seja gosto. Já ví gente usando Calendar, outros usando Date…

    Falando nisso já está na hora de sair um javax.date, não?

  16. Erich Egert 29/08/2011 at 15:35 #

    Ola Otávio! Para manipulação de datas ainda é mais produtivo usar a biblioteca Joda Time, que tem vários métodos úteis já implementados, com uso de interface fluente e preocupação com imutabilidade.

  17. Lucas Murata 30/08/2011 at 14:45 #

    @Erich, com certeza, manipulação de Datas é com JodaTime, mas utilizo o Date como variável de instancia, como disse o Otavio.

    Calendar API não é bom, há uma nova especificação pra Data baseada no Joda Time: http://today.java.net/pub/a/today/2008/09/18/jsr-310-new-java-date-time-api.html, liderado pelo seu criador.

  18. Marcus Jimenez 31/08/2011 at 16:55 #

    Considerando o que foi exposto no artigo podemos então concluir que a modelagem do banco de dados é uma coisa e o design das entidades é outra, e que elas devem ser vistas como coisas distintas e, teoricamente, independentes?

    Existem pessoas que defendem a idéia de fazer uma espécie de “espelhamento”, deixando nome de classe igual ao da tabela no banco, nome de atributo igual ao campo da tabela, de maneira implicita (sem usar as anotações de nome de tabela e de colunas), ou seja, fica a cargo do framework a geração do banco com a nomenclatura que ele usa como padrão.

    No meu ponto de vista, se com o advento do mapeamento objeto-relacional a aplicação não precisa saber do banco, eu acho que o contrário também deveria ser verdadeiro, ou seja, a modelagem do banco não deveria estar de modo algum condicionada à aplicação.

    Alguém aí pensa diferente?

  19. Jefferson 11/09/2011 at 20:28 #

    Olá, estou trabalhando na tela de cadastro de usuários. Tenho uma @Entity Usuario e os campos username, password, email, dentre outros. Acontece que na tela de cadastro colocamos duas vezes o campo de password para que o usuário confirme o password, como o segundo password nos serve somente para validação, não gostaria que o mesmo esteja em nossa Entity.

    Qual um modo legal de trabalhar com esse cenário? Usar um DTO? colocar o atributo confirmaPassword como sendo um atributo Transiente?

  20. Erich Egert 12/09/2011 at 14:47 #

    Ola Jefferson! A confirmação de password é apenas para averiguar se o usuário digitou a senha desejada. Não chega a ser uma validação como o preço de um produto que tem que ser positivo (que precisa de validação server-side para garantir), portanto podemos fazer a verificação da confirmação de senha apenas client side em JS.

  21. Thiago 04/05/2013 at 16:38 #

    Ola, o post é antigo mas depois de lê-lo me veio uma questão. Voces falam para modelar os objetos para o framework, mas o contrario? Modelar o banco e a partir dai, gerar as entidades, nao seria recomendado?

  22. Thiago 04/05/2013 at 16:39 #

    .. para o framework gerar as tabelas…

  23. Erich Egert 06/05/2013 at 07:57 #

    Olá Thiago!
    Acho válido tanto começar a modelagem pelo banco quanto pelas entidades, meu objetivo com o post é dizer que independente da abordagem que você preferir, suas classes não precisam ser o reflexo exato da tabela. Uma das vantagens de começar modelando as entidades é o fato do Framework ficar com a responsabilidade de gerar a DDL de criação do banco cuja sintaxe pode variar um pouco de banco pra banco.

  24. Ricardo 21/11/2013 at 18:02 #

    Quando banco e aplicação surgem juntos é tranquilo, a coisa fica bonita mesmo é quando o banco é legado e várias aplicações de diferentes linguagens e frameworks puxam dados desse banco e sua aplicação puxa dados de vários bancos, e esse é um cenário bem comum, infelizmente.Por isso ainda acredito que devemos pensar primeiramente na base de dados, uma base mal modelada não tem orientação a objeto que salve.

  25. Erich Egert 26/11/2013 at 20:52 #

    Olá Ricardo! Certamente, muito mais importante focar na modelagem do banco! Só acho importante, mesmo quando o banco é legado e gerarmos reversamente as entidades através das tabelas, que utilizemos alguns recursos da JPA para tentar deixar nossa aplicação mais O.O. sem sacrificar a modelagem do banco.

  26. Matheus 02/04/2017 at 00:42 #

    Qual a melhor prática ? Criar minhas classes de acordo com o banco, ou pensar primeiro em minhas classes e então deixar o resto por conta do JPA.

Deixe uma resposta