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

Postado em 09. ago, 2011 por Erich Egert em Arquitetura, Java

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.

Erich Egert

Mais sobre o autor

Tags: , , , , , ,

23 Respostas para “Adequar o banco às entidades ou o contrário?”

  1. Leandro Moreira

    09. ago, 2011

    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. ago, 2011

    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. ago, 2011

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

  4. Paulo Silveira

    09. ago, 2011

    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. ago, 2011

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

  6. Gabriel Bezerra

    10. ago, 2011

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

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

  7. Paulo Silveira

    10. ago, 2011

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

  8. Raphael

    12. ago, 2011

    excelente post!

    @Alex… pq é incorreto utilizar Calendar?

  9. Paulo Silveira

    12. ago, 2011

    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. ago, 2011

    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. ago, 2011

    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. ago, 2011

    “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. ago, 2011

    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. ago, 2011

    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. ago, 2011

    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. ago, 2011

    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. ago, 2011

    @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. ago, 2011

    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. set, 2011

    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. set, 2011

    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. mai, 2013

    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. mai, 2013

    .. para o framework gerar as tabelas…

  23. Erich Egert

    06. mai, 2013

    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.

Deixar uma Resposta