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
.
-- -- 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.
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 + '}'; } }
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.
<!--adicione logo a baixo do mapeamento da classe Pessoa --> <mapping class="com.wp.mb.consultas.model.Veiculo"/>
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.
// 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 Listv1 = 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(); } }
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
.
// Inserir no método main() // Busca todos veiculos do tipo 1.8 E que a pessoa tenha nome Fernandes Listv2 = 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.
-- -- 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);
-- -- 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
.
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 Collectionpessoas = new ArrayList (); //gere os métodos get/set e equals/hashCode @Override public String toString() { return "Telefone{" + "id=" + id + ", ddd=" + ddd + ", numero=" + numero + '}'; } }
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
.
//insira os imports: import java.util.ArrayList; import java.util.Collection; //insira o seguinte código: @ManyToMany(mappedBy = "pessoas") private Collectiontelefones = 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.
<!--adicione logo a baixo do mapeamento da classe Pessoa e Veiculo --> <mapping class="com.wp.mb.consultas.model.Telefone"/>
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).
// Inserir no método main() TelefoneDao dao = new TelefoneDao(); Listt1 = 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(); } }
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.
//Inserir na classe PessoaDao public ListfindFoneByNomePessoa(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.
// Inserir no método main() // Busca todas pessoas com nome Fernandes e seus Listp7 = 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).
// 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
- Criteria http://download.oracle.com/javaee/6/tutorial/doc/gjitv.html
- Criteria com hibernate http://docs.jboss.org/hibernate/core/3.3/reference/en/html/querycriteria.html
- CriteriaSpecification interface http://docs.jboss.org/hibernate/core/3.2/api/org/hibernate/criterion/CriteriaSpecification.html
- Hibernate Annotations http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html