hibernate efetivo (coalti-2014 / aljug)

Post on 02-Dec-2014

383 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

Mesmo anos após o lançamento do Hibernate ainda é fácil encontrar projetos utilizando o framework de maneira ineficiente, podendo leva-lo a problemas sérios de performance ou até inviabilizar a aplicação. O uso não efetivo do Hibernate está intimamente ligado a erros comuns e más práticas em sua utilização, que vão desde pool de conexões, select n+1, configuração de cache, batch-size até o uso indevido do cache level 1 em processamentos batch e o tratamento de LazyInitializationException. Palestra ministrada no evento COALTI 2014 a convite do ALJUG (Grupo de usuários Java de Alagoas).

TRANSCRIPT

Hibernate EFETIVOERROS COMUNS E SOLUÇÕES

Rafael Ponte COALTI-2014 / ALJUG

Eu estava me perguntando quando de fato o Hibernate foi criado...

Eu estava me perguntando quando de fato o Hibernate foi criado...

Luca Bastos

O “Boom” foi em 2003!

Mais de uma década! 2014 - 2003 = 11

mas ainda hoje...

mas ainda hoje...

existem devs que subutilizam o framework

mas ainda hoje...

existem devs que subutilizam o framework

problemas de perfomance e escalabilidade

e pra piorar...

culpam o Hibernate

culpam o Hibernate

A culpa é do Banco de Dados!

Sérgio

é fácil culpar o que não se conhece...

um SGDB mal configurado pode ser o problema...

um SGDB mal configurado pode ser o problema...

MAS na maioria das vezes o

problema está na SUA APLICAÇÃO

tabelas sem índices

<3

tabelas sem índices consultas mal-feitas

<3 <3

tabelas sem índices consultas mal-feitas

<3 <3 <3

muitos hits ao banco

tabelas sem índices consultas mal-feitas

muitos hits ao banco

connection leaks

<3 <3 <3

tabelas sem índices consultas mal-feitas

muitos hits ao banco

connection leaks

<3 <3 <3

memory leaks

Não saber tirar proveito framework

Hibernate Efetivo 6 dicas para não deixar

sua app morrer

@rponte

Fortaleza - Terra do Sol

Miguel Lima

#1 POOL DE

CONEXÕES

com certeza todos aqui já ouviram falar...

mas nem todos dão a devida atenção...

mas nem todos dão a devida atenção... até perceberem a app engasgando

mas nem todos dão a devida atenção... até perceberem a app engasgando

ou até receberem um

mas nem todos dão a devida atenção... até perceberem a app engasgando

ou até receberem um

org.hibernate.exception.GenericJDBCException: Cannot

open connection

daí percebem que não configuraram o o pool do Hibernate

hibernate.connection.driver_class=org.postgresql.Driver  hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect  hibernate.connection.url=jdbc:postgresql://localhost:5432/myapp  hibernate.connection.username=postgres  hibernate.connection.password=1234  !!!

hibernate.properties

daí percebem que não configuraram o o pool do Hibernate

hibernate.connection.driver_class=org.postgresql.Driver  hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect  hibernate.connection.url=jdbc:postgresql://localhost:5432/myapp  hibernate.connection.username=postgres  hibernate.connection.password=1234hibernate.connection.pool_size=30  !!

hibernate.properties

daí percebem que não configuraram o o pool do Hibernate

hibernate.connection.driver_class=org.postgresql.Driver  hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect  hibernate.connection.url=jdbc:postgresql://localhost:5432/myapp  hibernate.connection.username=postgres  hibernate.connection.password=1234hibernate.connection.pool_size=30  !!

hibernate.properties

a app volta a funcionar bem por um tempo

a app volta a funcionar bem por um tempo

e vão alocando mais conexões com o tempo

hibernate.connection.pool_size=30                                                                40                                                                55                                                                70                                                            ...  

hibernate.properties

o pool PADRÃO do Hibernate

que possui uma impl. RUDIMENTAR

INFO DriverManagerConnectionProvider:64 - Using Hibernate built-in connection pool (not for production use!)!INFO DriverManagerConnectionProvider:65 - Hibernate connection pool size: 20!

INFO DriverManagerConnectionProvider:64 - Using Hibernate built-in connection pool (not for production use!)!INFO DriverManagerConnectionProvider:65 - Hibernate connection pool size: 20!

not for production use!

qual pool utilizar?

temos ótimas opções...

pools como c3p0 ou commons-dbcp

temos ótimas opções...

o Hibernate já vem com c3p0

configurando c3p0

hibernate.connection.driver_class=org.postgresql.Driver  hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect  hibernate.connection.url=jdbc:postgresql://localhost:5432/myapp  hibernate.connection.username=postgres  hibernate.connection.password=1234  hibernate.c3p0.min_size=5  hibernate.c3p0.max_size=20  hibernate.c3p0.timeout=1800  hibernate.c3p0.max_statements=50  

hibernate.properties

ou melhor ainda...

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">!! <!-- access configuration -->!! <property name="driverClass" value="${jdbc.driverclass}" />!! <property name="jdbcUrl" value="${jdbc.url}" />!! <property name="user" value="${jdbc.username}" />!! <property name="password" value="${jdbc.password}" />!! <!-- pool sizing -->!! <property name="initialPoolSize" value="3" />!! <property name="minPoolSize" value="6" />!! <property name="maxPoolSize" value="25" />!! <property name="acquireIncrement" value="3" />!! <property name="maxStatements" value="0" />!! <!-- retries -->!! <property name="acquireRetryAttempts" value="30" />!! <property name="acquireRetryDelay" value="1000" /> <!-- 1s -->!! <property name="breakAfterAcquireFailure" value="false" />!! <!-- refreshing connections -->!! <property name="maxIdleTime" value="180" /> <!-- 3min -->!! <property name="maxConnectionAge" value="10" /> <!-- 1h -->!! <!-- timeouts e testing -->!! <property name="checkoutTimeout" value="5000" /> <!-- 5s -->!! <property name="idleConnectionTestPeriod" value="60" /> <!-- 60 -->!! <property name="testConnectionOnCheckout" value="true" />!! <property name="preferredTestQuery" value="SELECT 1+1" />!</bean>!

podemos obter as conexões de um DataSource

Pool traz melhoria de performance

Pool traz melhoria de performance

mas não faz milagres

#2 lidando com

LazyInitializationException

quando e por que acontece?

@Entity  class  NotaFiscal  {      …      @OneToMany      List<Item>  itens;  }

NotaFiscal  nf  =  (NotaFiscal)  session.load(NotaFiscal.class,  42);  List<Item>  itens  =  nf.getItens();  

Percorrendo os itens de uma nota

select nf.* from NotaFiscal nf where nf.id=42

select i.* from Item i where i.nota_fiscal_id=42

Hibernate executa 2 selects

NotaFiscal  nf  =  (NotaFiscal)  session.load(NotaFiscal.class,  42);

List<Item>  itens  =  nf.getItens();

Session  session  =  sessionFactory.openSession();  !NotaFiscal  nf  =  (NotaFiscal)  session.load(NotaFiscal.class,  42);  session.close();  !List<Item>  itens  =  nf.getItens();System.out.println("numero  de  pedidos:"  +  itens.size());  

a session do Hibernate foi fechada

Session  session  =  sessionFactory.openSession();  !NotaFiscal  nf  =  (NotaFiscal)  session.load(NotaFiscal.session.close();  !List<Item>  itens  =  nf.getItens();System.out.println(itens.size());

mas ao ler os itens da nota

org.hibernate.LazyInitializationException: failed to lazily initialize a collection - no session or session was closed.

resolver parece fácil, certo?

Session  session  =  sessionFactory.openSession();  !NotaFiscal  nf  =  (NotaFiscal)  session.load(NotaFiscal.class,  42);  !List<Item>  itens  =  nf.getItens();System.out.println("numero  de  pedidos:"  +  itens.size());session.close();  !

fechar a session ao término do trabalho

Mas e quando estamos trabalhando na Web?

@Get("/notas/{id}")  public  void  view(Long  id)  {    NotaFiscal  nf  =  notaFiscalDao.carrega(id);      result.include("nf",  nf);    result.forwardTo("/notas/view.jsp");  }  

view.jsp

NotaFiscalController.java

<c:forEach  var="item"  items="${nf.itens}">          ${item.produto.descricao}<br/>  </c:forEach>

@Get("/notas/{id}")  public  void  view(Long  id)  {    NotaFiscal  nf  =  notaFiscalDao.carrega(id);      result.include("nf",  nf);    result.forwardTo("/notas/view.jsp");  }  

view.jsp

NotaFiscalController.java

<c:forEach  var="item"  items="${nf.itens}">          ${item.produto.descricao}<br/>  </c:forEach>

session foi fechada!

@Get("/notas/{id}")  public  void  view(Long  id)  {    NotaFiscal  nf  =  notaFiscalDao.carrega(id);      result.include("nf",  nf);    result.forwardTo("/notas/view.jsp");  }  

view.jsp

NotaFiscalController.java

<c:forEach  var="item"  items="${nf.itens}">          ${item.produto.descricao}<br/>  </c:forEach>

LazyInitializationException

e agora? #comofas

e agora? #comofas

Sérgio

passa tudo pra EAGER! ;D

@Entity  class  NotaFiscal  {      …      @OneToMany(fetch=FetchType.EAGER)      List<Item>  itens;  }

select nf.*, i.* from NotaFiscal nf left outer join Item i on nf.id = i.nota_fiscal_id where nf.id=42

Hibernate executa 1 select

NotaFiscal  nf  =  (NotaFiscal)  session.load(NotaFiscal.class,  42);

mas isso poderia gerar uma sobrecarga...

mas isso poderia gerar uma sobrecarga...

pois os itens da nota não são necessários em

muitos lugares

lembre-se que ter os relacionamentos como LAZY é

uma boa prática

como evitar LIE sem modificar o relacionamento

para EAGER?

Open Session In View

@WebFilter(urlPatterns="/*")  public  class  OpenSessionInViewFilter  implements  Filter  {  !   SessionFactory  sessionFactory;         @Override     public  void  doFilter(ServletRequest  req,  ServletResponse  res,  FilterChain  chain)  {       Transaction  transaction  =  null;  

    try  {         Session  session  =  sessionFactory.getCurrentSession();         transaction  =  session.beginTransaction();                                    chain.doFilter(req,  res);  

                                 transaction.commit();  

    }  finally  {         if  (transaction  !=  null  &&  transaction.isActive())  {           transaction.rollback();         }       }     }  }

Servlet Filter

o OSIV só evita LIE no mesmo request!

anti-pattern?

#3 Second Level

Cache

NotaFiscal  nf  =  (NotaFiscal)  session.load(NotaFiscal.class,  42);

Carregando uma nota por ID

Banco de Dados

Session

Banco de Dados

Session

First Level Cache

Banco de Dados

Session Session Session Session

Banco de Dados

Second Level Cache

Session Session Session Session

Banco de Dados

Second Level Cache

Session Session Session

First Level Cache

Session

Banco de Dados

SessionFactory

Session Session Session Session

Configurar é simples

#1 configuramos o Hibernate

hibernate.cache.use_second_level_cache=true  hibernate.cache.region.factory_class=net.sf.ehcache.hibernate.EhCacheRegionFactory  

hibernate.properties

@Entity  @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)  class  Bug  {    @Id      private  Long  id;    private  String  descricao;  private  String  status;    @ManyToOne  private  Projeto  projeto;  

   @OneToMany      private  List<Comentario>  comentarios;  }

#2 configuramos as entidades

@Cache(  usage=CacheConcurrencyStrategy.READ_WRITE)

Caching Strategy

READ_ONLY

NONSTRICT_READ_WRITE

READ_WRITE

@Cache(  usage=CacheConcurrencyStrategy.READ_WRITE)

Caching Strategy

READ_ONLY

NONSTRICT_READ_WRITE

READ_WRITE

Melhor performance

@Cache(  usage=CacheConcurrencyStrategy.READ_WRITE)

Caching Strategy

READ_ONLY

NONSTRICT_READ_WRITE

READ_WRITE

Dados não críticos

@Cache(  usage=CacheConcurrencyStrategy.READ_WRITE)

Caching Strategy

READ_ONLY

NONSTRICT_READ_WRITE

READ_WRITE Modificações frequentes

<cache          name="br.com.triadworks.model.Bug"          maxElementsInMemory="10000"          eternal="false"          timeToIdleSeconds="1800"          timeToLiveSeconds="10000"          overflowToDisk="true"          memoryStoreEvictionPolicy="LRU"  />

ehcache.xml

Como 2nd Level Cache funciona?

2nd Level Cache não faz cache das instancias das entidades

2nd Level Cache não faz cache das instancias das entidades

somente dos valores das propriedades

@Entity  @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)  class  Bug  {    @Id      private  Long  id;    private  String  descricao;  private  String  status;    @ManyToOne  private  Projeto  projeto;  

   @OneToMany      private  List<Comentario>  comentarios;  }

modelo conceitual do cache

Bug Data Cache

17 -> [ “Bug #1”, “ABERTA” , 1 ]!18 -> [ “Bug #2”, “FECHADA”, 2 ]!19 -> [ “Bug #3”, “ABERTA” , 1 ]

modelo conceitual do cache

Bug Data Cache

17 -> [ “Bug #1”, “ABERTA” , 1 ]!18 -> [ “Bug #2”, “FECHADA”, 2 ]!19 -> [ “Bug #3”, “ABERTA” , 1 ]

id descricao status

id do projeto

modelo conceitual do cache

Bug Data Cache

17 -> [ “Bug #1”, “ABERTA” , 1 ]!18 -> [ “Bug #2”, “FECHADA”, 2 ]!19 -> [ “Bug #3”, “ABERTA” , 1 ]

não é uma árvore de objetos, mas sim um Map de Arrays

2nd Level Cache não faz cache das associações

@Entity  @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)  class  Bug  {    @Id      private  Long  id;    private  String  descricao;  private  String  status;    @ManyToOne  private  Projeto  projeto;  

   @OneToMany      @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)      private  List<Comentario>  comentarios;  }

modelo conceitual do cache

Bug Data Cache

17 -> [ “Bug #1”, “ABERTA” , 1, [1,2] ]!18 -> [ “Bug #2”, “FECHADA”, 2, [] ]!19 -> [ “Bug #3”, “ABERTA” , 1, [3] ]

modelo conceitual do cache

Bug Data Cache

17 -> [ “Bug #1”, “ABERTA” , 1, [1,2] ]!18 -> [ “Bug #2”, “FECHADA”, 2, [] ]!19 -> [ “Bug #3”, “ABERTA” , 1, [3] ]

ids dos comentarios

E o que o Hibernate faz com todos estes IDs?

E o que o Hibernate faz com todos estes IDs?

vai no banco de novo! a não ser que

você…

@Entity  @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)  class  Projeto  {      ...  }  !!@Entity  @Cache(usage=CacheConcurrencyStrategy.READ_ONLY)  class  Comentario  {    ...  }

configure o cache das entidades

Devo cachear todas as minhas entidades?

Com 2nd Level Cache tudo funciona bem enquanto buscamos por ID...

session.load(Bug.class,  17);

Com 2nd Level Cache tudo funciona bem enquanto buscamos por ID...

...mas e quando precisamos de uma consulta um pouco diferente?

session.load(Bug.class,  17);

session    .createQuery("from  Bug  where  status  =  ?")      .setString(0,"ABERTO")    .list();  

#4 Query Cache

Query Cache faz cache do resultado de uma query

Configurando Hibernate para usar Query Cache

hibernate.cache.use_query_cache=true  

hibernate.properties

Query Cache

session    .createQuery("from  Bug  where  status  =  ?")      .setString(0,  status)    .setCacheable(true)    .list();  

modelo conceitual do query cache

Query Cache

[“from Bug where status = ?”, [“ABERTO”]] -> [17, 19]

modelo conceitual do query cache

Query Cache

[“from Bug where status = ?”, [“ABERTO”]] -> [17, 19]

Query + Parâmetros IDs

por isso Query Cache SEM 2nd Level Cache não é de

muita ajuda

Utilize somente em consultas que são executadas repetidas

vezes com os mesmos parâmetros

Utilize somente em consultas que são executadas repetidas

vezes com os mesmos parâmetros

#5 Select n+1

o campeão em prejudicar a performance da

aplicação

NotaFiscal  nf  =  (NotaFiscal)  session.load(NotaFiscal.class,  42);processaItensDaNota(nf);  

Processando os itens de uma nota

select nf.* from NotaFiscal nf where nf.id=42

select i.* from Item i where i.nota_fiscal_id=42

Hibernate executa 2 selects

NotaFiscal  nf  =  (NotaFiscal)  session.load(NotaFiscal.class,  42);

processaItensDaNota(nf);

List<NotaFiscal>  notas  =  dao.listaTudo();  for  (NotaFiscal  nf  :  notas)  {          processaItensDaNota(nf);  }  

Processando os itens de varias notas

select nf.* from NotaFiscal nf

select i.* from Item i where i.nota_fiscal_id=? select i.* from Item i where i.nota_fiscal_id=? select i.* from Item i where i.nota_fiscal_id=? select i.* from Item i where i.nota_fiscal_id=? select i.* from Item i where i.nota_fiscal_id=? ...

Hibernate executa n+1 selects

List<NotaFiscal>  notas  =  dao.listaTudo();

for  (NotaFiscal  nf  :  notas)  {          processaItensDaNota(nf);  }

são muitos hits no banco de dados

são muitos hits no banco de dados

mas podemos resolver isso...

3 soluções

#1 EAGER ou join-fetch

@Entity  class  NotaFiscal  {      …      @OneToMany(fetch=FetchType.EAGER)      List<Item>  itens;  }

Utilizando FetchMode=EAGER

select nf.*, i.* from NotaFiscal nf left outer join Item i on nf.id = i.nota_fiscal_id

Hibernate executa 1 select

List<NotaFiscal>  notas  =  dao.listaTudo();

antes de definir um mapeamento global deste

tipo você precisa se perguntar...

SEMPRE que uma nota é necessária, todos seus

itens também são necessários?

não?

session    .createQuery("from  NotaFiscal  n  left  join  fetch  n.itens")    .list();  

Utilizando Join Fetch

#2 batch-size nas associações

É o meio termo entre EAGER e LAZY

@Entity  class  NotaFiscal  {      …      @OneToMany      @BatchSize(size=10)      List<Item>  itens;  }

@BatchSize

select nf.* from NotaFiscal nf

select i.* from Item i where i.nota_fiscal_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) select i.* from Item i where i.nota_fiscal_id in (?, ?, ?, ?, ?)

Hibernate executa n/10+1 selects

List<NotaFiscal>  notas  =  dao.listaTudo();

for  (NotaFiscal  nf  :  notas)  {          processaItensDaNota(nf);  }

@BatchSize também é conhecido como:

@BatchSize também é conhecido como:

otimização de adivinhação cega(blind-guess optimization)

@BatchSize também é conhecido como:

otimização de adivinhação cega(blind-guess optimization)

ou seja, é um palpite

#3 FetchMode SUBSELECT

@Entity  class  NotaFiscal  {      …      @OneToMany      @Fetch(FetchMode.SUBSELECT)      List<Item>  itens;  }

SUBSELECT

select nf.* from NotaFiscal nf

select i.* from Item i where i.nota_fiscal_id in (select nf.id from NotaFiscal nf)

Hibernate executa 2 selects

List<NotaFiscal>  notas  =  dao.listaTudo();

for  (NotaFiscal  nf  :  notas)  {          processaItensDaNota(nf);  }

Qual utilizar?

Qual utilizar?

depende

#6 Processamento

em lote

Imagine que temos que importar 100k produtos para

o banco de dados

Session  session  =  sf.openSession();  Transaction  tx  =  session.beginTransaction();        for  (  int  i=0;  i  <  100000;  i++  )  {     Produto  produto  =  new  Produto(...);     session.save(produto);  }        tx.commit();  session.close();

em ~50k produtos nós receberíamos um

OutOfMemoryException

por que?

Hibernate faz cache de todas as instâncias dos Produtos

inseridos na Session

mas como melhorar?

!!!

Session  session  =  sf.openSession();  Transaction  tx  =  session.beginTransaction();        for  (  int  i=0;  i  <  100000;  i++  )  {     Produto  produto  =  new  Produto(...);     session.save(produto);      if  (i  %  100  ==  0)  {          session.flush();          session.clear();      }  }        tx.commit();  session.close();

evitamos o OutOfMemoryException

evitamos o OutOfMemoryException

Mas o processamento ainda continua lento!

evitamos o OutOfMemoryException

Mas o processamento ainda continua lento!

Dá pra melhorar?

JDBC puro?

JDBC puro?

código de maxu!

Handerson Frota

StatelessSession

StatelessSession

sem 1st Level Cache

sem 2nd Level Cache

sem dirty-checking

sem cascade

Collections são ignorados

sem modelo de eventos sem interceptors

próxima ao jdbc

API mais baixo nível

mapeamento básico

!

StatelessSession  session  =  sf.openStatelessSession();  Transaction  tx  =  session.beginTransaction();        for  (  int  i=0;  i  <  100000;  i++  )  {     Produto  produto  =  new  Produto(...);     session.insert(produto);  }        tx.commit();  session.close();

menos consumo de memória e mais rápida!

menos consumo de memória e mais rápida!

Yuri Adams

dá pra melhorar?

hibernate.jdbc.batch_size=50  

hibernate.properties

CONCLUSÃO

Foi apenas a ponta o iceberg!

cada uma destas dicas são simples, mas requerem mais estudo

cada uma destas dicas são simples, mas requerem mais estudopois depende do projeto

um DBA certamente pode te ajudar em muitos cenários

Hibernate não é seu inimigo, deixem de #mimimi

Rafael Ponterponte@triadworks.com.br

top related