Documente Academic
Documente Profesional
Documente Cultură
http://www.glugpb.org.br/
Introduo
O Hibernate o mais bem sucedido framework de mapeamento objeto/relacional (ORM Object/Relational Mapping) da comunidade Java e o seu sucesso foi to grande que ele influenciou mudanas drsticas em partes da especificao Java EE (antes J2EE) e ainda gerou uma verso para a plataforma .Net, que ainda est em fase beta. O sucesso do framework vem tanto da facilidade de se comear a trabalhar, quanto da quantidade de opes avanadas que melhoram vrios quesitos da aplicao e ns vamos ver um pouco dessas opes e de como us-las eficientemente.
Dicas
Alguns costumes se tornaram comuns na comunidade de usurios, tanto por alguns problemas inerentes da maneira como o framework trabalha, como de entendimentos incorretos do funcionamento de alguns servios que so providos. Vejamos alguns deles:
Quando voc no define a os relacionamentos como lazy=true ou usa left join fetch, o Hibernate vai fazer uma query para os objetos que voc queria receber e depois vai fazer mais uma query para cada objeto resultante . Quer dizer, em vez de fazer uma query que traz N objetos resultantes, voc vai ter (N +1) buscas no banco, o que com certeza vai ser um tremendo gargalo de desempenho na sua aplicao, j que ele vai fazer um select para cada objeto que foi recebido. Mas claro que isso no uma verdade absoluta, voc pode ter que necessariamente carregar algum relacionamento do seu objeto sempre que ele for carregado. Quando isso for necessrio, defina o atributo lazy como false e o atributo outer-join como true.
No exemplo, criamos a query from Curso e informamos ao objeto Query que queremos apenas os 10 primeiros resultados selecionados (na maioria dos bancos, os resultados se iniciam no 0, como em um Array). Se a busca no dinmica, use Named Queries Quando voc sabe que no vai precisar montar a query dinamicamente e j sabe quais vo ser os parmetros enviados para ela, use uma named query configurada em um dos arquivos de mapeamento do Hibernate. As named queries so compiladas e preparadas no banco de dados assim que o Hibernate se inicializa, o que aumenta a velocidade na qual essas queries executada (pois nem o banco nem o Hibernate precisam compilar elas outra vez). Em um arquivo de mapeamento qualquer:
Listagem 3 Exemplo de named query <query name="listar.usuarios.para.login"> <![CDATA[ from Pessoa p where p.nome = :nome and p.email = :email order by p.nome asc ]]> </query>
E chamando em cdigo:
Listagem 4 Chamando uma named query Session session = HibernateUtility.getSession(); Query query = session.getNamedQuery( "listar.usuarios.para.login" ); query.setString("nome", "Mauricio"); query.setString("email", "mauricio.linhares@gmail.com"); Grupo de Usurios Java http://www.guj.com.br Pgina 2
O cdigo muito parecido com o anterior, mas em vez de chamar createQuery() ns chamamos getNamedQuery() passando como parmetro o nome que foi dado a query no arquivo de mapeamento. A named query pode estar em qualquer dos arquivos de mapeamento do Hibernate, voc pode at mesmo criar um arquivo de mapeamento que s contenha named queries. O importante evitar que queries previsveis sejam montadas a todo o momento. Se a busca dinmica, use Criteria, no HQL Voc aprendeu HQL, achou uma linguagem interessante e simples de se trabalhar, mas o Hibernate tambm oferece buscas em uma API orientada a objetos, onde voc chama mtodos e passa parmetros para montar uma pesquisa e especialmente simples para se montar buscas que so construdas dinamicamente. Muitas vezes por j estar acostumado a trabalhar com HQL, voc simplesmente escreve a query em HQL e sai montando ela, concatenando Strings e inserindo ou retirando parmetros conforme eles so descobertos, o que normalmente traz erros que s so descobertos quando a aplicao comea a funcionar e demonstra comportamentos estranhos (ou queries que no funcionam, porque foram montadas incorretamente). Usando a API de Criteria, voc pode evitar a maioria desses problemas e ainda ter queries dinmicas que podem ser montadas facilmente. As associaes em Java no so bidirecionais Parece estranho ler isso em um primeiro momento, mas isso uma considerao importante. No ltimo artigo recebi vrias dvidas sobre problemas quando as pessoas tentavam executar o seguinte cdigo:
Session session = HibernateUtility.getSession(); HibernateUtility.beginTransaction(); Aluno aluno = new Aluno(); Endereco endereco = new Endereco(); aluno.setNome("Maurcio Linhares"); endereco.setCidade("Joo Pessoa"); aluno.setEndereco(endereco); session.save(aluno); HibernateUtility.commitTransaction(); HibernateUtility.closeSession();
O erro lanado pelo Hibernate indicava que o objeto endereco no tinha como ser inserido porque o valor do objeto pessoa relacionado a ele (que no nosso caso uma subclasse, Aluno) era nulo, mas ns colocamos o objeto endereco em pessoa, na chamada do mtodo setEndereco(), porque isso acontece? Como eu disse anteriormente, as associaes em Java no so bidirecionais automaticamente, voc tem que tornar a associao bidirecional explicitamente colocando a pessoa no endereco tambm, porque o relacionamento entre eles no banco de dados entre chaves primrias, no pode existir um endereco sem uma pessoa relacionada a ele. Para o nosso cdigo funcionar, ele deveria estar assim:
Session session = HibernateUtility.getSession(); HibernateUtility.beginTransaction(); Aluno aluno = new Aluno(); Endereco endereco = new Endereco(); aluno.setNome("Maurcio Linhares"); endereco.setCidade("Joo Pessoa"); aluno.setEndereco(endereco); endereco.setPessoa(aluno); session.save(aluno); HibernateUtility.commitTransaction(); HibernateUtility.closeSession();
Agora, como ns definimos explicitamente o relacionamento como bidirecional o cdigo funciona normalmente. UPDATES e DELETES em massa utilizando o Hibernate Um dos grandes problemas de se utilizar uma ferramenta de ORM que voc normalmente precisa trazer um objeto para a memria para poder editar ou atualizar algum campo. A coisa fica ainda mais complicada quando voc tem que atualizar vrios objetos de uma s vez, pois sero ainda mais objetos carregados na memria consumindo os recursos da sua mquina, uma coisa simples que em SQL poderia ser resolvida com um simples e rpido UPDATE. Agora com o Hibernate j possvel fazer UPDATES e DELETES em massa sem ter que carregar os objetos para a memria, a sintaxe dos comandos simples e muito parecida com as suas contrapartes em SQL, vejamos o comando: ( update | delete) from nomeDaClasse where As duas palavras em negrito (from e where) so opcionais, obrigatrio mesmo so apenas o comando (update ou delete) e o nome da classe onde esse comando vai ser utilizado. Em um update ou delete no Hibernate voc s pode utilizar apenas uma classe e no pode fazer nenhum join nem utilizar apelidos, apenas acessar as propriedades da classe. Vejamos um exemplo de cdigo onde vamos atualizar os nomes de todos os cursos que tenham o nome DSI:
HibernateUtility.beginTransaction(); Session session = HibernateUtility.getSession(); String updateQuery = update Curso set nome = :novoNome where nome = DSI ; Query update = session.createQuery( updateQuery ); update.setString( novoNome, Desenvolvimento de Software); int registrosAtualizados = update.executeUpdate(); HibernateUtility.commitTransaction(); HibernateUtility.closeSession();
Como voc pode ver, montar um update simples e a sintaxe do delete igual (mudando apenas o nome do comando). O mtodo executeUpdate() retorna a quantidade de registros atualizadas pelo comando, assim voc pode saber se ele surtiu efeito ou no no banco de dados.
Ferramentas
O site http://tools.hibernate.org/ traz as novas ferramentas do Hibernate (para a verso 3.x) para o Eclipse e vrios tasks do Ant. Os novos plugins se conectam ao banco para testar queries, geram arquivos de mapeamento e at mesmo as classes das tabelas do banco de dados. Vale a pena fazer os testes com o seu banco de dados, mas no v com muita sede ao pote, a gerao de arquivos de mapeamento e classes Java ainda no perfeita (e depende muito do suporte a metadata do driver JDBC do seu banco). Quando for utilizlos, faa sempre uma vistoria nos arquivos gerados.
banco de dados que est sendo utilizado, mas ainda existem algumas arestas que devem ser aparadas. Um dos principais que os objetos que utilizam a camada de persistncia (os Actions do seu sistema web ou os formulrios da sua aplicao Swing) no deveriam saber que esto utilizando o Hibernate. Idealmente, eles nem deveriam saber que existe um banco de dados relacional do outro lado, mas isso ainda um sonho distante nos nossos dias. Vamos ento montar uma camada de persistncia com padres de projeto que podem nos ajudar a manter o cdigo de acesso ao banco longe dos objetos que utilizam a camada, aproveitando ao mximo as possibilidades que o Hibernate nos fornece. Data Access Object (DAO) O padro de projeto Data Access Object (daqui pra frente chamado de DAO) um velho conhecido da comunidade Java, seu objetivo principal manter a lgica de acesso a bancos de dados dentro de objetos especializados, que transformam as queries e ResultSets retornados pela API de JDBC em objetos que faam sentido para a aplicao que est fazendo uso dos DAOs. Se ns no estivssemos utilizando o Hibernate, poderamos ter um DAO com um mtodo Collection listarCursos() que criaria um Statement, executaria uma query no banco de dados (algo como select * from Curso), trataria o ResultSet retornado e transformaria cada linha desse ResultSet em um objeto Curso, que seria adicionado a uma coleo qualquer (uma List, por exemplo) que seria ento retornada como resultado da chamada ao mtodo. Mas como estamos usando o Hibernate, todo esse trabalho j feito pelo prprio framework, o que nos indica que os nossos DAOs vo ser mais simples de serem implementados, como tambm podem ser mais poderosos do que os DAOs comuns, j que o Hibernate ns d novas escolhas no acesso ao banco. O primeiro DAO que ns vamos definir um DAO genrico, que vai lidar com as aes conhecidas como CRUD Create/Read/Update/Delete (Criar/Ler/Atualizar/Deletar) que so o bsico de qualquer camada de persistncia. Aqui ns percebemos uma das primeiras vantagens da camada de persistncia montada sobre o Hibernate, porque ns s precisamos de um nico DAO genrico para fazer CRUD de todas as classes mapeadas, j que o prprio Hibernate vai cuidar de fazer a persistncia no banco de dados. Se estivssemos usando JDBC puro, provavelmente teramos um DAO fazendo CRUD para cada classe mapeada para o banco de dados, que s no nosso modelo de exemplo geraria oito classes, enquanto com o Hibernate apenas uma classe necessria. Vejamos a interface que vai definir esse comportamento (e ser implementada por uma classe que use o Hibernate):
Listagem 5 interface para o DAO base da aplicao public interface GenericDao { public void save (Object objeto); public void update (Object objeto); public void delete(Object objeto); public List list (Class clazz); public List list (Class clazz, int firstResult, int maxResults); public List listByExample(Object example); public Object getById(Serializable id, Class clazz); }
Definir essa interface vai nos ajudar a desenvolver vrias implementaes do DAO (e at mesmo desenvolver uma que no utilize o Hibernate, por exemplo). A interface no demonstra em momento algum qual o mecanismo de persistncia que ela utiliza, os objetos que fizerem uso dela no vo estar presos a o Hibernate nem a nenhuma implementao especfica dela. Os mtodos que esto definidos so simples: save() Insere o objeto no banco de dados; update() Atualiza um objeto que j tenha sido inserido no banco de dados; delete() Deleta um objeto do banco de dados;
Grupo de Usurios Java http://www.guj.com.br Pgina 5
list() Retorna todos os objetos da classe passada como parmetro que esto no banco de dados, a segunda verso do mtodo faz a mesma coisa, mas com paginao de resultados; listByExample() Retorna todos os objetos que so parecidos com o objeto que foi passado como parmetro; getById() Retorna o objeto que tenha o identificador indicado e pertena a classe passada como parmetro; Com a interface definida, podemos comear a pensar em uma possvel implementao dela utilizando o Hibernate. Vamos comear por uma classe de apoio que vai nos ajudar a acessar e configurar o Hibernate na nossa aplicao na listagem 6:
Listagem 6 Classe utilitria para configurao e acesso ao Hibernate public class HibernateUtility { private static final SessionFactory factory; private static final ThreadLocal sessionThread = new ThreadLocal(); private static final ThreadLocal transactionThread = new ThreadLocal(); static { //Bloco esttico que inicializa o Hibernate, escreve o stack trace se houver algum problema e relana a exceo try { factory = new Configuration().configure().buildSessionFactory(); } catch (RuntimeException e) { e.printStackTrace(); throw e; } }
public static Session getSession() { if ( sessionThread.get() == null ) { Session session = factory.openSession(); sessionThread.set( session ); } return (Session) sessionThread.get(); }
public static void closeSession() { Session session = (Session) sessionThread.get(); if ( session != null && session.isOpen() ) { sessionThread.set(null); session.close(); } } public static void beginTransaction() { Transaction transaction = getSession().beginTransaction(); transactionThread.set(transaction); } public static void commitTransaction() { Transaction transaction = (Transaction) transactionThread.get(); if ( transaction != null && !transaction.wasCommitted() && !transaction.wasRolledBack() ) { transaction.commit(); transactionThread.set(null); } } public static void rollbackTransaction() { Transaction transaction = (Transaction) transactionThread.get(); if ( transaction != null && !transaction.wasCommitted() && !transaction.wasRolledBack() ) { transaction.rollback(); transactionThread.set(null); } } }
O cdigo da listagem 6 a nossa classe utilitria para acessar e configurar o Hibernate no nosso ambiente. Ela se inicia com as declaraes de trs variveis constantes, factory que vai guardar a SessionFactory do Hibernate e dois objetos ThreadLocal que vo guardar os valores da Session e da Transaction do Hibernate.
A implementao do DAO tambm segue a simplicidade que ns espervamos, a maioria dos mtodos conta com apenas uma simples linha de cdigo, apenas dois mtodos fugiram a regra, o mtodo list() com suporte a paginao e o mtodo listByExample() que recebe como parmetro um objeto de exemplo (que seja um objeto mapeado pelo Hibernate, como um objeto Curso no nosso modelo) e utiliza ele como exemplo para fazer uma busca no banco de dados. Voc j deve ter percebido uma coisa estranha no cdigo, em todos os mtodos ns chamamos uma sesso atravs da classe HibernateUtility, mas em nenhum momento ns iniciamos uma transao para nossas sesses e sem transaes no acontecem mudanas no banco de dados. Voc no deveria colocar cdigo de controle de transaes dentro dos seus objetos DAOs, porque pode chegar um momento onde voc precise de dois mtodos no mesmo DAO executem dentro de uma mesma transao (como fazer uma insero e logo aps uma atualizao de algum objeto), como voc no quer que apenas uma das aes tenha sucesso sozinha, necessrio manter tudo dentro de uma nica transao que deve ficar fora dos objetos DAO. Mas onde chamar as transaes? Existem algumas maneiras de se fazer esse controle, uma maneira comum adicionar aos objetos DAO mtodos que iniciem e terminem uma transao (parecidos com aqueles que temos na classe HibernateUtility), mas esse mtodo traz um outro problema, que deixar o controle de transaes aparente no seu cdigo, pois voc vai ter que chamar diretamente os mtodos e definir programaticamente quais so as transaes, o que termina deixando essa soluo at mesmo parecida com o controle de transaes dentro do prprio DAO. Em aplicaes web, uma prtica comum criar uma classe filtro (que implementa a interface javax.servlet.Filter) , essa classe vai abrir uma sesso e iniciar uma transao com o banco de dados para que todas as chamadas na requisio acessem essa sesso e essa transao, alm disso, essa classe tambm vai ser responsvel por fazer o rollback() da transao se alguma exceo for lanada e no fim vai fechar a sesso em todos os casos, liberando o cdigo da aplicao de se preocupar com isso. Vejamos na listagem 8 como esse filtro poderia ser implementado:
Listagem 8 filtro para uso do Hibernate em aplicaes web public class FiltroDoHibernate implements Filter { public void init(FilterConfig config) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HibernateUtility.beginTransaction(); try { chain.doFilter(request, response); HibernateUtility.commitTransaction(); Grupo de Usurios Java http://www.guj.com.br Pgina 8
} catch (HibernateException exception) { exception.printStackTrace(); HibernateUtility.rollbackTransaction(); } finally { HibernateUtility.closeSession(); } } public void destroy() { } }
Mas essa implementao tambm apresenta problemas, porque ela pressupe automaticamente que cada requisio feita ao sistema apenas uma transao e em algum momento pode ser necessrio o uso de vrias transaes dentro de uma mesma requisio (mesmo que na maioria dos casos seja mesmo apenas uma requisio por transao). Outro problema que ela provavelmente vai iniciar transaes at mesmo quando elas no so necessrias, o que pode gerar um gasto desnecessrio de recursos da aplicao e do banco de dados.
Um ponto importante a ser percebido na nossa implementao que a interface CursoDao extende a interface GenericDao forando a implementao dessa interface pela classe que implementar CursoDao. Isso no uma obrigao, voc pode simplesmente retirar essa necessidade, isso apenas uma
convenincia, pois eu posso simplesmente usar CursoDao quando estiver trabalhando com Cursos, em vez de ter que trabalhar com GenericDao e tambm com CursoDao. Voc no precisa criar DAOs para cada objeto do seu modelo, voc pode ter DAOs de mdulos, que renem comportamentos de mdulos especficos do seu sistema, trabalhando com vrios objetos diferentes. No nosso exemplo poderia existir um DAO especfico para o mdulo de inscrio nas turmas, que englobaria mtodos para lidar com Turmas, Alunos, Professores e Disciplinas. Quando estiver montando a sua aplicao, avalie a quantidade de mtodos e o tamanho dos DAOs, se eles ficarem grandes demais, est na hora de voc comear a dividir as responsabilidades.
No Spring voc pode trabalhar dois tipos diferentes de gerncia de transaes: Transaes declarativas: So as transaes que ficam definidas em arquivos de configurao. So o tipo mais simples e mais utilizado, porque deixam o seu cdigo livre de transaes e ainda podem ser trocadas de maneira simples, normalmente apenas editando um arquivo de configurao. Transaes definidas como atributos tambm podem ser consideradas declarativas, mas elas so mais complexas porque exigem a compilao da classes. Transaes programticas: So as transaes que ficam definidas dentro do seu cdigo. No so uma boa escolha porque se houver necessidade de mudar alguma coisa voc vai ter que mexer no seu cdigo, mas podem existir casos onde ela necessria (transaes programticas com o Spring no vo ser vistas nesse artigo). A interface base de suporte a transaes no Spring PlataformTransactionManager, cada plataforma (como JTA, JDBC, JMS e outras) tem uma implementao especfica dessa interface, que rene os servios essenciais para a gerncia de transaes em uma aplicao. As informaes de uma transao (como a propagao, o tempo de espera e o nvel de isolamento) so encapsulados em uma classe que implementa a interface TransactionDefinition. Tambm na interface TransactionDefinition que ns vamos encontrar as constantes que simbolizam o nvel de isolamento e a propagao das transaes vejamos essas constantes: Nvel de isolamento TransactionDefinition.ISOLATION_DEFAULT TransactionDefinition.ISOLATION_READ_UNCOMMITED Descrio o nvel geral definido no PlataformTransactionManager utilizado. Nesse nvel as transaes podem ler informaes que ainda no receberam um commit. Esse nvel no indicado porque voc pode estar trabalhando com informaes que no so confiveis. Esse nvel garante que voc s vai ler informaes que receberam um commit, o nvel mais comum de ser utilizado. Esse nvel garante que voc pode selecionar a mesma informao mais uma vez, mesmo que ela tenha sido alterada durante uma outra transao. Esse nvel garante que todas as transaes so executadas sequencialmente, uma aps a outra, sem que haja nenhuma influncia entre as informaes que eles lidam. o modo mais confivel mas tambm o que exige mais recursos da aplicao.
TransactionDefinition.ISOLATION_READ_COMMITED
TransactionDefinition.ISOLATION_REPEATABLE_READ
TransactionDefinition.ISOLATION_SERIALIZABLE
Descrio Indica que o uso de uma transao obrigatrio. Se j houver uma transao ela utilizada, se no houver nenhuma uma nova criada. Indica que se houver uma transao ela utilizada, se no houver nenhuma a ao executada de forma no-transacional. Indica que a existncia de uma transao obrigatria. Se no houver uma transao acontecendo uma exceo lanada. Indica que a ao deve ser executada sempre fora de uma transao. Se j houver uma transao ela suspendida e a ao
TransactionDefinition.PROPAGATION_SUPPORTS
TransactionDefinition.PROPAGATION_MANDATORY
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
executada e forma no-transacional. TransactionDefinition.PROPAGATION_REQUIRES_NEW Indica que uma nova transao sempre vai ser iniciada. Se j houver uma transao ocorrendo ela suspensa. Indica que nunca vai ser executado dentro de transaes. Se houver uma transao corrente uma exceo lanada. Executa em transaes aninhadas (uma transao acontecendo dentro de outra transao). Se no houver nenhuma transao ocorrendo se comporta de forma igual a PROPAGATION_REQUIRED
TransactionDefinition.PROPAGATION_NEVER
TransactionDefinition.PROPAGATION_NESTED
Agora que voc j entendeu a base do controle de transaes do Spring, vamos voltar para a nossa camada de persistncia, implementando a interface GenericDao com o Spring na listagem 11:
Listagem 11 Implementao da interface GenericDao com o Spring e Hibernate public class HibernateGenericDao extends HibernateDaoSupport implements GenericDao { public Serializable save(Object objeto) { return this.getHibernateTemplate().save(objeto); } public void update(Object objeto) { this.getHibernateTemplate().update(objeto); } public void delete(Object objeto) { this.getHibernateTemplate().delete(objeto); } public List list(Class clazz) { return this.getHibernateTemplate().loadAll(clazz); }
public List list(Class clazz, int firstResult, int maxResults) { return this.getHibernateTemplate() .executeFind( new CriteriaListCallback(clazz, firstResult, maxResults) ); } public List listByExample(Object example) { return this.getHibernateTemplate().findByExample(example); } public Object getById(Serializable id, Class clazz) { return this.getHibernateTemplate().get(clazz, id); } private class CriteriaListCallback implements HibernateCallback { private Class clazz; private Integer inicio; private Integer quantidade; public CriteriaListCallback(Class clazz, Integer inicio, Integer quantidade) { this.clazz = clazz; this.inicio = inicio; this.quantidade = quantidade; } public Object doInHibernate(Session session) throws HibernateException, Grupo de Usurios Java http://www.guj.com.br Pgina 12
A nova implementao da interface GenericDao tambm quase to simples quanto a anterior, utilizando a classe utilitria HibernateUtility, mas ela tem algumas diferenas muito importantes. Uma das principais que ela extende uma classe do Spring, HibernateDaoSupport, que fornece vrios mtodos e objetos utilitrios para se trabalhar com o Hibernate. Uma das principais caractersticas da classe HibernateDaoSupport o objeto HibernateTemplate, que pode ser acessado atravs to mtodo getHibernateTemplate(), ele oferece atalhos para vrios mtodos da Session do Hibernate, sem acessar a Session diretamente, como save(), update() e delete(). O HibernateTemplate tambm executa objetos que implementem a interface HibernateCallback, que serve para se definir lgicas mais complexas que necessitem realmente de acesso a um objeto Session. No nosso exemplo, criamos um objeto que implementa a interface HibernateCallback para executar a listagem de objetos com paginao. Veja que o objeto CriteriaListCallback tem trs propriedades, que so a classe que vai ser listada, o resultado inicial e a quantidade mxima de resultado, usando essa classe ns acessamos um objeto Session sem correr nenhum risco de atrapalhar o controle de transaes, j que ele vai ser chamado atravs do objeto HibernateCallback. Sempre que voc necessitar de uma lgica complexa nas suas queries do Hibernate, monte um objeto que implemente a interface HibernateCallback para fazer o servio. Agora que ns j temos uma implementao com o Hibernate, vamos ver como ficaria a configurao do Spring para controlar a transao do nosso DAO:
Listagem 12 Configurao do Spring
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource"> <ref local="dataSource"/> </property> <property name="mappingResources"> <value> br/edu/cefetpb/Curso.hbm.xml, br/edu/cefetpb/Disciplina.hbm.xml, br/edu/cefetpb/Turma.hbm.xml, br/edu/cefetpb/Pessoa.hbm.xml, br/edu/cefetpb/Aluno.hbm.xml, br/edu/cefetpb/Professor.hbm.xml, br/edu/cefetpb/Endereco.hbm.xml </value> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect"> org.hibernate.dialect.MySQLDialect
Grupo de Usurios Java http://www.guj.com.br Pgina 13
</prop> <prop key="show_sql">true</prop> <prop key="hibernate.generate_statistics">true</prop> <prop key="hibernate.use_sql_comments">true</prop> </props> </property> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="url"> <value>jdbc:mysql://localhost/hibernate?autoReconnect=true</value> </property> <property name="username"> <value>root</value> </property> <property name="password"> <value></value> </property> <property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> </property> <property name="initialSize"> <value>5</value> </property> <property name="maxActive"> <value>20</value> </property> <property name="maxIdle"> <value>5</value> </property> <property name="poolPreparedStatements"> <value>true</value> </property> </bean> <bean id="daoAlvo" class="br.edu.cefetpb.spring.HibernateGenericDao"> <property name="sessionFactory"> <ref local="sessionFactory"/> </property> </bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory"> <ref local="sessionFactory"/> </property> <property name="dataSource"> <ref local="dataSource"/> </property> </bean> <bean id="daoGenericoTransacional" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager"> <ref local="transactionManager"/> </property> <property name="target"> <ref local="daoAlvo"/>
Grupo de Usurios Java http://www.guj.com.br Pgina 14
</property> <property name="transactionAttributes"> <props> <prop key="delete*"> PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED </prop> <prop key="save*"> PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED </prop> <prop key="update*"> PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED </prop> <prop key="get*"> PROPAGATION_SUPPORTS, readOnly </prop> <prop key="list*"> PROPAGATION_SUPPORTS, readOnly </prop> </props> </property> </bean> </beans>
Na nossa configurao do Spring, foram definidos 5 objetos, que vo ser utilizados para o nosso controle de transaes, vejamos um grfico de relacionamentos entre eles:
Imagem 1 Beans do Spring
Agora vamos entender o que cada um desses objetos no arquivo de configurao do Spring: dataSource: o objeto que faz conexo com o banco e utilizado pela SessionFactory do Hibernate para criar novas conexes JDBC, no nosso exemplo foi escolhido o data source do projeto Jakarta Commons DBCP (o mesmo data source utilizado pelo Tomcat); sessionFactory: a SessionFactory do Hibernate, mas configurada por uma classe do Spring, a classe org.springframework.orm.hibernate3.LocalSessionFactoryBean. Voc pode at utilizar a prpria SessionFactory do Hibernate, mas vai perder muito das facilidades que o Spring lhe fornece. Alm das configuraes normais do Hibernate, esse objeto tambm recebe um dataSource para obter conexes JDBC. transactionManager: aqui ns j estamos chegando no controle de transao. O transactionManager escolhido foi o HibernateTransactionManager, que a implementao da interface PlatformTransactionManager (citada acima) que lida com as transaes do Hibernate. Esse recebe como dependncias a sessionFactory do Hibernate e (opcionalmente) o dataSource que est sendo utilizado. daoAlvo: a nossa implementao do Spring para o GenericDao, como dependncia ele recebe a sessionFactory do Hibernate. No esse o objeto que ns vamos utilizar, porque ele ainda no transacional, ele s est aqui para ser transformado em um objeto transacional. Por ltimo temos o daoGenericoTransacional. Esse o mais complicado dos nossos objetos, ele um objeto proxy (um objeto que serve de acesso para outro objeto ou alguma outra coisa). esse objeto proxy que faz todo o controle de transaes no nosso objeto daoAlvo e esse objeto que ns vamos utilizar na nossa aplicao.
Ele encapsula o nosso objeto DAO e usa o transactionManager para montar as transaes conforme ele est configurado na propriedade transactionAtributes. Essa propriedade um objeto Properties onde as chaves so os nomes dos mtodos e os valores so as definies de isolamento e do comportamento de propagao das transaes, como por exemplo na propriedade seguinte:
protected void setUp() throws Exception { super.setUp(); context = new ClassPathXmlApplicationContext("applicationContext.xml"); } public void testInserirCurso() { GenericDao dao = (GenericDao) context.getBean("daoGenericoTransacional"); Integer quantidadeAnterior = dao.list(Curso.class).size(); Curso curso = new Curso(); curso.setNome("DSI"); curso.setDescricao("Desenvolvimento de Software Para internet"); dao.save(curso); if ( quantidadeAnterior.equals( dao.list(Curso.class) ) ) { this.fail("A quantidade de cursos deveria ter sido aumentada"); } }
Como voc percebeu, no existe nada de estranho no cdigo, apenas pegamos o bean do Spring e chamamos os mtodos nele, em nenhum momento o cdigo cliente (o do teste do JUnit) soube que estava trabalhando com um objeto proxy, nem mesmo com um banco de dados e exatamente esse nvel de abstrao que ns estamos procurando. A gerncia de transaes continua no DAO? No nosso exemplo, o DAO se tornou um objeto transacional, mas nada impede que qualquer outro objeto torne-se transacional, o DAO foi escolhido apenas por simplicidade. Voc poderia ter, por exemplo, um objeto GerenciadorDeTransferncias com um mtodo parecido com o da listagem 13:
Listagem 13 mtodo de transferncia de contas public void transferir (ContaCorrente sacado, ContaCorrente beneficiado, Double valor) { sacado.remover(valor); beneficiado.adicionar(valor); this.getGenericDao().update(sacado); this.getGenericDao().update(beneficiado); }
E claro que voc quer que ele execute todo dentro de uma nica transao, porque seno voc pode retirar dinheiro do sacado e no depositar no beneficiado, o que seria um grande problema pro seu banco e pro seu emprego tambm! Bastaria configurar o objeto GerenciadorDeTransfncias como um proxy, do mesmo jeito que foi configurado o daoGenericoTransacional, como ns vamos ver na listagem 14:
Listagem 14 parte da configurao do Spring para o GerenciadorDeTransferncias transacional
<bean id="gerenciadorDeTransferenciasAlvo" class="br.edu.cefetpb.banco.GerenciadorDeTransferencias"> <property name="genericDao"> <ref bean="daoAlvo"/> </property> </bean> <bean id="gerenciadorDeTransferencias" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager"> <ref local="transactionManager"/> </property> <property name="target"> <ref local="gerenciadorDeTransferenciasAlvo"/> </property> <property name="transactionAttributes"> <props> <prop key="transferir*"> PROPAGATION_REQUIRED, ISOLATION_SERIALIZABLE </prop> </props> </property> </bean>
Como voc pode perceber, o caminho foi o mesmo, s que em vez de criar um proxy com base no DAO, ns criamos um proxy com base no GerenciadorDeTransferencias, alm disso, ns colocamos o DAO que no transacional no gerenciador, porque se ns colocssemos o transacional poderamos perder informaes (e voc perder o emprego =] ). No fim, voc deve escolher onde o seu controle de transaes deve ficar e voc tem toda a liberdade pra isso utilizando o Spring, pois apenas editando o arquivo de configurao dele voc pode mudar completamente o controle de transaes da sua aplicao, sem ter que recompilar nem uma linha de cdigo. Spring em aplicaes web Trabalhando com aplicaes web junto com o esquema de gerncia de transaes do Spring, voc tem ainda um filtro que mantm sempre uma sesso aberta para cada requisio as suas pginas JSP. Com essa sesso aberta voc evita problemas de carregamento de objetos e vai ter sempre uma sesso aberta para fazer o lazy-load dos seus objetos, evitando assim algumas das mais chatas excees lanadas pelo Hibernate, como LazyInitializationException (que acontece quando voc tenta acessar uma coleo ou objeto no carregado sem uma sesso aberta) e NonUniqueObjectException (que acontece quando voc adiciona o mesmo objeto duas vezes a mesma Session). O filtro o org.springframework.orm.hibernate3.support.OpenSessionInViewFilter, basta adicionar ele com filtro no seu web.xml para todas as pginas que precisem de uma sesso do Hibernate.
Realmente, a performance que pode ser conseguida com acesso direto via JDBC realmente maior se compararmos diretamente com a performance de uma aplicao que use um desses mecanismos. Mas claro que as ferramentas de ORM se preocupam com performance, uma das principais demonstraes disso que praticamente todas elas tem um esquema de cache de objetos, para evitar idas desnecessrias ao banco de dados. Os caches de objetos, quando bem montados, podem tornar a aplicao ainda mais rpida do que a mesma aplicao feita com JDBC, que dificilmente vai ser capaz de lidar com caches de objetos de maneira satisfatria (a no ser que alm de fazer a camada de persistncia na mo voc ainda v montar um esquema de cache, quando voc terminar tudo, ser que ainda vai ter prazo pra fazer a aplicao?). O Hibernate tem um sistema de cache automtico e no-desligvel, que o cache de primeiro nvel. O cache de primeiro nvel um cache relacionado a uma nica sesso (Session) do Hibernate, esse cache garante que voc vai acessar sempre os mesmos objetos dentro de uma nica sesso. Por exemplo, se voc fizer um load() em uma sesso passando o mesmo identificador e a mesma classe duas vezes o Hibernate deve retornar o mesmo objeto, isso garante que voc no vai trabalhar com objetos que tenham valores invlidos dentro de uma mesma sesso. Esse cache no tem nenhuma preocupao com performance, o seu objetivo garantir a integridade das informaes dentro de uma mesma Session. O outro sistema de cache, que opcional e o que ns vamos ver aqui, o cache de segundo nvel, que o cache de objetos do Hibernate. O cache de segundo nvel pode ser em cluster ou por SessionFactory (ou por processo). O que ns vamos ver aqui o cache por SessionFactory (tambm existe o cache de queries, mas no vamos v-lo neste artigo). Antes de comear a planejar o cache da sua aplicao voc deve levar algumas coisas em considerao e uma das mais importantes que se houverem outras aplicaes fazendo inseres e atualizaes no mesmo banco de dados que o Hibernate est conectado, melhor nem utilizar o cache, porque a outra aplicao pode atualizar informaes que podem no ser percebidas pelo Hibernate por elas estarem em cache. Outro porm se a sua aplicao tem mais inseres e atualizaes do que leituras, nesse caso o cache pode at mesmo diminuir a performance da aplicao. Pense bem antes de resolver que a sua aplicao precisa de um cache e escolha os objetos que devem ficar no cache, algumas indicaes sobre bons candidatos: Objetos que contm apenas meta-informao, como por exemplo um Caderno em um jornal, os cadernos normalmente simplesmente indicam qual o tipo do assunto abordado; Objetos pouco atualizados e lidos com muita freqncia. Mais um exemplo jornalstico so as notcias, voc no vai ver uma notcia ser atualizada vrias vezes depois que ela foi inserida (a no ser em casos bem especficos); Objetos onde a incoerncia de informaes no vai causar danos ao funcionamento da aplicao; Se voc tem objetos que so atualizados com muita freqncia ou onde muito importante que eles estejam sempre atualizados, no os coloque no cache, isso pode lhe dar muita dor de cabea. Esse cache mantm os objetos utilizados na memria ou em disco e extremamente simples de ser utilizado. No seu mapeamento, basta adicionar um novo n, o <cache/>, como no nosso exemplo na listagem 15:
Listagem 15 Exemplo de mapeamento com configurao de cache
<class name="Curso"> <cache usage="read-write" region="curso"/> <id name="id"> <generator class="increment"/> </id> <property name="nome"/> <property name="descricao"/>
<set name="disciplinas" inverse="true" cascade="save-update"> <key column="Curso_id"/> <one-to-many class="Disciplina"/> </set> </class>
A configurao do cache extremamente simples, voc indica o modo de utilizao (usage) e a regio onde os objetos vo ser encontrados (region). Uma regio um espao de cache que vai ser utilizado para guardar os objetos, voc pode ter todos os seus objetos em uma nica regio ou pode ter uma regio diferente para cada objeto que vai para o cache. O melhor trabalhar com uma regio para cara objeto, pois assim se voc precisar retirar todos os objetos de um certo tipo do seu cache, pode simplesmente fazer um evict() na regio onde ele est e todos os objetos vo ser retirados do cache.
Para habilitar o cache na nossa aplicao, ns precisamos escolher uma implementao da interface CacheProvider e adicionar ela a configurao do Hibernate. Ns vamos utilizar a implementao EhCacheProvider que uma implementao no-clusterizvel da interface CacheProvider para o Hibernate (isso significa que ela funciona como cache de processo, mas no deveria ser utilizada em um ambiente de cluster). Para indicar isso, vamos adicionar uma linha na nossa configurao do Hibernate no Spring (ela tambm pode ser adicionada da mesma forma a configurao normal do Hibernate):
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
Essa configurao indica que ns vamos utilzar o EhCache como implementao de cache para a nossa aplicao. Agora que j indicamos qual a implementao, temos que definir as configuraes do EhCache para a nossa aplicao na listagem 16 (o arquivo deve se chamar ehcache.xml e estar na raiz do classpath da sua aplicao):
Listagem 16 Exemplo de configurao do EhCache
<ehcache>
<diskStore path="user.home"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /> <cache name="curso" maxElementsInMemory="50" eternal="true" timeToIdleSeconds="0" timeToLiveSeconds="0" overflowToDisk="false" /> </ehcache>
O arquivo de configurao do EhCache tambm simples, o primeiro elemento, <diskStore/> contm a localizao de onde deve ficar o arquivo que contm o cache (se algum objeto no for ser guardado na memria). Voc pode indicar um diretrio ou usar os apelidos: user.home indica o diretrio raiz do usurio; user.dir indica o diretrio corrente para aplicao; java.io.tmpdir indica o diretrio temporrio pra a mquina virtual; O segundo elemento, <defaultCache/>, indica as configuraes gerais para o cache na aplicao, ele s vai ser utilizado se o objeto enviado para o cache no estiver relacionado a nenhuma regio. Os atributos so: maxElementsInMemory a quantidade mxima de objetos que vo ficar em memria, alm disso, o cache comea a enviar os objetos para o disco (apenas se o valor de overflowToDisk for true); eternal indica se o conjunto eterno. Conjuntos eternos no levam e condio o tempo de vida nem o tempo se uso. Esse modo normalmente utilizado para objetos que tenham a utilizao no cache de read-write ou read-only. timeToIdleSecons indica a quantidade de segundos que um objeto pode passar no cache sem ser utilzado, aps o limite ele retirado; timeToLiveSeconds indica a quantidade de segundos que um objeto pode passar no cache, aps o limite ele retirado; overflowToDisk indica se os objetos devem ou no ser enviados para um arquivo no disco quando a quantidade mxima de objetos em memria for alcanada; O terceiro (e ltimo) elemento o <cache/>, que contm as informaes especficas de cada regio de cache. Alm dos atributos de <defaultCache/>, esse elemento tambm contm o atributo name, que o nome da regio que identifica esse cache (no nosso exemplo ns demos o nome curso que o mesmo nome definido na regio do mapeamento da classe Curso).
Mais detalhes
Sites: Hibernate http://www.hibernate.org/ Spring http://www.springframework.org/ EhCahe - http://ehcache.sourceforge.net/ Referncias: Pro Spring. Harrop, Rob; Machacek, Jan. Editora Apress, 2005. Hibernate In Action. Bauer, Christian; King, Gavin. Editora Manning, 2004.
Concluso
Agora voc j sabe como montar uma camada de persistncia com o Hibernate, como integrar ele com o Spring e ainda como configurar o cache para aumentar ainda mais a performance da sua aplicao. O Hibernate continua sendo a ferramenta de mapeamento objeto/relacional mais utilizada na comunidade Java, mas sempre importante ficar de olhos abertos para as novidades do mercado. A especificao EJB 3.0 est praticamente pronta e segundo os seus defensores ela vai funcionar normalmente fora dos servidores de aplicao. Se a promessa se tornar realidade (o Hibernate j tem uma implementao da verso atual da implementao, o EntityManager) podemos esperar por uma verdadeira guerra entre os fornecedores de ferramentas para entrar no mercado e com essa concorrncia ns s temos a ganhar. Outro fator interessante a configurao do Hibernate atravs de annotations, que ainda est em fase de testes mas que em pouco tempo deve ser tornar uma maneira mais simples de se lidar com os quilos de configurao necessrios para se utilizar em uma aplicao com o Hibernate (e aplicaes Java em geral). Annotations ajudam mas continuam sendo apenas uma mudana de lugar para a configurao, que saiu dos arquivos XML e foi parar dentro do cdigo Java, trazendo mais uma complicao, que ter sempre que recompilar a aplicao quando mudanas ocorrerem, alm de ser mais complicado adicionar named queries em arquivos de classes, pois elas realmente misturam cdigo de acesso a banco de dados dentro das suas classes de modelo. O exagero e o cansao dos desenvolvedores com a edio de tantos arquivos XML to grande que novos frameworks simplesmente aboliram o uso de configurao e buscam informaes do prprio cdigo e organizao da aplicao, o movimento de conveno sobre configurao que est tomando invadindo os desenvolvedores cansados com tantos arquivos para manter. Vamos esperar que esse movimento realmente cresa na comunidade Java pra podermos diminuir a quantidade de configurao e nos preocuparmos mais com o que as aplicaes devem realmente fazer.
Maurcio Linhares de Arago Junior (mauricio.linhares@gmail.com) graduando em Desenvolvimento de Software para Internet no CEFET-PB e Comunicao Social (habilitao Jornalismo) na UFPB. membro do Paraba Java Users Group (PBJUG), do Grupo de Usurios GNU/Linux da Paraba (GLUG-PB) e moderador dos fruns do Grupo de Usurios Java (GUJ).