Auditoria Avançada de Persistência com Hibernate, JPA e Envers - PDF
Download "Auditoria Avançada de Persistência com Hibernate, JPA e Envers"
Ilda Rosa Alves
1 a r t i g o José Yoshiriro Ajisaka Ramos bacharel em Sistema de Informação (IESAM). Mestrando em Ciência da Computação (UFPA). Instrutor na Equilibrium Web e na UAB. Engenheiro de software do Tribunal de Justiça do Pará. Possui as certificações SCJP, SCWCD e SCBCD. Trabalha com Java há 6 anos. Márcia Amaral bacharel em Ciência da Computação (UFPA). Analista de Sistemas do Tribunal de Justiça do Pará. "Java Girl" há mais de 8 anos. Eterna estudante de novas tecnologias. Auditoria Avançada de Persistência com Hibernate, JPA e Envers Aprenda como implementar um robusto e completo sistema de auditoria de persistência com o Hibernate em conjunto com o Envers A auditoria sobre as operações de banco de dados é importantíssima, quando não, indispensável em sistemas de porte corporativo. Essa importância surge a partir de necessidades como correção de inconsistências de dados, detecção de incidentes de segurança etc. Caso os requisitos de um sistema exijam muitos detalhes na auditoria de persistência, torna-se necessário o uso de ferramentas de alto nível para cumpri-los com eficiência. Este artigo visa apresentar o caminho das pedras para implementar auditoria em persistência objeto relacional com Hibernate, JPA e Envers, apresentando um grande leque de recursos para esse tipo de tarefa. A uditar a persistência permite analisar operações que envolvem o acesso a bancos de dados. Uma boa auditoria de persistência deve fornecer informações sobre as operações nas tabelas envolvidas: O nível de detalhe das informações geradas pela auditoria deve ser suficiente para que seja possível desfazer operações sempre que necessário. Muito provavelmente, o tipo de auditoria mais usado atualmente é o baseado em Stored Procedures e Triggers de bancos de dados. As técnicas abordadas neste artigo não entram em conflito com tais mecanismos, podendo até trabalhar em conjunto. Este artigo visa apresentar uma robusta e flexível ferramenta para implementação de auditoria de persistência, composta pelos frameworks Hibernate, JPA e Envers tendo como foco principal o último, que é quem possibilita que a tecnologia de mapeamento objeto relacional dos demais possa contar com um forte sistema de auditoria. 51
2 Hibernate e Envers O Hibernate é um framework de mapeamento objeto relacional muito popular entre os desenvolvedores Java. Atualmente, ele pode ser usado de duas maneiras: como Hibernate Core ou provedor de persistência JPA (Java Persistence API). Na primeira, não se depende dos recursos da JPA, podendo lançar mão de vários recursos exclusivos, como Criteria, por exemplo. Os recursos exclusivos do Hibernate Core que possibilitam a implementação de auditoria são os Interceptors e Events, que não fazem parte do escopo deste artigo. Na segunda, ele se comporta como uma implementação da JPA. A biblioteca que permite que o Hibernate seja usado como provedor JPA é a Hibernate Entity Manager. É só com esse tipo de uso que é possível usar o framework Envers para auditoria de persistência. O JPA é a tecnologia padrão de mapeamento objeto relacional do Java. Na edição 37 da Mundoj, o artigo Auditando persistência com JPA apresentou o mecanismo de Listeners de Callbacks, que permite a implementação de auditoria com JPA. Neste artigo, foram abordados os pontos fracos de se implementar auditoria com JPA, que são a não escuta dos Listaners aos métodos invocados de Query e a não-obtenção do estados antes e depois dos objetos envolvidos na operação de persistência. Conforme será apresentado nas próximas seções, o Envers supera essas limitações e ainda oferece funcionalidades exclusivas. O Envers é um framework que permite criar auditoria através do controle de versões de persistência em mapeamentos objetos relacionais feitos com o Hibernate como provedor JPA. A grande inovação é a geração e alimentação automáticas de tabelas que permitem o controle de versões dos conteúdos das tabelas mapeadas. Cada vez que uma tabela sofre alterações em seus registros, uma nova versão dela é gerada pelo Envers. Assim, este framework torna opcional a criação de tabelas e programação de módulos de sistemas para armazenar as informações de auditoria. O Hibernate e o Envers são mantidos pela JBoss.org e, em 30 de outubro de 2008, o Envers tornou-se um módulo do Hibernate. Todavia, sua biblioteca ainda não vem junto do Hibernate, sendo necessário fazer seu download separadamente. Segundo o site da JBoss, o Hibernate 3.5 já trará as bibliotecas do Envers juntos das suas, assim como já ocorre com C3P0, Ehcache e outros frameworks. Nessa versão, provavelmente será possível usar o Envers também em conjunto com o Hibernate Core e não apenas com o Hibernate como provedor JPA. Configuração do Hibernate. Deve-se configurar o Hibernate para que ele trabalhe em conjunto com o Envers. Na Listagem 1 há um exemplo de como isso pode ser feito em arquivo de configuração JPA. Listagem 1. Configuração do Envers no persistence.xml de um projeto Hibernate/JPA. <persistence-unit > <properties> <!-- configurações comuns --> <property name="hibernate.ejb.event.post-insert" <property name="hibernate.ejb.event.post-update" <property name="hibernate.ejb.event.post-delete" <property name="hibernate.ejb.event.pre-collection-update" <property name="hibernate.ejb.event.pre-collection-remove" <property name="hibernate.ejb.event.post-collection-recreate" </properties> </persistence-unit> Em seu modo padrão de configuração (o que está sendo apresentado até agora), o Envers precisa de uma tabela auxiliar para cada tabela oficial do sistema cuja classe de entidade for marcada para auditoria, além de uma tabela auxiliar única por todo o projeto. Por isso, é uma boa prática configurar a criação automática de tabelas com a propriedade hbm2ddl. auto do Hibernate. A figura 1 mostra como seria um DER preparado para o trabalho com o Envers se houvessem apenas duas tabelas no sistema (tabela jogador e tabela time_futebol ). Detalhes sobre essas tabelas estão na seção Armazenamento das Informações pelo Envers. Criando auditoria de persistência com o framework Envers Até o fechamento desta edição, a versão atual do Envers era GA, compatível com as versões 3.3.x do Hibernate. É com estas configurações que este artigo irá apresentar sua proposta. A seguir, um passo-a-passo de como configurar o Envers em seu projeto. Instalação de bibliotecas. A biblioteca do Envers é composta de um único arquivo, o envers-x.ga-hibernate-3.y.jar, em que X e Y são as versões do Envers e Hibernate da biblioteca, respectivamente. Outras bibliotecas necessárias são: individual. A tabela revinfo é usada por todas as tabelas com sufixo _aud. E existe uma tabela com esse sufixo para cada tabela cuja entidade for marcada para auditoria com o Envers (veja como fazer isso no próximo passo). 52
3 Indicar quais tabelas serão auditadas. Com uma simples anotação é possível indicar quais classes de entidade fiquem sob a escuta do Envers e passarão a fazer parte de seu controle de versão. É a import org.hibernate.envers.audited. Na Listagem 2 há um exemplo de como usá-la. Listagem 2. Marcando a classe de entidade Jogador para auditoria com o Envers. @Table(name = "jogador") public class Jogador implements java.io.serializable { // campos e métodos Se estes três passos forem executados corretamente, sua aplicação já estará sob a auditoria e controle de versão de tabelas do Envers. Armazenamento das informações pelo Envers Vamos supor que na tabela time_futebol foram feitas as seguintes operações a partir de um projeto que usa Hibernate, JPA e Envers configurado no modelo padrão: 1. dois times foram inseridos; 2. o registro de um time sofreu alterações; 3. o registro de um time foi excluído. As figuras 2 e 3 mostram como ficariam as tabelas time_futebol_aud e revinfo, respectivamente. As tabelas de auditoria individuais, as que terminam com _ possuem os mesmos campos das tabelas as quais auditam e mais estes: indica o tipo de operação, em que: indica novo registro indica edição de registro indica exclusão faz a ligação com o campo de mesmo nome na tabela. O Envers usa uma tabela para centralizar o controle de operações, a re-. Ela possui os seguintes campos: faz a ligação com as tabelas de auditoria individuais (as de final _ ). O valor de REV é único por aplicação que usa o Envers, independentemente de quantas tabelas estão sendo auditadas. indica o momento exato (o timestamp ) em que a operação ocorreu. Recursos avançados de auditoria com Envers Os recursos abordados até aqui são suficientes para um sistema mínimo de auditoria, mas não para um sistema profissional em um ambiente corporativo. Para tal, é necessário saber pelo menos que usuário foi responsável pelas operações realizadas. É hora de "lançar mão" do recurso de escuta de revisão (Revision Listener) do Envers e criar uma classe para ser a entidade de revisão do projeto. Uma entidade de revisão é a classe que será usada para representar a tabela revinfo, portanto, deve estar mapeada como uma entidade JPA. Ela pode ser criada estendendo a classe org.hibernate.envers.defaultrevisionentity e criando os campos que achar importante para a auditoria. Essa classe também deve ser anotada para indicar qual classe de escuta de revisões (Revision Listener) irá gerenciá-la. Há um exemplo de entidade de revisão na Listagem 3. Se você preferir que sua tabela central de auditoria não se chame revinfo, basta usar outra palavra no atributo name na ou mesmo omiti-lo para aproveitar o nome da própria classe. Uma característica fantástica deste framework é que ele escuta tanto as operações feitas os métodos CRUD da interface EntityManager (persist, merge,remove e find) quanto às operações feitas pelo uso do método executeupdate() da interface Query. Como o leitor deve ter percebido, o Envers torna muito produtiva a criação de auditoria ao acesso a bancos de dados, uma vez que ele assume a responsabilidade pelas tabelas de auditoria as quais formam históricos parecidos com o que vemos em sistemas de controle de versão de arquivos, como CVS e Subversion, por exemplo. Caso não deseje estender a classe DefaultRevisionEntity para a entidade de revisão, você deve criar pelo menos dois campos devidamente encapsulados (com seus get e set ): 1. do tipo Integer ou int, anotado Number; 2. do tipo Long ou long, anotado Dependendo do tipo e da necessidade de um sistema, uma entidade de revisão pode conter campos como um número IP, um host name, o sistema operacional do usuário etc. A Listagem 4 mostra um exemplo de classe Revision Listener. 53
4 Listagem 3. Exemplo de classe de entidade de revisão. import org.hibernate.envers.revisionentity; import org.hibernate.envers.defaultrevisionentity; Listagem 5. Exemplo de recuperação de identificador usando o Seam. import org.jboss.seam.security.identity; // classe de escuta de revisões public class ExampleRevEntity extends DefaultRevisionEntity { private Integer idusuario; public void newrevision(object revisionentity) { Identity identity = (Identity) Component.getInstance( "org.jboss.seam.security.identity"); Integer idusuario = new Integer(identity.getPrincipal().getName()); // get e set de idusuario entidaderevisoes.setidusuario(idusuario); Listagem 4. Exemplo de classe de escuta de revisões. import public class AuditoriaFutebolListener extends RevisionListener { public void newrevision(object revisionentity) { EntidadeRevisoes entidaderevisoes = (EntidadeRevisoes) revisionentity; Listagem 6. Exemplo de recuperação de identificador usando o Struts2. import com.opensymphony.xwork2.actioncontext; public void newrevision(object revisionentity) { Integer idusuario = (Integer)ActionContext.getContext().getSession(). get("idusuarioatual"); Integer idusuario = ; //Recuperação do identificador do usuário // conforme a tecnologia usada entidaderevisoes.setidusuario(idusuario); entidaderevisoes.setidusuario(idusuario); Uma classe que implementa possui uma instância por. Por isso, muito cuidado com o uso de variáveis de instância. No exemplo da Listagem 4 existem várias formas de recuperar o identificador do usuário, dependo da tecnologia usada. Na Listagem 5 há um exemplo de como fazer isso com o framework JBoss Seam (através de busca de componente) e na Listagem 6, de como fazer com o Apache Struts2 (através de busca de atributo no escopo de sessão). Esse tipo de classe tem comportamento parecido com o dos Interceptors do EJB3 e do Struts2, afinal lida com uma responsabilidade transversal, no caso, a auditoria de persistência. Note que esse recurso não é indicado para estudar a operação em si se foi inclusão, exclusão ou alteração. O método newrevision(object revisionentity) é o único método da interface org.hibernate.envers. RevisionListener. O que interessa nesse tipo de classe é apenas atribuir informações extras para o registro da operação. Para escuta das operações CRUD, deve-se usar o recurso de escuta de eventos do Envers, que será apresentado na próxima seção. Recursos de flexibilização e personalização da auditoria com Envers O Envers tem uma proposta pronta de arquitetura de tabelas para prover auditoria e versionamento de persistência. É uma arquitetura muito inteligente e organizada que permite certas customizações como vistas no tópico anterior. Todavia, pode ser necessário que a arquitetura de tabelas de auditoria seja diferente da proposta pelo Envers. Também pode ser necessário usar outros recursos de registro de auditoria, como envio de s e criação de logs em arquivos-texto, por exemplo. O mecanismo do Envers que permite esse tipo de flexibilização é a escuta de eventos de auditoria (Audit Event Listener). 54
5 Escuta de eventos de auditoria (Audit Event Listener) Para poder usar o Envers em seu modo padrão, é preciso configurá-lo no JPA como visto no 2º passo do tópico Criando auditoria de persistência com o framework Envers. O que é feito na verdade é o mapeamento dos mecanismos de escuta de eventos de auditoria padrão do Envers. Para personalizar essas escutas, basta criar uma ou mais classes que estendam org.hibernate.envers.event.auditeventlistener (exemplo na Listagem 7) e indicá-las na configuração do JPA (exemplo na Listagem 8). Listagem 7. Exemplo de classe de escuta de eventos de auditoria personalizada. package mj.projetofutebol.listeners; import org.hibernate.envers.event.auditeventlistener; public class FutebolListener extends AuditEventListener{ public void onpostinsert(postinsertevent event) { // faça o que quiser com as informações do evento post insert public void onpostdelete(postdeleteevent event) { // faça o que quiser com as informações do evento post delete"" public void (PostUpdateEvent event) { // faça o que quiser com as informações do evento post update Todos os métodos de escuta de eventos de auditoria padrão do Envers possuem como parâmetro um objeto que estende org.hibernate.event. AbstractEvent. Esses objetos de evento permitem recuperar várias informações pertinentes sobre o evento e a entidade envolvida nele. Os principais métodos desses objetos de eventos estão na tabela 1. A Listagem 9 possui um pequeno exemplo de como usar alguns do métodos dessa tabela. Se for invocada a versão da superclasse dos métodos de escuta de eventos, o Envers tenta fazer seu procedimento padrão para a operação (registros numa tabela _aud e na tabela central de controle de revisões). Assim, caso seja necessário apenas acrescentar funcionalidades adicionais ao Envers (como envio de , por exemplo) sem alterar seu mecanismo padrão de armazenamento de dados de auditoria de persistência. Por exemplo: se você quiser que toda vez que um registro de determinada tabela seja excluído o administrador do sistema receba um , mas deseja manter a auditoria padrão do Envers, basta implementar o código de envio de no método onpostdelete(postdeleteevent event) e depois chamar super.onpostdelete(event). Recuperação dos dados de auditoria gerados pelo Envers Além de assumir o trabalho pesado pela alimentação das tabelas de auditoria, o Envers oferece um mecanismo robusto de recuperação de versões das tabelas. Na Listagem 10 há um exemplo de recuperação de versão com o uso da classe org.hibernate.envers.auditreader. Listagem 8. Configuração do Envers no persistence.xml de um projeto Hibernate/JPA. <persistence-unit > <properties> <!-- configurações comuns --> <property name="hibernate.ejb.event.post-insert" value="mj.projetofutebol.listeners.futebollistener" /> <property name="hibernate.ejb.event.post-update" value="mj.projetofutebol.listeners.futebollistener " /> <property name="hibernate.ejb.event.post-delete" value="mj.projetofutebol.listeners.futebollistener " /> </properties> </persistence-unit> O Envers possui um mecanismo de prevenção de criação de versões inúteis nas tabelas de auditorias individuais. Se for feito um pedido de atualização através do método () ou de uma query e os valores enviados forem os mesmos dos que já estão no banco de dados para o registro envolvido na operação, não será gerada uma nova versão da tabela em sua tabela de auditoria individual. E todos os mecanismos de escuta do Envers também são ignorados quando é feito um pedido de atualização que contém dados iguais aos já persistidos. A classe possui os recursos para recuperação de versões de dados de tabelas auditadas. Seus métodos estão na tabela 2. Existem duas outras formas de consultar as informações de auditoria geradas no banco de dados pelo Envers. Ambas utilizam a interface. Essa interface permite fazer consultas mais complexas em cima dos dados que foram armazenados. As queries do Envers são semelhantes às queries do Hibernate Criteria, facilitando a vida dos leitores que já têm familiaridade com a interface. A primeira forma é consultar uma entidade em uma determinada revisão. A segunda é consultar as revisões de uma determinada entidade. A Listagem 11 mostra um exemplo simples de uma consulta de entidade dada uma revisão. A consulta da Listagem 11 retorna uma lista de jogadores na revisão "revisionnumber" e cuja propriedade "nome_em_campo" tem o valor "Ronaldo". A Listagem 12 possui um exemplo simples de consulta a uma das revisões de uma entidade com AuditQuery. 55
6 Listagem 9. Exemplo de classe de escuta de eventos de auditoria personalizada. public class FutebolListener extends AuditEventListener{ public void onpostinsert(postinsertevent event) { Object entidade = event.getentity(); Serializable chave = event.getid(); // faça o que quiser com as informações do evento post insert Método Descrição Eventos Object getentity() Retorna a instância da entidade envolvida no evento. No caso de (), onpostinsert onpostdelete os campos da entidade recuperada por este método vêm com os valores alterados. Java.io.Serializable event. getid() Retorna a chave da entidade envolvida no evento. onpostinsert onpostdelete public void onpostdelete(postdeleteevent event) { Object[] getstate() Retorna um vetor de objetos com os valores dos campos da entidade já sob onpostinsert Object[] estadodeletado = event.getdeletedstate(); os efeitos do evento. // faça o que quiser com as informações do evento post delete Object[] getoldstate() Retorna um vetor de objetos com os valores dos campos da versão antiga public void (PostUpdateEvent event) { Object[] estadonovo = event.getstate(); Object[] estadoantigo = event.getoldstate(); // faça o que quiser com as informações do evento post update Object[]getDeletedState() da entidade (valores de antes de sofrer atualização). Assim pode-se ter acesso ao registro como estava antes da atualização. Equivale ao getstate(), porém só se aplica à escuta de post-delete onpostdelete public void onpreupdatecollection(precollectionupdateevent event) { PersistentCollection colecaoafetada = event.getcollection(); Serializable chavedaentidadequepossuiacolecao = event.getaffectedowneridornull(); String nomeentidadequepossuiacolecao = event.getaffectedownerentityname(); // faça o que quiser com as informações do evento pre update collection org.hibernate.collection. PersistentCollection Retorna a coleção de instâncias das entidades envolvidas no evento. onpostrecreatecollection onpreremovecollection onpreupdatecollection getcollection() Serializable getaffectedowneridornull() Retorna a chave da entidade à qual a coleção pertence. onpostrecreatecollection onpreremovecollection onpreupdatecollection String getaffectedownerentityname Retorna o nome da entidade à qual a coleção pertence. onpostrecreatecollection onpreremovecollection onpreupdatecollection Listagem 10. Recuperando uma determinada versão de registro da tabela time_futebol, sob a auditoria do Envers. import org.hibernate.envers.auditreader; import org.hibernate.envers.auditreaderfactory; public void recuperartimedeauditoria(integer idtime, Integer versao) { EntityManager em =. ; AuditReader leitorauditoria = AuditReaderFactory.get(em); TimeFutebol timerecuperado = leitorauditoria.find(timefutebol.class, idtime, versao); Listagem 11. Recuperando uma lista de versões de registro com AuditQuery. List jogadores = getauditreader().createquery(). forentitiesatrevision(jogador.class, revisionnumber).add(auditentity.property("nome_em_campo").eq("ronaldo")).getresultlist(); Listagem 12. Recuperando uma determinada versão de registro com AuditQuery. Number revision = (Number) getauditreader().createquery().forrevisionsofentity(jogador.class, false, true).setprojection(auditentity.revisionnumber().min()).add(auditentity.id().eq(idjogador)).add(auditentity.revisionnumber().gt(12)).getsingleresult(); A consulta da Listagem 12 retorna a menor versão na qual uma entidade da classe Jogador de id "idjogador" foi alterada, depois da versão 12. A classe AuditQuery tem uma série de métodos que permitem especificar restrições, fazer ordenação, limitar o número de resultados, assim como também é possível fazer uso de agregações e projeções. Abordar todos os seus métodos e combinações tornaria este trabalho muito extenso. Por isso encorajamos o leitor a consultar sua documentação da API (link nas referências) a fim de conhecer melhor os métodos e as possibilidades de utilização. 56
7 Método T find(class<t> cls, Object primarykey, Number revision) T findrevision(class<t> cls, Number revision) Descrição Recupera uma determinada versão (parâmetro revision ) de certo registro (parâmetro primarykey ) de uma determinada classe de entidade (parâmetro cls ). Recupera uma determinada versão (parâmetro revision ) de certo registro (identificado pelo parâmetro primarykey ) de uma determinada classe de entidade (parâmetro cls ). Listagem 13. Recuperando instâncias de objetos mapeados para chaves estrangeiras de entidades recuperadas pelo controle de versão do Envers. import org.hibernate.hibernate; public void recuperarjogadordeauditoria(integer idjogador, Integer versao) { EntityManager em =. ; AuditReader leitorauditoria = AuditReaderFactory.get(em); Jogador jogadorrecuperado = leitorauditoria.find(jogador.class, idjogador, versao); // objeto em Lazy eterno TimeFutebol timerecuperado = jogadorrecuperado.gettimefutebol(); T getcurrentrevision(class<t> revisionentityclass, boolean persist) java.util.list<number> getrevisions(class<?> cls, Object primarykey) Number getrevisionnumberfordate(java.util. Date date) java.util.date getrevisiondate(number revision) Recupera a versão atual da entidade da classe indicada (parâmetro revisionentityclass ). O parâmetro persist indica se o registro retornado deve ou não ser salvo no banco de dados caso ainda não esteja efetivamente salvo. Retorna a lista de versões existentes para um determinado registro (parâmetro primarykey ) de uma determinada classe de entidade (parâmetro cls ). Retorna a última versão de uma determinada data. Retorna a data de uma determinada versão de entidade (parâmetro revision ). Pontos fracos da auditoria com Hibernate e Envers Existe uma informação equivocada na documentação da API do Envers. Nela diz que o método find(class<t> cls, Object primarykey, Number revision) da classe AuditReader retorna null caso seja passada como parâmetro uma versão inexistente na tabela de auditoria individual da tabela. Todavia o que ocorre nesse caso é que o método retorna a última versão da entidade. Nos métodos de recuperação de versões anteriores das entidades sob o controle de versão, os campos mapeados para objetos de outras entidades não são recuperados. É como se estivessem em modo Lazy eternamente, pois o Hibernate não consegue recuperar seus campos em runtime. Para isso, é preciso uma técnica alternativa através do uso de recursos específicos do Hibernate, como mostra a Listagem 13. // única maneira de fazer o objeto ser preenchido Hibernate.initialize(timeRecuperado); Impacto na performance com uso do Envers Um trabalho de estudo do impacto na performance em uma aplicação com uso de Envers seria muito longo, mas, por dedução, acredita-se que o impacto é diretamente proporcional ao número de tabelas marcadas para auditoria e ao número de entidades envolvidas por operação. Para cada entidade mapeada para auditoria com Envers, a cada operação interceptada por ele, um método de escuta (seção Escuta de eventos de auditoria ) é invocado pelo menos uma vez. Caso a entidade possua mapeamentos de um para muitos ou de muitos para muitos em cascata com outras entidades também mapeadas para auditoria, o número de chamadas aos métodos de escuta é proporcional ao número de objetos envolvidos na operação. Portanto, é uma boa prática evitar o mapeamento de todas as classes de entidades e grandes projetos para evitar grandes perdas de performance. Porém, se os requisitos exigirem isso, uma menor performance é um preço a se pagar. Considerações finais Neste artigo foi abordado o framework Envers para auditoria de persistência com o Hibernate. Foram mostrados a configuração básica, alguns aspectos avançados e algumas dificuldades no uso deste framework. Os recursos oferecidos pelo Envers para auditoria com Hibernate e JPA são bem variados e poderosos, portanto, espera-se que este artigo estimule a criação de sistemas de auditoria de persistência robustos que são importantíssimos para a maioria dos tipos de sistema e indispensáveis em sistemas corporativos. Referências 57
Aumentando a performance do Hibernate com Ehcache
a r t i g o Aumentando a performance do Hibernate com Ehcache José Yoshiriro Ajisaka Ramos (jyoshiriro@gmail.com) bacharel em Sistemas de Informação (IESAM). Analista/desenvolvedor do TJ Pará. Trabalha