sábado, 25 de abril de 2009

Mapeamento objeto-relacional (ORM) com Hibernate – Uma abordagem prática

Nesta postagem será mostrado como fazer o mapeamento objeto-relacional (ORM) utilizando o hibernate através de um exemplo prático. A idéia principal do exemplo é apenas mostrar como fazer o mapeamento objeto-relacional sem ressaltar conceitos envolvidos. Espera-se que ele sirva como uma referência rápida e objetiva para desenvolvedores.

Para iniciar, vamos definir o modelo de dados relacional através de um diagrama entidade-relacionamento (figura abaixo).



Para melhor visualização clique na imagem


Através do diagrama entidade-relacionamento acima, podemos extrair alguns casos a serem mapeados.
  • Herança – Entidades envolvidas: aluno, professor, pessoa;
  • N : M sem atributos intermediários – Entidades envolvidas: disciplina_professor, professor, disciplina;
  • 1 : N – Entidades envolvidas: turma, aluno;
  • N : M com atributo intermediário – Entidades envolvidas: professor_turma, turma, professor.
Abaixo temos as classes java com o mapeamento objeto-relacional exemplificando os casos citados acima.

Mapeamento de Pessoa:

import static javax.persistence.GenerationType.IDENTITY;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;

@SuppressWarnings("serial")
@Entity
@Table(name = "pessoa", catalog = "mapeamentohibernate")
@Inheritance(strategy = InheritanceType.JOINED)
public class Pessoa implements Serializable {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;

@Column(name = "nome")
private String nome;
// getters e setters omitidos
}

Mapeamento de Aluno:


import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;


@SuppressWarnings("serial")
@Entity
@Table(name = "aluno", catalog = "mapeamentohibernate")
@PrimaryKeyJoinColumn(name = "id") // id da tabela aluno
public class Aluno extends Pessoa implements Serializable {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idturma")
private Turma turma;

@Column(name = "matricula")
private String matricula;

// getters e setters omitidos
}

Mapeamento de Professor:


import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@SuppressWarnings("serial")
@Entity
@Table(name = "professor", catalog = "mapeamentohibernate")
@PrimaryKeyJoinColumn(name = "id") // id da tabela professor
public class Professor extends Pessoa implements Serializable {

@Column(name = "formacao")
private String formacao;

@OneToMany(fetch = FetchType.LAZY, mappedBy = "professor")
private Set<ProfessorTurma> professorTurma =
new HashSet<ProfessorTurma>(0);

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "disciplina_professor", catalog = "mapeamentohibernate",
joinColumns = { @JoinColumn(name = "idprofessor", nullable = false,
updatable = false) }, inverseJoinColumns = {
@JoinColumn(name = "iddisciplina", nullable = false, updatable = false) })
private Set<Disciplina> disciplinas = new HashSet<Disciplina>(0);

// getters e setters omitidos
}

Mapeamento de Disciplina:

import static javax.persistence.GenerationType.IDENTITY;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;


@SuppressWarnings("serial")
@Entity
@Table(name = "disciplina", catalog = "mapeamentohibernate")
public class Disciplina implements Serializable {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;

@Column(name = "nome")
private String nome;

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "disciplina_professor", catalog = "mapeamentohibernate",
joinColumns = { @JoinColumn(name = "iddisciplina", nullable = false,
updatable = false) }, inverseJoinColumns = {
@JoinColumn(name = "idprofessor", nullable = false, updatable = false) })
private Set<Professor> professores = new HashSet<Professor>(0);

// getters e setters omitidos
}

Mapeamento de Turma:

import static javax.persistence.GenerationType.IDENTITY;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@SuppressWarnings("serial")
@Entity
@Table(name = "turma", catalog = "mapeamentohibernate")
public class Turma implements Serializable {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;

@Column(name = "descricao")
private String descricao;

@OneToMany(fetch = FetchType.LAZY, mappedBy = "turma")
private Set<ProfessorTurma> professorTurma = new
HashSet<ProfessorTurma>(0);

@OneToMany(fetch = FetchType.LAZY, mappedBy = "turma")
private Set<Aluno> alunos = new HashSet<Aluno>(0);

// getters e setters omitidos
}

Mapeamento de ProfessorTurma:

import java.io.Serializable;
import java.util.Date;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@SuppressWarnings("serial")
@Entity
@Table(name = "professor_turma", catalog = "mapeamentohibernate")
public class ProfessorTurma implements Serializable {

@EmbeddedId
@AttributeOverrides( { @AttributeOverride(name = "idturma", column =
@Column(name = "idturma", nullable = false)), @AttributeOverride(name =
"idprofessor", column = @Column(name = "idprofessor", nullable = false)) })
private Id id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idturma", nullable = false, insertable = false,
updatable = false)
private Turma turma;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "idprofessor", nullable = false, insertable = false,
updatable = false)
private Professor professor;

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "horario", length = 19)
private Date horario;

// getters e setters omitidos

//Id da associacao (chave composta no banco)
@Embeddable
public static class Id implements Serializable {

@Column(name = "idTurma", nullable = false)
private Integer idTurma;

@Column(name = "idProfessor", nullable = false)
private Integer idProfessor;

public Integer getIdTurma() {
return this.idTurma;
}

public void setIdTurma(Integer idturma) {
this.idTurma = idturma;
}

public Integer getIdProfessor() {
return this.idProfessor;
}

public void setIdProfessor(Integer idprofessor) {
this.idProfessor = idprofessor;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + idProfessor;
result = prime * result + idTurma;
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Id other = (Id) obj;
if (idProfessor != other.idProfessor)
return false;
if (idTurma != other.idTurma)
return false;
return true;
}
}
}


Note que, no caso N:M que não tem atributo intermediário (tabela disciplina_professor) , não há necessidade de uma classe para representar a associação. No caso em que há atributos intermediários, como o da tabela professor_turma, temos que criar uma classe para representar a associação (classe ProfessorTurma).

O banco de dados utilizado no exemplo foi o MySQL. Quem quiser fazer o download do script do banco basta acessar o link:
http://sites.google.com/site/csscode/Home/mapeamentohibernate.sql

Uma dica para quem não tem muita familiaridade com o mapeamento objeto-relacional é usar o hibernate tools. O hibernate tools trata-se de um plugin do eclipse que auxilia na árdua e massante tarefa de mapear. Vale a pena conferir!!!


Thiago Baesso Procaci

quinta-feira, 23 de abril de 2009

Testes Unitários da Camada de Persistência com Spring, Hibernate e JUnit.


O teste unitário é uma modalidade de teste voltada para a verificação de “pedaços de software” (ou unidades) que funcionam de maneira independente das demais partes de um sistema. Em softwares construídos em linguagens orientadas a objetos, essas unidades podem ser uma classe ou um método. Em linhas gerais, busca-se com testes unitários garantir que uma parte isolada de um sistema funcione corretamente de acordo com o contexto em que ela se encontra.

Atualmente, em projetos de software, muito se tem visto testes unitários para a camada de persistência com objetivo de assegurar que os objetos que representam o domínio de dados serão salvos, recuperados e excluídos corretamente. Em projetos em que há mudanças consideráveis no modelo de dados (devido a necessidade de adaptações ou mudança de requisitos) os testes unitários da camada de
persistência mostram-se como uma ferramenta poderosa na verificação dos impactos das mudanças na persistência dos dados. Afinal, nada melhor que após mudanças no projeto averiguar que partes isoladas ainda descrevem o comportamento esperado.

Nesta postagem será mostrada uma forma eficiente e prática de testar a camada de persistência de uma aplicação através do uso do spring-test (parte do spring destinada a testes unitários), juntamente com o JUnit. Por questões de praticidade será utilizado o hibernate para o mapeamento objeto-relacional do exemplo.

Para iniciar o exemplo, vamos definir a classe Pessoa que representará a entidade que será persistida no banco de dados.

import static javax.persistence.GenerationType.IDENTITY;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@SuppressWarnings("serial")
@Entity
@Table(name = "pessoa", catalog = "springtest")
public class Pessoa implements Serializable {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;
@Column(name = "nome")
private String nome;
@Column(name = "idade")
private Integer idade;
// getters e setters omitidos
}

Vamos também definir um DAO para pessoa cuja interface será:

public interface PessoaDao {
void save(Pessoa pessoa);
void delete(Pessoa pessoa);
Pessoa getById(Integer id);
}

Certamente para a classe que implementará a interface PessoaDao deverá ser definido um bean no contexto do spring.

Agora vamos montar os testes unitários da camada de persistência de pessoa utilizando o spring-test junto com o JUnit.


import static junit.framework.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
import com.springtest.dao.PessoaDao;
import com.springtest.model.Pessoa;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:WebContent/WEB-INF/persistencia.xml" })
@Transactional
@TransactionConfiguration(defaultRollback = false)
public class PessoaTest {
@Autowired
private PessoaDao pessoaDao;

@Test
public void testSave() {
Pessoa pessoa = new Pessoa();
pessoa.setNome("Thiago Baesso Procaci");
pessoa.setIdade(22);
pessoaDao.save(pessoa);
assertNotNull(pessoa.getId());
}

@Test
public void testDelete(){
// implementacao do teste
}

@Test
public void testUpdate(){
// implementacao do teste
}
}
Através da classe de teste acima pode-se notar algumas facilidades que o spring-test e o JUnit oferecem através de anotações. Segue abaixo uma breve explicação de cada uma delas:
  • @RunWith(SpringJUnit4ClassRunner.class): anotação responsável por definir “quem” irá rodar os testes. No caso, aclasse SpringJUnit4ClassRunner já provê algumas facilidades para testes
    unitários que utilizam o spring;

  • @ContextConfiguration: carrega o contexto do spring (beans) para serem utilizados na classe de teste;

  • @Transactional / @TransactionConfiguration: definem cada método de teste unitário como uma transação;

  • @Autowired: Injeta os beans do spring para serem utilizados nos testes;

  • @Test: definem os métodos que serão os testes unitários.

Enfim, testes em geral são fundamentais para a assegurar a qualidade de qualquer sistema. Achei essa abordagem com o spring-test muito interessante e prática para a confecção de testes unitários.

Para quem quiser mais detalhes sobre o funcionamento do spring-test acesse:

http://static.springframework.org/spring/docs/2.5.x/reference/testing.html

Os fontes do exemplo estão disponíveis em:

http://sites.google.com/site/csscode/Home/Exemplo_Teste_Unitario.zip?attredirects=0

Abraço a todos!!!

Thiago Baesso Procaci