Upload
rafael-ponte
View
14.074
Download
0
Embed Size (px)
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.
Citation preview
Hibernate EFETIVOERROS COMUNS E SOLUÇÕES
Tuesday, August 7, 2012
Eu estava me perguntando quando de fato o Hibernate foi criado...
Tuesday, August 7, 2012
Eu estava me perguntando quando de fato o Hibernate foi criado...
Luca Bastos
O “Boom” foi em 2003!
Tuesday, August 7, 2012
Quase uma década!2012 - 2003 = 9
Tuesday, August 7, 2012
mas ainda hoje...
Tuesday, August 7, 2012
mas ainda hoje...
existem devs que subutilizam o framework
Tuesday, August 7, 2012
mas ainda hoje...
existem devs que subutilizam o framework
problemas de perfomance e escalabilidade
Tuesday, August 7, 2012
e pra piorar...
Tuesday, August 7, 2012
culpam o Hibernate
Tuesday, August 7, 2012
culpam o Hibernate
A culpa é do Banco de Dados!
Sérgio
Tuesday, August 7, 2012
é fácil culpar o que não se conhece...
Tuesday, August 7, 2012
um SGDB mal configurado pode ser o problema...
Tuesday, August 7, 2012
um SGDB mal configurado pode ser o problema...
MAS na maioria das vezes o
problema está na SUA APLICAÇÃO
Tuesday, August 7, 2012
tabelas sem índices
<3Tuesday, August 7, 2012
tabelas sem índices
consultas mal-feitas
<3 <3Tuesday, August 7, 2012
tabelas sem índices
consultas mal-feitas
<3 <3 <3
muitos hits ao banco
Tuesday, August 7, 2012
tabelas sem índices
consultas mal-feitas
muitos hits ao banco
connection leaks
<3 <3 <3Tuesday, August 7, 2012
tabelas sem índices
consultas mal-feitas
muitos hits ao banco
connection leaks
<3 <3 <3
memory leaks
Tuesday, August 7, 2012
Não saber tirar proveito framework
Tuesday, August 7, 2012
Hibernate Efetivo6 dicas para não deixar
sua app morrer
Tuesday, August 7, 2012
@rponte
Tuesday, August 7, 2012
Tuesday, August 7, 2012
Fortaleza - Terra do SolTuesday, August 7, 2012
#1POOL DE
CONEXÕES
Tuesday, August 7, 2012
com certeza todos aqui já ouviram falar...
Tuesday, August 7, 2012
mas nem todos dão a devida atenção...
Tuesday, August 7, 2012
mas nem todos dão a devida atenção...até perceberem a app engasgando
Tuesday, August 7, 2012
mas nem todos dão a devida atenção...até perceberem a app engasgando
ou até receberem um
Tuesday, August 7, 2012
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
Tuesday, August 7, 2012
daí percebem que não configuraram o o pool do Hibernate
hibernate.connection.driver_class=org.postgresql.Driverhibernate.dialect=org.hibernate.dialect.PostgreSQLDialecthibernate.connection.url=jdbc:postgresql://localhost:5432/myapphibernate.connection.username=postgreshibernate.connection.password=1234
hibernate.properties
Tuesday, August 7, 2012
daí percebem que não configuraram o o pool do Hibernate
hibernate.connection.driver_class=org.postgresql.Driverhibernate.dialect=org.hibernate.dialect.PostgreSQLDialecthibernate.connection.url=jdbc:postgresql://localhost:5432/myapphibernate.connection.username=postgreshibernate.connection.password=1234hibernate.connection.pool_size=30
hibernate.properties
Tuesday, August 7, 2012
daí percebem que não configuraram o o pool do Hibernate
hibernate.connection.driver_class=org.postgresql.Driverhibernate.dialect=org.hibernate.dialect.PostgreSQLDialecthibernate.connection.url=jdbc:postgresql://localhost:5432/myapphibernate.connection.username=postgreshibernate.connection.password=1234
hibernate.connection.pool_size=30
hibernate.properties
Tuesday, August 7, 2012
a app volta a funcionar bem por um tempo
Tuesday, August 7, 2012
a app volta a funcionar bem por um tempo
Tuesday, August 7, 2012
e vão alocando mais conexões com o tempo
hibernate.connection.pool_size=30 40 55 70 ...
hibernate.properties
Tuesday, August 7, 2012
o pool PADRÃO do Hibernate
Tuesday, August 7, 2012
que possui uma impl. RUDIMENTAR
Tuesday, August 7, 2012
INFO DriverManagerConnectionProvider:64 - Using Hibernate built-in connection pool (not for production use!)INFO DriverManagerConnectionProvider:65 - Hibernate connection pool size: 20
Tuesday, August 7, 2012
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!
Tuesday, August 7, 2012
qual pool utilizar?
Tuesday, August 7, 2012
temos ótimas opções...
Tuesday, August 7, 2012
pools como c3p0 ou commons-dbcp
temos ótimas opções...
Tuesday, August 7, 2012
o Hibernate já vem com c3p0
Tuesday, August 7, 2012
configurando c3p0
hibernate.connection.driver_class=org.postgresql.Driverhibernate.dialect=org.hibernate.dialect.PostgreSQLDialecthibernate.connection.url=jdbc:postgresql://localhost:5432/myapphibernate.connection.username=postgreshibernate.connection.password=1234hibernate.c3p0.min_size=5hibernate.c3p0.max_size=20hibernate.c3p0.timeout=1800hibernate.c3p0.max_statements=50
hibernate.properties
Tuesday, August 7, 2012
ou melhor ainda...
Tuesday, August 7, 2012
<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
Tuesday, August 7, 2012
Pool traz melhoria de performance
Tuesday, August 7, 2012
Pool traz melhoria de performance
mas não faz milagres
Tuesday, August 7, 2012
#2lidando com
LazyInitializationException
Tuesday, August 7, 2012
quando e por que acontece?
Tuesday, August 7, 2012
@Entityclass NotaFiscal { … @OneToMany List<Item> itens;}
Tuesday, August 7, 2012
NotaFiscal nf = (NotaFiscal) session.load(NotaFiscal.class, 42);List<Item> itens = nf.getItens();
Percorrendo os itens de uma nota
Tuesday, August 7, 2012
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();
Tuesday, August 7, 2012
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
Tuesday, August 7, 2012
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());
mas ao ler os itens da nota
org.hibernate.LazyInitializationException: failed to lazily initialize a collection -no session or session was closed.
Tuesday, August 7, 2012
resolver parece fácil, certo?
Tuesday, August 7, 2012
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
Tuesday, August 7, 2012
Mas e quando estamos trabalhando na Web?
Tuesday, August 7, 2012
@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>
Tuesday, August 7, 2012
@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
Tuesday, August 7, 2012
e agora? #comofas
Tuesday, August 7, 2012
e agora? #comofas
Sérgio
passa tudo pra EAGER! ;D
Tuesday, August 7, 2012
@Entityclass NotaFiscal { … @OneToMany(fetch=FetchType.EAGER) List<Item> itens;}
Tuesday, August 7, 2012
select nf.*, i.* from NotaFiscal nf left outer join Item i on nf.id = i.nota_fiscal_idwhere nf.id=42
Hibernate executa 1 select
NotaFiscal nf = (NotaFiscal) session.load(NotaFiscal.class, 42);
Tuesday, August 7, 2012
mas isso poderia gerar uma sobrecarga...
Tuesday, August 7, 2012
mas isso poderia gerar uma sobrecarga...
pois os itens da nota não são necessários em
muitos lugares Tuesday, August 7, 2012
lembre-se que ter os relacionamentos como LAZY é
uma boa prática
Tuesday, August 7, 2012
como evitar LIE sem modificar o relacionamento
para EAGER?
Tuesday, August 7, 2012
Open Session In View
Tuesday, August 7, 2012
@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
Tuesday, August 7, 2012
o OSIV só evita LIE no mesmo request!
Tuesday, August 7, 2012
anti-pattern?
Tuesday, August 7, 2012
#3Second Level
Cache
Tuesday, August 7, 2012
NotaFiscal nf = (NotaFiscal) session.load(NotaFiscal.class, 42);
Carregando uma nota por ID
Tuesday, August 7, 2012
Banco de Dados
Session
Tuesday, August 7, 2012
Banco de Dados
Session
First Level Cache
Tuesday, August 7, 2012
Banco de Dados
Session Session Session Session
Tuesday, August 7, 2012
Banco de Dados
Second Level Cache
Session Session Session Session
Tuesday, August 7, 2012
Banco de Dados
Second Level Cache
Session Session Session Session
First Level Cache
Tuesday, August 7, 2012
Banco de Dados
SessionFactory
Session Session Session Session
Tuesday, August 7, 2012
Configurar é simples
Tuesday, August 7, 2012
#1 configuramos o Hibernate
hibernate.cache.use_second_level_cache=truehibernate.cache.region.factory_class=net.sf.ehcache.hibernate.EhCacheRegionFactory
hibernate.properties
Tuesday, August 7, 2012
@Entity@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)class Issue {@Id
private Long id;private String descricao;private String status;@ManyToOneprivate Projeto projeto;
@OneToMany private List<Comentario> comentarios;}
#2 configuramos as entidades
Tuesday, August 7, 2012
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
Caching Strategy
READ_ONLY
NONSTRICT_READ_WRITE
READ_WRITE
Tuesday, August 7, 2012
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
Caching Strategy
READ_ONLY
NONSTRICT_READ_WRITE
READ_WRITE
Melhor performance
Tuesday, August 7, 2012
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
Caching Strategy
READ_ONLY
NONSTRICT_READ_WRITE
READ_WRITE
Dados não críticos
Tuesday, August 7, 2012
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
Caching Strategy
READ_ONLY
NONSTRICT_READ_WRITE
READ_WRITE Modificações frequentes
Tuesday, August 7, 2012
<cache name="br.com.triadworks.model.Issue" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="10000" overflowToDisk="true" memoryStoreEvictionPolicy="LRU"/>
ehcache.xml
Tuesday, August 7, 2012
Como 2nd Level Cache funciona?
Tuesday, August 7, 2012
2nd Level Cache não faz cache das instancias das entidades
Tuesday, August 7, 2012
2nd Level Cache não faz cache das instancias das entidades
somente dos valores das propriedades
Tuesday, August 7, 2012
@Entity@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)class Issue {@Id
private Long id;private String descricao;private String status;@ManyToOneprivate Projeto projeto;
@OneToMany private List<Comentario> comentarios;}
Tuesday, August 7, 2012
modelo conceitual do cache
Issue Data Cache
17 -> [ “Bug #1”, “ABERTA” , 1 ]18 -> [ “Bug #2”, “FECHADA”, 2 ]19 -> [ “Bug #3”, “ABERTA” , 1 ]
Tuesday, August 7, 2012
modelo conceitual do cache
Issue 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"
Tuesday, August 7, 2012
2nd Level Cache não faz cache das associações
Tuesday, August 7, 2012
@Entity@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)class Issue {@Id
private Long id;private String descricao;private String status;@ManyToOneprivate Projeto projeto;
@OneToMany @Cache(usage=CacheConcurrencyStrategy.READ_WRITE) private List<Comentario> comentarios;}
Tuesday, August 7, 2012
modelo conceitual do cache
Issue Data Cache
17 -> [ “Bug #1”, “ABERTA” , 1, [1,2] ]18 -> [ “Bug #2”, “FECHADA”, 2, [] ]19 -> [ “Bug #3”, “ABERTA” , 1, [3] ]
Tuesday, August 7, 2012
Devo cachear todas as minhas entidades?
Tuesday, August 7, 2012
Com 2nd Level Cache tudo funciona bem enquanto buscamos por ID...
session.load(Issue.class, 17);
Tuesday, August 7, 2012
Com 2nd Level Cache tudo funciona bem enquanto buscamos por ID...
...mas e quando precisamos de uma consulta um pouco diferente?
session.load(Issue.class, 17);
session .createQuery("from Issue where status = ?") .setString(0,"ABERTO") .list();
Tuesday, August 7, 2012
#4Query Cache
Tuesday, August 7, 2012
Query Cache faz cache do resultado de uma query
Tuesday, August 7, 2012
Configurando Hibernate para usar Query Cache
hibernate.cache.use_query_cache=true
hibernate.properties
Tuesday, August 7, 2012
Query Cache
session .createQuery("from Issue where status = ?") .setString(0, status) .setCacheable(true) .list();
Tuesday, August 7, 2012
modelo conceitual do query cache
Query Cache
[“from Issue where status = ?”, [“ABERTA”]] -> [17, 19]
Tuesday, August 7, 2012
modelo conceitual do query cache
Query Cache
[“from Issue where status = ?”, [“ABERTA”]] -> [17, 19]
Query + Parâmetros IDs
Tuesday, August 7, 2012
por isso Query Cache SEM 2nd Level Cache não é de
muita ajuda
Tuesday, August 7, 2012
Utilize somente em consultas que são executadas repetidas
vezes com os mesmos parâmetros
Tuesday, August 7, 2012
Utilize somente em consultas que são executadas repetidas
vezes com os mesmos parâmetros
Tuesday, August 7, 2012
#5Select n+1
Tuesday, August 7, 2012
o campeão em prejudicar a performance da
aplicação
Tuesday, August 7, 2012
NotaFiscal nf = (NotaFiscal) session.load(NotaFiscal.class, 42);processaItensDaNota(nf);
Processando os itens de uma nota
Tuesday, August 7, 2012
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);
Tuesday, August 7, 2012
List<NotaFiscal> notas = dao.listaTudo();for (NotaFiscal nf : notas) { processaItensDaNota(nf);}
Processando os itens de varias notas
Tuesday, August 7, 2012
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);}
Tuesday, August 7, 2012
são muitos hits no banco de dados
Tuesday, August 7, 2012
são muitos hits no banco de dados
mas podemos resolver isso...
Tuesday, August 7, 2012
3 soluções
Tuesday, August 7, 2012
#1 EAGER ou join-fetch
Tuesday, August 7, 2012
@Entityclass NotaFiscal { … @OneToMany(fetch=FetchType.EAGER) List<Item> itens;}
Utilizando FetchMode=EAGER
Tuesday, August 7, 2012
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();
Tuesday, August 7, 2012
antes de definir um mapeamento global deste
tipo você precisa se perguntar...
Tuesday, August 7, 2012
SEMPRE que uma nota é necessária, todos seus
itens também são necessários?
Tuesday, August 7, 2012
não?Tuesday, August 7, 2012
session .createQuery("from NotaFiscal n left join fetch n.itens") .list();
Utilizando Join Fetch
Tuesday, August 7, 2012
#2 batch-size nas associações
Tuesday, August 7, 2012
É o meio termo entre EAGER e LAZY
Tuesday, August 7, 2012
@Entityclass NotaFiscal { … @OneToMany @BatchSize(size=10) List<Item> itens;}
@BatchSize
Tuesday, August 7, 2012
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);}
Tuesday, August 7, 2012
@BatchSize também é conhecido como:
Tuesday, August 7, 2012
@BatchSize também é conhecido como:
otimização de adivinhação cega(blind-guess optimization)
Tuesday, August 7, 2012
@BatchSize também é conhecido como:
otimização de adivinhação cega(blind-guess optimization)
ou seja, é um palpiteTuesday, August 7, 2012
#3 FetchMode SUBSELECT
Tuesday, August 7, 2012
@Entityclass NotaFiscal { … @OneToMany @Fetch(FetchMode.SUBSELECT) List<Item> itens;}
SUBSELECT
Tuesday, August 7, 2012
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);}
Tuesday, August 7, 2012
Qual utilizar?
Tuesday, August 7, 2012
Qual utilizar?
dependeTuesday, August 7, 2012
#6Processamento
em lote
Tuesday, August 7, 2012
Imagine que temos que importar 100k produtos para
o banco de dados
Tuesday, August 7, 2012
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();
Tuesday, August 7, 2012
em ~50k produtos nós receberíamos um
OutOfMemoryException
Tuesday, August 7, 2012
por que?
Tuesday, August 7, 2012
Hibernate faz cache de todas as instâncias dos Produtos
inseridos na Session
Tuesday, August 7, 2012
mas como melhorar?
Tuesday, August 7, 2012
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();
Tuesday, August 7, 2012
evitamos o OutOfMemoryException
Tuesday, August 7, 2012
evitamos o OutOfMemoryException
Mas o processamento ainda continua lento!
Tuesday, August 7, 2012
evitamos o OutOfMemoryException
Mas o processamento ainda continua lento!
Dá pra melhorar?
Tuesday, August 7, 2012
JDBC puro?
Tuesday, August 7, 2012
JDBC puro?
código de maxu!
Handerson Frota
Tuesday, August 7, 2012
StatelessSession
Tuesday, August 7, 2012
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
Tuesday, August 7, 2012
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();
Tuesday, August 7, 2012
menos consumo de memória e mais rápida!
Tuesday, August 7, 2012
menos consumo de memória e mais rápida!
Yuri Adams
dá pra melhorar?
Tuesday, August 7, 2012
hibernate.jdbc.batch_size=50
hibernate.properties
Tuesday, August 7, 2012
CONCLUSÃO
Tuesday, August 7, 2012
Foi apenas a ponta o iceberg!
Tuesday, August 7, 2012
cada uma destas dicas são simples, mas requerem mais estudo
Tuesday, August 7, 2012
cada uma destas dicas são simples, mas requerem mais estudopois depende do projeto
Tuesday, August 7, 2012
um DBA certamente pode ter ajudar em muitos cenários
Tuesday, August 7, 2012
Hibernate não é seu inimigo, deixem de #mimimi
Tuesday, August 7, 2012