Archive for the ‘Hibernate’ Category.

Coletando métricas da sua aplicação Hibernate

Bem, esta é uma dica que apesar de simples, muita gente não conhece. O Hibernate pode ser configurado para gerar estatísticas de uso da SessionFactory. Em meus projetos já é comum rodar estas estatísticas uma vez que o projeto entra em homologação e ate mesmo em produção. Estas estatísticas vão ajudá-lo em alguns pontos:

  • Encontrar queries que podem estar consumindo muito tempo
  • Identificar possíveis objetos que podem ser colocados em cache
  • Verificar se os caches de 2nd level estão realmente sendo utilizados
  • Identificar possíveis campos que podem ser definidos como índices em suas tabelas

Por maior que seja nosso esforço como arquitetos de software, é muito difícil prevermos quais consultas serão mais usadas, quais objetos deveremos manter em cache, e quando sua equipe for grande, por mais que você tente, peça, xingue, sempre vai existir um desenvolvedor que não executa as queries no Hibernate tools e verifica se o SQL gerado é realmente a versão mais otimizada. Se eu ganhasse 1 real para cada join desnecessário que já encontrei em queries de projetos quando faço revisão de código, eu provavelmente poderia adiantar minha aposentadoria.

Fica aqui então uma forma de como verificar as estatísticas do hibernate. A abordagem que vou adotar é de disponibilizar um ManagedBean que irá exportar como serviço o HibernateStatistics. Vamos lá.

A aplicação de exemplo a ser monitorada, é uma aplicação de aprovação de horas (usando seam + jbpm_ que foi demonstrada neste blog neste link

Primeiro precisamos da nossa interface de MBean:

package com.furiousbob.hibernate.stats;
 
public interface HibernateStatsMBean {
	public void start() throws Exception;
	public void stop() throws Exception;
	public void create()throws Exception;
	public void destroy();
}

Bem, alguns poderão questionar aqui que poderíamos usar o ServiceMBeanSupport do JBoss, mas eu preferi manter a forma tradicional de mbeans ;)

Agora nossa implementação:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.furiousbob.hibernate.stats;
 
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
 
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.jmx.StatisticsService;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.system.ServiceMBeanSupport;
 
 
public class HibernateStats implements HibernateStatsMBean {
 
	private final String  SessionFactoryName = "java:/AprovacaoHorasSessionFactory";
 
 
	public void start() throws Exception {
		InitialContext ctx = new InitialContext();
		SessionFactory sessionFactory = (SessionFactory)ctx.lookup(SessionFactoryName);
		MBeanServer server = MBeanServerLocator.locateJBoss();
		ObjectName on = new ObjectName("Hibernate:type=statistics,application=AprovacaoHoras");
		StatisticsService service = new StatisticsService();
		service.setSessionFactory(sessionFactory);
		server.registerMBean(service, on);
	}
 
	public void stop() throws Exception {
		MBeanServer server = MBeanServerLocator.locateJBoss();
		ObjectName on = new ObjectName("Hibernate:type=statistics,application=AprovacaoHoras");
		server.unregisterMBean(on);
	}
 
	public void create() throws Exception {
	}
 
	public void destroy() {
	}

Como podem notar é realmente muito simples registrar o servico de stats do Hibernate. Note que estamos usando o nome da SessionFactory de forma fixa dentro do código, você poderia por exemplo, definir isto como um atributo de seu MBean de forma que este código possa ser usado em várias aplicações. O nome do MBean também reflete o nome da aplicação a ser monitorada, e é outro candidato a se tornar uma propriedade configurável.

A última parte de nosso exemplo é o arquivo jboss-service.xml que deve ser colocado na pasta META-INF de sua aplicação

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<server>
 
   <mbean name="MyApp:Hibernate=Stats" code="com.furiousbob.hibernate.stats.HibernateStats">
 		<depends>jboss:service=Naming</depends>
 		<depends>persistence.units:ear=AprovacaoHoras.ear,unitName=AprovacaoHoras</depends>
   </mbean>
</server>

Este arquivo irá informar que nosso Mbean possui uma dependência com a PersistenceUnit. E esta também deve sofrer uma pequena modificação, devemos adicionar as propriedades abaixo em nosso arquivo de configuração de persistencia:

<property name="hibernate.generate_statistics" value="true"/>
<property name="hibernate.session_factory_name" value="java:/AprovacaoHorasSessionFactory"/>

Agora precisamos de empacotar nossas classes como um Service ARchive (SAR). Um SAR é nada mais que um JAR com o jboss-service.xml em seu META-INF. Ao final da operação você deverá ter um arquivo SAR com a seguinte estrutura:

vinicius@cybertron:~/workspace/HibernateStats/dist$ jar -tvf HibernateStats.sar
     0 Wed Mar 04 09:09:40 BRT 2009 META-INF/
   106 Wed Mar 04 09:09:38 BRT 2009 META-INF/MANIFEST.MF
   305 Wed Mar 04 09:02:46 BRT 2009 META-INF/jboss-service.xml
     0 Wed Mar 04 09:03:02 BRT 2009 com/
     0 Wed Mar 04 09:03:02 BRT 2009 com/furiousbob/
     0 Wed Mar 04 09:03:02 BRT 2009 com/furiousbob/hibernate/
     0 Wed Mar 04 09:03:02 BRT 2009 com/furiousbob/hibernate/stats/
  2082 Wed Mar 04 09:03:34 BRT 2009 com/furiousbob/hibernate/stats/HibernateStats.class
   290 Wed Mar 04 09:03:02 BRT 2009 com/furiousbob/hibernate/stats/HibernateStatsMBean.class

Agora, acesse seu jmx-console de você deverá encontrar uma tela contendo um link para seu novo mbean. Existem dois mebans novos o primeiro:

AprovacaoHoras:Hibernate=Stats é o seu MBean que registra o serviço de stats

Hibernate:application=AprovacaoHoras,type=statistics: é o serviço de stats que você poderá coletar as métricas

Pronto, agora basta acessar o serviço e começar a coletar algumas métricas de seu Hibernate.

Aproveitem

Eu sei o que vocês fizeram no verão passado

Não se assustem! Não se trata de mais um filme de quinta categoria com adolescentes gostosas e sem nenhum sentido do cinema americano.
Vim aqui hoje falar do Envers. O Envers é um dos mais novos frameworks da JBoss a alcançar a maturidade GA (General Availability), a grande sacada do envers é gerar um histórico de todas modificações que ocorrem para uma determinada entidade. Quantas vezes você já não esbarrou em um requisito de projeto onde o cliente solicitava a capacidade de restaurar uma versão do objeto modificado há 3 meses atrás com os valores que este tinha na época? Ou então por motivo de alguma auditoria interna, você precisou guardar esta modificações em uma tabela “histórico”. Bem, eu já passei pelas duas situações, e tenho certeza que muitos de vocês já tiveram o mesmo problema.
Para resolver este problema chegou o Envers. O Envers funciona como um interceptor do Hibernate que atua em toda modificação que é realizada para uma classe anotada para ser versionada. Quando sua Session modifica a classe o envers gera uma entrada em uma tabela de histórico. A configuração é extremamente simples. E o seu uso mais ainda, mas achei que valia a pena fazer um pequeno post sobre ele.
Como todos exemplos do meu blog, vamos iniciar criando um novo projeto seam (consulte posts anteriores e a documentação do Seam para isso)
Uma vez que seu projeto esteja criado. Defina uma nova entidade:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.furiousbob.com;
 
import java.io.Serializable;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
 
import org.jboss.envers.Versioned;
 
@Entity
@Table(name="NEWS")
@Versioned
public class News implements Serializable {
	@Id
	@Column(name="ID")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private long id;
 
 
	@Column(name="TITLE")
	private String title;
 
	@Column(name="CONTENTS")
	private String contents;
 
	public long getId() {
		return id;
	}
 
	public String getTitle() {
		return title;
	}
 
	public String getContents() {
		return contents;
	}
 
	public void setId(long id) {
		this.id = id;
	}
 
	public void setTitle(String title) {
		this.title = title;
	}
 
	public void setContents(String contents) {
		this.contents = contents;
	}
}

Note na linha 16 a presença de uma anotação que define que nossa classe é agora versionada. O Envers suporta versionamento de classes completas ou de atributos.

Vá no console do seam, e execute ./seam generate-ui. Isso vai gerar todo o código e interfaces html para nosso CRUD.

Vamos então criar uma nova entidade. Ao clicar em create news, o sistema vai solicitar que você se autentique (admin:admin). Vamos então entrar com algum texto para nossa entidade, conforme a figura abaixo:

Criando uma entidade (clique para fullsize)

Criando uma entidade (clique para fullsize)

Clique em “Done” e você será redirecionado para a tela que apresenta a entidade. Uma das colunas possui uma ação para selecionar a entidade para edição “Select”, clique nesta ação e mude por exemplo o valor da propriedade contents conforme a figura abaixo:

Conteudo alterado de forma indevida (clique para fullsize)

Conteudo alterado de forma indevida (clique para fullsize)

Oops, alguem modificou o conteúdo! Como podemos restaurar a ordem no sistema? Se você estiver usando o Envers a tarefa é simples :). Baixe o envers, adicione o jar ao seu lib. Edite o arquivo deployed-jars-ear.list e adicione a lib do envers. Edite seu persistence.xml de forma que fique parecido com o abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<!-- Persistence deployment descriptor for dev profile -->
<persistence xmlns="http://java.sun.com/xml/ns/persistence" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" 
             version="1.0">
 
   <persistence-unit name="envers">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <jta-data-source>java:/enversDatasource</jta-data-source>
      <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
         <property name="hibernate.hbm2ddl.auto" value="update"/>
         <property name="hibernate.show_sql" value="true"/>
         <property name="hibernate.format_sql" value="true"/>
         <property name="jboss.entity.manager.factory.jndi.name" value="java:/enversEntityManagerFactory"/>
           <property name="hibernate.ejb.event.post-insert"   
             value="org.jboss.envers.event.VersionsEventListener" />  
          <property name="hibernate.ejb.event.post-update"   
             value="org.jboss.envers.event.VersionsEventListener" />  
          <property name="hibernate.ejb.event.post-delete"   
             value="org.jboss.envers.event.VersionsEventListener" />  
 
      </properties>
   </persistence-unit>
 
</persistence>

As linhas 17-22 são as responsáveis por ativar o envers. Se você checar seu SGDB agora, pode notar 2 tabelas novas: NEWS_versions e _revisions_info. Estas são as tabelas criadas automaticamente pelo Envers para você. A tabela _versions, guarda a informação da entidade versionada, é gerada uma para cada entidade anotada, a outra tabela é de controle interno do Envers.

Bem, vamos modificar nosso arquivo NewsList.xhtml (gerado pelo Seam) de forma que agora possamos verificar as versões existentes para cada uma de nossas entidades. A lista abaixo apresenta a nova coluna que inserimos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
 
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:s="http://jboss.com/products/seam/taglib"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:rich="http://richfaces.org/rich"
                template="layout/template.xhtml">
 
<ui:define name="body">
 
    <h:messages globalOnly="true" styleClass="message" id="globalMessages"/>
 
    <h:form id="newsSearch" styleClass="edit">
 
        <rich:simpleTogglePanel label="News search parameters" switchType="ajax">
 
            <s:decorate template="layout/display.xhtml">
                <ui:define name="label">contents</ui:define>
                <h:inputText id="contents" value="#{newsList.news.contents}"/>
            </s:decorate>
 
            <s:decorate template="layout/display.xhtml">
                <ui:define name="label">title</ui:define>
                <h:inputText id="title" value="#{newsList.news.title}"/>
            </s:decorate>
 
 
        </rich:simpleTogglePanel>
 
        <div class="actionButtons">
            <h:commandButton id="search" value="Search" action="/NewsList.xhtml"/>
        </div>
 
    </h:form>
 
    <rich:panel>
        <f:facet name="header">News search results</f:facet>
    <div class="results" id="newsList">
 
    <h:outputText value="The news search returned no results." 
               rendered="#{empty newsList.resultList}"/>
 
    <rich:dataTable id="newsList" 
                var="news"
              value="#{newsList.resultList}" 
           rendered="#{not empty newsList.resultList}">
        <h:column>
            <f:facet name="header">
                <s:link styleClass="columnHeader"
                             value="id #{newsList.order=='id asc' ? messages.down : ( newsList.order=='id desc' ? messages.up : '' )}">
                    <f:param name="order" value="#{newsList.order=='id asc' ? 'id desc' : 'id asc'}"/>
                </s:link>
            </f:facet>
            #{news.id}
        </h:column>
        <h:column>
            <f:facet name="header">
                <s:link styleClass="columnHeader"
                             value="contents #{newsList.order=='contents asc' ? messages.down : ( newsList.order=='contents desc' ? messages.up : '' )}">
                    <f:param name="order" value="#{newsList.order=='contents asc' ? 'contents desc' : 'contents asc'}"/>
                </s:link>
            </f:facet>
            #{news.contents}
        </h:column>
        <h:column>
            <f:facet name="header">
                <s:link styleClass="columnHeader"
                             value="title #{newsList.order=='title asc' ? messages.down : ( newsList.order=='title desc' ? messages.up : '' )}">
                    <f:param name="order" value="#{newsList.order=='title asc' ? 'title desc' : 'title asc'}"/>
                </s:link>
            </f:facet>
            #{news.title}
        </h:column>
        <h:column>
            <f:facet name="header">action</f:facet>
            <s:link view="/#{empty from ? 'News' : from}.xhtml" 
                   value="Select" 
                      id="news">
                <f:param name="newsId" 
                        value="#{news.id}"/>
            </s:link>
        </h:column>
        <h:column>
        	<f:facet name="header">Revisions</f:facet>
        	<s:link view="/newsRevision.xhtml" value="check revisions">
        		<f:param name="id" value="#{news.id}"/>
        	</s:link>
        </h:column>
    </rich:dataTable>
 
    </div>
    </rich:panel>
 
    <div class="tableControl">
 
        <s:link view="/NewsList.xhtml" 
            rendered="#{newsList.previousExists}" 
               value="#{messages.left}#{messages.left} First Page"
                  id="firstPage">
          <f:param name="firstResult" value="0"/>
        </s:link>
 
        <s:link view="/NewsList.xhtml" 
            rendered="#{newsList.previousExists}" 
               value="#{messages.left} Previous Page"
                  id="previousPage">
            <f:param name="firstResult" 
                    value="#{newsList.previousFirstResult}"/>
        </s:link>
 
        <s:link view="/NewsList.xhtml" 
            rendered="#{newsList.nextExists}" 
               value="Next Page #{messages.right}"
                  id="nextPage">
            <f:param name="firstResult" 
                    value="#{newsList.nextFirstResult}"/>
        </s:link>
 
        <s:link view="/NewsList.xhtml" 
            rendered="#{newsList.nextExists}" 
               value="Last Page #{messages.right}#{messages.right}"
                  id="lastPage">
            <f:param name="firstResult" 
                    value="#{newsList.lastFirstResult}"/>
        </s:link>
 
    </div>
 
    <s:div styleClass="actionButtons" rendered="#{empty from}">
        <s:button view="/NewsEdit.xhtml"
                    id="create" 
                 value="Create news">
            <f:param name="newsId"/>
        </s:button>
    </s:div>
 
</ui:define>
 
</ui:composition>

O que nos interessa aqui são as linhas 87-92, que adicionam uma nova coluna a tabela de listagem de entidades. Após ter feito este passo, sua tela deverá parecer com a tela abaixo:

Nova listagem de entidades (clique para fullsize)

Nova listagem de entidades (clique para fullsize)

Bem, precisamos agora criar a página que irá apresentar as versões de nosso objeto, a página newsRevision.xhtml é apresentada abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:s="http://jboss.com/products/seam/taglib"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:rich="http://richfaces.org/rich"
                template="layout/template.xhtml">
 
<ui:define name="body">
 
    <h:messages globalOnly="true" styleClass="message" id="globalMessages"/>
 
    <rich:panel>
        <f:facet name="header">Revision history for news id #{newsRevision.newsId}</f:facet>
			<rich:dataTable value="#{revisions}" var="_revision">
				<rich:column>
					<f:facet name="header">Title</f:facet>
					#{_revision[0].title}
				</rich:column>	
				<rich:column>
					<f:facet name="header">Contents</f:facet>
					#{_revision[0].contents}
				</rich:column>	
				<rich:column>
					<f:facet name="header">Version</f:facet>
					#{_revision[1]}
				</rich:column>
				<rich:column>
					<f:facet name="header">Modified at</f:facet>
					#{newsRevision.fetchDate(_revision[1])}
				</rich:column>
				<rich:column>
					<f:facet name="header">Action</f:facet>
					<s:link action="#{newsRevision.replaceRevision(_revision[0])}" value="Replace current instances"></s:link>
				</rich:column>	
			</rich:dataTable>
        <div style="clear:both"/>
 
    </rich:panel>
 
</ui:define>
 
</ui:composition>

Esta página precisa de receber um parâmetro chamado newsId, por isso, adicione as seguintes linhas no seu pages.xml

19
20
21
<page view-id="/newsRevision.xhtml" action="#{newsRevision.fetchRevisions}">
		<param name="id" value="#{newsRevision.newsId}"/>
	</page>

A tela produzida pelo código acima é mostrada na figura abaixo:

Listagem com as versões (clique para fullsize)

Listagem com as versões (clique para fullsize)

Finalmente a nosso backing bean associado à página (NewsRevisionService.java):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.furiousbob.beans;
 
import java.io.Serializable;
import java.util.Date;
import java.util.List;
 
import javax.persistence.EntityManager;
 
import org.jboss.envers.VersionsReader;
import org.jboss.envers.VersionsReaderFactory;
import org.jboss.envers.query.VersionsRestrictions;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.datamodel.DataModel;
 
import com.furiousbob.com.News;
 
@Name("newsRevision")
public class NewsRevisionService implements Serializable {
 
	@In
	private EntityManager entityManager;
 
	private Long newsId;
 
	@DataModel(value="revisions")
	private List revisions;
 
 
	@Factory(value="revisions")
	public void fetchRevisions(){
		VersionsReader reader = VersionsReaderFactory.get(entityManager);
		this.revisions = reader.createQuery().forRevisionsOfEntity(News.class, false).add(VersionsRestrictions.idEq(newsId)).getResultList();
	}
 
	public Date fetchDate(long n){
		VersionsReader reader = VersionsReaderFactory.get(entityManager);
		Date d = reader.getRevisionDate((int)n);
		return d;
	}
 
	public void replaceRevision(News n){
		entityManager.merge(n);
	}
 
	public Long getNewsId() {
		return newsId;
	}
 
 
	public void setNewsId(Long newsId) {
		this.newsId = newsId;
	}
 
 
 
}

Para buscar as revisões de uma entidade, usamos o método fetchRevisions, para acessar o SessionFactory do envers, basta criarmos seu objeto e passarmos como referência nosso entityManager. O método createQuery().forRevisionsOfEntity(News.class, false) retorna uma lista de arrays de tamanho 2, onde a primeira posição é a entidade procurada e a segunda posição o número da revisão. Além disto precisamos buscar a data em que a revisão foi feita, note que em nosso template usamos a EL extendida do Seam para invocar um método no nosso backing bean passando um parâmetro para o mesmo #{newsRevision.fetchDate(_revision[1])}.
Nosso backing bean possui ainda um método para voltar com a versão da entidade, de acordo com a versão selecionada.
Este exemplo demonstra como é fácil gerar versionamento de entidades em seu sistema com o envers. Usando o envers e a API hibernate é possível realizar operações de restauração de objetos ou então apenas auditoria no seu sistema.
Isso é apenas um exemplo simples, uma funcionalidade desejável seria a adição do usuário corrente dentro de sua entidade, para isso basta usar as interfaces de callback do Hibernate (@PrePersist,@PreUpdate e @PreRemove) de forma que adicionem o usuário corrente em uma propriedade base de sua entidade.
Uma coisa que na minha opinião, faltou ao envers é expor de forma simples, além da versão e timestamp, o tipo de operação (INSERT,UPDATE,REMOVE) que foi realizada na entidade. Mas para isso, já abri um JIRA junto ao projeto, se você acha que esta funcionalidade vai ajudá-lo por favor vote nela para aumentar a chance de a mesma ser implementada ;)

Bem, acho que ficamos por aqui, próximos posts vou sair um pouco de Seam e vou falar de WebServices, fiquem atentos.

Inté