Consultas com Hibernate e a API Criteria – Parte II

Na primeira parte deste artigo (Consultas com Hibernate e a API Criteria – Parte I) foram apresentadas algumas formas de realizar consultas na base de dados com o uso da Criteria Query API. Essas consultas foram realizadas em uma única tabela (Pessoas) apresentando elementos iniciais da API. Nesta segunda parte veremos como realizar algumas consultas entre relacionamentos do tipo 1-N (Pessoas – Veículos) e N-N (Pessoas – Telefones). A estrutura do projeto continua a mesma do primeiro artigo, apenas adicione as novas classes no projeto e as novas tabelas no banco de dados.

1. Tabela Veículos

Para começar vamos criar a tabela Veiculos no banco de dados, para isso, utilize o script SQL descrito a seguir na Listagem 1. O relacionamento entre as tabelas Pessoas (1) e Veiculos (N) será do tipo 1-N. Na tabela Veiculos teremos uma chave estrangeira referente a tabela Pessoas.

Listagem 1. Script Tabela Veiculos.
--
-- Estrutura da tabela `veiculos`
--

CREATE TABLE IF NOT EXISTS `veiculos` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `MODELO` varchar(20) DEFAULT NULL,
  `PLACA` varchar(8) DEFAULT NULL,
  `FK_ID_PESSOA` bigint(20) NOT NULL,
  PRIMARY KEY (`ID`),
  KEY `FKA81CC2441D33B43E` (`FK_ID_PESSOA`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=24 ;

--
-- Dados da tabela `veiculos`
--

INSERT INTO `veiculos` (`ID`, `MODELO`, `PLACA`, `FK_ID_PESSOA`) VALUES
(1, 'Uno 1.5R', 'JKS 1032', 5),
(2, 'Stilo 1.8', 'JKS 1432', 5),
(3, 'Stilo 1.8', 'JEE 3332', 3),
(4, 'Gol III 1.6', 'JWS 4052', 6),
(5, 'Gol III 1.6', 'FRS 5432', 7),
(6, 'Escort XR3', 'POL 3343', 11),
(7, 'Vectra 1.8', 'QWE 4432', 19),
(8, 'Pointer 1.8', 'QWE 3699', 19),
(9, 'Golf 1.8', 'ASS 2022', 14),
(10, 'Corcel II', 'AKS 4455', 16),
(11, 'Premio CS 1.5', 'XZS 5522', 15),
(12, 'Elba CSL 1.5', 'XZS 1232', 15),
(13, 'Fusca 1.3', 'XZS 1736', 15),
(14, 'Vectra 1.8', 'JKS 6532', 20),
(15, 'Tipo 1.6', 'ADS 1333', 3),
(16, 'Corsa Sedan', 'LOL 3332', 17),
(17, 'Corsa 1.0', 'SDR 5222', 9),
(18, 'Corsa 1.0', 'POL 5487', 8),
(19, 'Corsa 1.0', 'SDR 2345', 9),
(20, 'Corsa 1.0', 'POL 7688', 12),
(21, 'Corsa Sedan 1.0', 'POL 1423', 18),
(22, 'Gol 1.0', 'POL 3232', 4),
(23, 'Uno 1.0', 'SDR 5222', 13);

2. Classe Veiculo e VeiculoDao

Tendo criado a tabela Veiculos no banco de dados, vamos agora criar as classes Veiculo (Listagem 2) e VeiculoDao (Listagem 3). A classe VeiculoDao terá seus métodos adicionados mais a frente no artigo, crie a classe por enquanto.

Listagem 2. Classe Veiculo.
package com.wp.mb.consultas.model;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name = "VEICULOS")
public class Veiculo implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID", nullable = false)
    private Long id;
    @Column(name = "MODELO", length = 20)
    private String modelo;
    @Column(name = "PLACA", length=8)
    private String placa;
    @ManyToOne
    @JoinColumn(name = "FK_ID_PESSOA", nullable = false)
    private Pessoa pessoa;

    //gere os métodos get/set e equals/hashCode

    @Override
    public String toString() {
        return "Veiculo{" +
                "id=" + id +
                ", modelo='" + modelo + '\'' +
                ", placa='" + placa + '\'' +
                ", pessoa=" + pessoa +
                '}';
    }
}
Listagem 3. Classe VeiculoDao.
package com.wp.mb.consultas.dao;

import com.wp.mb.consultas.model.Veiculo;
import com.wp.mb.consultas.util.HibernateUtil;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;

import java.util.List;

public class VeiculoDao extends HibernateUtil {
    //os métodos serão mostrados a seguir.
}

4. Classe de testes e arquivo hibernate.cfg.xml

Vamos agora adicionar no arquivo hibernate.cfg.xml a linha que faz referencia ao mapeamento da classe Veiculo, para isso, adicione a o código da Listagem 4. Na classe Main você deve importar as classes Veiculo e VeiculoDao, conforme a Listagem 5.

Listagem 4. Arquivo hibernate.cfg.xml.
<!--adicione logo a baixo do mapeamento da classe Pessoa -->
<mapping class="com.wp.mb.consultas.model.Veiculo"/>
Listagem 5. Classe Main.
package com.wp.mb.consultas.executa;

import com.wp.mb.consultas.dao.PessoaDao;
import com.wp.mb.consultas.model.Pessoa;

import com.wp.mb.consultas.dao.VeiculoDao;
import com.wp.mb.consultas.model.Veiculo;

import java.util.List;

public class Main {
}

5. Realizando consultas entre as tabelas Pessoas e Veiculos

Agora vamos partir para as consultas. Nas listagens teremos o código que executa a chamada a consulta e que deve ser inserido no método main(), e o método que deve ser inserido na classe VeiculoDao.

As consultas mais simples já foram explicadas no artigo anterior, vamos então começar com uma consulta que leve como parametros dados de veículos e também de pessoas. Na Listagem 6, vamos localizar todos os veículos que tenham placas com as letras “JWS” e que seu proprietário tenha idade entre 18 e 30 anos. Uma novidade nessa consulta é o método createCriteria() onde passamos como parametro uma String. Esse par metro faz referencia ao atributo Pessoa pessoa declarado na classe Veiculo. Dessa forma dizemos para a consulta que os critérios de busca definidos após essa declaração, são referentes aos dados da tabela Pessoas. Essa consulta seria similar a consulta em SQL da Listagem 6.1.

Listagem 6. Consulta Parte 1.
// Inserir no método main()     

	VeiculoDao dao = new VeiculoDao();

	//Busca veiculos que tenham placas iniciando com as letras JWS
	// E a pessoa com idade entre 18 e 30 anos
	List v1 = dao.findVeiculosByLetrasAndIadade("JWS", 18, 30);
	for (Veiculo v : v1)
		System.out.println("findVeiculosByLetrasAndIadade: " + v.toString());

//Inserir na classe VeiculoDao

    public List findVeiculosByLetrasAndIadade(String letras, int first, int last) {
        try {
            return getSession()
			.createCriteria(Veiculo.class)
				.add(Restrictions.like("placa", letras, MatchMode.START))
			.createCriteria("pessoa")
				.add(Restrictions.between("idade", first, last))
			.list();       
        } finally {
            close();
        }
    }
Listagem 6.1. Consulta em SQL da Listagem 6.
SELECT * FROM veiculos v, pessoas p
WHERE v.placa LIKE  '%JWS%'
AND v.fk_id_pessoa = p.id
AND p.idade BETWEEN 18 AND 30

A seguir veremos uma consulta (Listagem 7) que também utiliza critérios mistos, entre as duas tabelas. Desta vez vamos localizar os veículos com motor “1.8”, descrito no campo modelo. O critério para a tabela Pessoas será localizar todas as pessoas que tenha “Fernandes” no nome. Usamos agora um método diferente para indicar que parte da consulta deverá ser direcionada a tabela Pessoas, o método createAlias(). Esse método possui 2 parametros, o 1° é o atributo pessoa da tabela Veiculo, e o 2° é o apelido dado ao atributo para referenciar os atributos da classe Pessoa. Esse apelido seria a mesma coisa que usamos no SQL quando fazemos um select desse tipo: SELECT v.id, v.modelo FROM Veiculos v.

Listagem 7. Consulta Parte 2.
// Inserir no método main()

        // Busca todos veiculos do tipo 1.8 E que a pessoa tenha nome Fernandes
        List v2 =
		dao.findVeiculosByModeloAndNomePessoa("1.8", "Fernandes");

        for (Veiculo v : v2) {
            System.out.println(
				"findVeiculosByModeloAndNomePessoa: "
				+ v.toString()
            );
        }

//Inserir na classe VeiculoDao

    // Busca todos veiculos do tipo 1.8 E que a pessoa tenha nome Fernandes
    public List findVeiculosByModeloAndNomePessoa(String modelo, String nomePessoa) {
        try {
            return getSession().createCriteria(Veiculo.class)
			.add(Restrictions.like("modelo", modelo, MatchMode.ANYWHERE))
			.createAlias("pessoa", "p")
				.add(Restrictions.like("p.nome", nomePessoa, MatchMode.ANYWHERE))
			.list();
        } finally {
            close();
        }
    }

6. Script Tabela Telefones

Partimos agora para a tabela Telefones (Listagem 8) que terá um relacionamento N-N com a tabela Pessoas. Quando temos um relacionamento N-N devemos criar uma nova tabela que ira gerenciar esse relacionamento. Criaremos para isso, a tabela Fone_Pessoas (Listagem 9). Esse relacionamento diz que podemos ter uma pessoa que possua vários telefones, e um telefone pode ser de várias pessoas.

Listagem 8. Script Tabela Telefones.
--
-- Estrutura da tabela `telefones`
--

CREATE TABLE IF NOT EXISTS `telefones` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `DDD` int(11) DEFAULT NULL,
  `NUMERO` int(11) DEFAULT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=17 ;

--
-- Dados da tabela `telefones`
--

INSERT INTO `telefones` (`ID`, `DDD`, `NUMERO`) VALUES
(1, 11, 21226369),
(2, 11, 21224569),
(3, 11, 21225439),
(4, 11, 22226229),
(5, 21, 23222362),
(6, 21, 3223122),
(7, 21, 23221261),
(8, 21, 32221319),
(9, 48, 33226119),
(10, 48, 33224344),
(11, 47, 34222449),
(12, 51, 92226009),
(13, 55, 92221269),
(14, 51, 92226312),
(15, 51, 91222269),
(16, 51, 91223239);
Listagem 9. Script Tabela Fone_Pessoas
--
-- Estrutura da tabela `fone_pessoas`
--

CREATE TABLE IF NOT EXISTS `fone_pessoas` (
  `ID_PESSOA` bigint(20) NOT NULL,
  `ID_FONE` bigint(20) NOT NULL,
  PRIMARY KEY (`ID_PESSOA`,`ID_FONE`),
  KEY `FKF1DC8DC1B6B4D1` (`ID_FONE`),
  KEY `FKF1DC8DD22C80FB` (`ID_PESSOA`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

--
-- Dados da tabela `fone_pessoas`
--

INSERT INTO `fone_pessoas` (`ID_PESSOA`, `ID_FONE`) VALUES
(1, 12),
(1, 13),
(3, 1),
(11, 11),
(14, 10),
(16, 14),
(17, 9),
(19, 10),
(20, 8),
(20, 15),
(20, 16),
(22, 7);

7. Classe Tefefone e TelefoneDao

Após criar as tabelas Telefones e Fone_Pessoas no banco de dados, vamos criar as classes Telefone (Listagem 10) e TelefoneDao (Listagem 11). Como usamos o Hibernate para gerenciar as transações com o banco de dados, não precisamos criar uma classe para a tabela Fone_Pessoas, o próprio Hibernate terá conhecimento desta classe a partir dos mapeamentos (anotações) nas classes Pessoa e Telefone.

Listagem 10. Classe Telefone
package com.wp.mb.consultas.model;

import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Entity
@Table(name = "TELEFONES")
public class Telefone implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID", nullable = false)
    private Long id;
    @Column(name = "DDD", length = 2)
    private int ddd;
    @Column(name = "NUMERO", length=8)
    private int numero;

    @ManyToMany(targetEntity = Pessoa.class)
    @JoinTable(
	name = "FONE_PESSOAS",
	joinColumns = @JoinColumn(name = "ID_FONE"),
	inverseJoinColumns = @JoinColumn(name = "ID_PESSOA") )
    private Collection pessoas = new ArrayList();

    //gere os métodos get/set e equals/hashCode

    @Override
    public String toString() {
        return "Telefone{" +
                "id=" + id +
                ", ddd=" + ddd +
                ", numero=" + numero +
                '}';
    }
}
Listagem 11. Classe TelefoneDao
package com.wp.mb.consultas.dao;

import com.wp.mb.consultas.model.Telefone;
import com.wp.mb.consultas.util.HibernateUtil;
import org.hibernate.Criteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;

import java.util.List;

public class TelefoneDao extends HibernateUtil {
    //metodos serão descritos mais a frente.
}

Precisamos agora mapear na classe Pessoa o relacionamento N-N entre as tabelas Pessoas e Telefones, para isso, adicione o código da Listagem 12 na classe Pessoa.

Listagem 12. Complemento da classe Pessoa.
//insira os imports:
import java.util.ArrayList;
import java.util.Collection;

//insira o seguinte código:
    @ManyToMany(mappedBy = "pessoas")
    private Collection telefones = new ArrayList();

    public Collection getTelefones() {
        return telefones;
    }

    public void setTelefones(Collection telefones) {
        this.telefones = telefones;
    }

8. Classe de testes e arquivo hibernate.cfg.xml

Vamos agora adicionar no arquivo hibernate.cfg.xml a linha que faz referencia ao mapeamento da classe Telefone, para isso, adicione a o código da Listagem 13. Na classe Main você deve importar as classes Telefone e TelefoneDao, conforme a Listagem 14.

Listagem 13. Arquivo hibernate.cfg.xml.
<!--adicione logo a baixo do mapeamento da classe Pessoa e Veiculo -->
<mapping class="com.wp.mb.consultas.model.Telefone"/>
Listagem 14. Classe Main.
package com.wp.mb.consultas.executa;

import com.wp.mb.consultas.dao.PessoaDao;
import com.wp.mb.consultas.model.Pessoa;

import com.wp.mb.consultas.dao.VeiculoDao;
import com.wp.mb.consultas.model.Veiculo;

import com.wp.mb.consultas.dao.TelefoneDao;
import com.wp.mb.consultas.model.Telefone;

import java.util.List;

public class Main {
}

9. Realizando consultas entre as tabelas Telefones, Pessoas e Veiculos

Na Listagem 15 vamos localizar as pessoas que tenham o nome “Fernandes”. A consulta é muito semelhante as executas nos exemplos da classe VeiculoDao. A diferença aqui é interna, o Hibernate sabe que existe uma tabela de relacionamento entre a tabela Pessoas e Telefones. Assim não precisamos no momento da consulta indicar esse relacionamento como faríamos em uma consulta em SQL (Listagem 15.1).

Listagem 15. Consulta Parte 3.
// Inserir no método main()

	TelefoneDao dao = new TelefoneDao();
	List t1 = dao.findFoneByNomePessoa("Fernandes");
	for (Telefone t : t1) {
		System.out.println("findFoneByNomePessoa: " + t.toString() + t.getPessoas()	);
	} 

//Inserir na classe TelefoneDao

    public List findFoneByNomePessoa(String nome) {
        try {
            return getSession().createCriteria(Telefone.class)
			.createAlias("pessoas", "p")
				.add(Restrictions.like("p.nome", nome, MatchMode.END))
			.list();
        } finally {
            close();
        }
    }
Listagem 15.1. Consulta SQL da Listagem 15.
SELECT
  t.ID, t.DDD, t.NUMERO,
  p.ID, p.NOME, p.idade,
  f.ID_PESSOA, f.id_fone
FROM telefones t, pessoas p, fone_pessoas f
WHERE p.nome LIKE  '%Fernandes%'
AND p.id = f.id_pessoa
AND f.id_fone = t.id

O resultado dessa consulta, tanto a consulta SQL quanto a consulta com Criteria seria o mesmo, veja a seguir o resultado usando Criteria:

Telefone{id=10, ddd=48, numero=33224344}[Pessoa{id=19, nome='Tatiane Fernandes', idade=21}, Pessoa{id=14, nome='Fabio Fernandes', idade=18}]

Telefone{id=10, ddd=48, numero=33224344}[Pessoa{id=19, nome='Tatiane Fernandes', idade=21}, Pessoa{id=14, nome='Fabio Fernandes', idade=18}]

Telefone{id=8, ddd=21, numero=32221319}[Pessoa{id=20, nome='Diego da Silva Fernandes', idade=19}]

Telefone{id=15, ddd=51, numero=91222269}[Pessoa{id=20, nome='Diego da Silva Fernandes', idade=19}]

Telefone{id=16, ddd=51, numero=91223239}[Pessoa{id=20, nome='Diego da Silva Fernandes', idade=19}]

Note que nessa consulta tivemos o mesmo resultado nas duas primeiras linhas. Isso aconteceu por que o telefone de id = 10 pertence a duas pessoas, a “Tatiane Fernandes” e ao “Fabio Fernandes”. Como é o mesmo resultado nas duas linhas, podemos fazer com que aparece apenas uma linha para evitar essa repetição, para isso, usamos a clausula DISTINCT do SQL. Veja na Listagem 16 que usamos o método setResultTransformer() e passamos como parametro o Criteria.DISTINCT_ROOT_ENTITY. Essa propriedade faz com que seja aplicado um DISTINCT na entidade raiz da consulta, nesse caso na entidade Telefone que possui duas linhas iguais no resultado.

Listagem 16. Consulta Parte 4.
//Inserir na classe PessoaDao

    public List findFoneByNomePessoa(String nome) {
        try {
            return getSession().createCriteria(Telefone.class)
			.createAlias("pessoas", "p")
				.add(Restrictions.like("p.nome", nome, MatchMode.END))
			.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
			.list();
        } finally {
            close();
        }
    }

Podemos também recuperar todos os telefones de uma pessoa pesquisando pela classe PessoaDao e através do atributo Collection telefones, poderemos recuperá-los, veja um exemplo na Listagem 17.

Listagem 17. Consulta Parte 5.
// Inserir no método main()

        // Busca todas pessoas com nome Fernandes e seus
        List p7 = dao.findPessoasAndFoneByName("Fernandes");
        for (Pessoa p : p7) {
            System.out.println("findPessoasAndFoneByName: "	+ p.toString() + " " + p.getTelefones());
        }

//Inserir na classe PessoaDao

    // Busca pessoas e telefones pertencentes a elas
    public List findPessoasAndFoneByName(String nome) {
        try {
            return getSession().createCriteria(Pessoa.class)
			.add( Restrictions.like("nome", nome, MatchMode.ANYWHERE) )
			.list();
        } finally {
            close();
        }
    }

Outra consulta que pode ser realizada para encontrar os telefones é pesquisando por veículos. Veículos não têm ligação direta com telefones, porem o resultado pode ser alcançado pela existência do relacionamento entre veículos-pessoas e pessoas-telefones. Vamos ver um exemplo onde usamos a placa "JKS 6532" para realizar a consulta e encontrar o telefone da pessoa que possui esse veiculo (Listagem 18).

Listagem 18. Consulta Parte 6.
// Inserir no método main()

	Veiculo t2 = dao.findFonesByPlacaVeiculo("JKS 6532");
	System.out.println(
		"findFonesByPlacaVeiculo: " +
		t2.toString() + " " +
		t2.getPessoa().getTelefones()
	);

//Inserir na classe VeiculoDao

    public Veiculo findFonesByPlacaVeiculo(String placa) {
        return (Veiculo) getSession().createCriteria(Veiculo.class)
		.add(Restrictions.eq("placa", placa))
		.uniqueResult();
    }

Chegamos ao final do artigo, fica como dica a documentação do Hibernate e acessar os links disponibilizados no artigo para aprender mais sobre como realizar consultas com a API Criteria.

Saiba mais

Ballem

Marcio Ballem é bacharel em Sistemas de Informação pelo Centro Universitário Franciscano em Santa Maria/RS. Tem experiência com desenvolvimento Delphi e Java em projetos para gestão pública e acadêmica. Possui certificação em Java, OCJP 6.

Você pode gostar...