hibernate efetivo (coalti-2014 / aljug)
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