Vamos refletir!
A princípio, para que serve um DAO? É um padrão enterprise utilizado para manter o acesso aos dados numa camada específica. Ao dividir as responsabilidades, o desenvolvimento é facilitado e a equipe de desenvolvimento se concentra no que realmente importa: as regras de negócio. Isso não significa que um DAO precisa ser um Singleton! Ele poderia ser genérico e a cada nova necessidade, seria instanciado e utilizado. Perfeito!
Mas atente no seguinte detalhe: você sabe o custo de processamento envolvido na criação de uma unidade de persistência? Ou seja, você sabe qual é o custo de processamento necessário para instanciar uma EntityManagerFactory? É por isso que resolvi fazer um estudo sobre o assunto.
Qual é o objeto deste artigo
Em poucas palavras, seria comparar a performance (custo de processamento) entre dois padrões de DAO: um Singleton e um DAO Genérico.
Vamos ao que interessa!
Os testes aqui apresentados foram realizados num notebook Dell Latitude D520, com Disco Rígido 60 GB (10000 RPM) e RAM DDR2 1024 MB (FSB 533). O framework utilizado foi o JPA Toplink e o banco de dados MySQL. Os arquivos fonte do Java foram codificados no Eclipse, mas nada impede de você criá-los em outras IDE's (ou até na unha).
Para produzir o teste, foi elaborado um complexo modelo UML com elementos bem estruturados e coerentes com a realidade de desenvolvimento atual. Veja abaixo o diagrama que representa este fabuloso artefato:

Não perca tempo tentando entender a complexidade do modelo apresentado na figura. Vamos para a codificação!
Lembre-se que uma tabela de respectivos campos deverá ser criada no banco de dados (esta também poderia ter sido criada automaticamente):
create table cliente (
id integer primary key,
nome varchar(40) not null
);
O arquivo Java que representa esse modelo é mostrado abaixo:
// Cliente.java
package engine;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table( name="cliente" )
public class Cliente {
@Id
@Column( name="id" )
private Long id;
@Column( name="nome" )
private String nome;
/**
* Devido a criação do outro constructor, o JPA necessita do constructor
* padrão explícito.
*/
public Cliente() {}
/**
* Constructor criado apenas para facilitar o código de teste.
*/
public Cliente(Long id, String nome) {
this.setId(id);
this.setNome(nome);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
}
Vale ressaltar o seguinte: optei em criar um constructor com parâmetros para facilitar o teste. Isso me obriga explicitamente a declarar o constructor default, caso contrário o JPA não conseguirá realizar a persistência. O arquivo persistence.xml associado ao projeto é mostrado abaixo:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
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">
<persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<class>engine.Cliente</class>
<properties>
<property name="toplink.jdbc.user" value="root"/>
<property name="toplink.jdbc.password" value="esqueciasenha"/>
<property name="toplink.jdbc.url" value="jdbc:mysql://localhost:3306/testedao"/>
<property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver"/>
</properties>
</persistence-unit>
</persistence>
Meu objeto não é abordar a necessidade de criação desses dois arquivos, portanto nada será comentado sobre eles. Caso você queira mais informações sobre esse tipo de implementação, veja http://linubr.blogspot.com.br/2012/08/desenvolvendo-um-crud-para-web-com-java.html.
Construindo um DAO Generic
Como um simples caso do DAO genérico, vamos implementar o arquivo abaixo:
// GenericDAO
package engine;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
public class GenericDAO<T extends Object> {
private EntityManagerFactory emf;
private EntityManager em;
public GenericDAO() {
this.emf = Persistence.createEntityManagerFactory("default");
this.em = this.emf.createEntityManager();
}
public void create(T object) throws PersistenceException {
try {
em.getTransaction().begin();
em.persist(object);
em.getTransaction().commit();
em.clear();
} catch (Exception e) {
throw new PersistenceException("Erro ao criar o objeto!");
}
}
}
Veja que o código é simples. Uma aplicação real teria, certamente, um código mais elaborado.
Para testar o funcionamento, vamos implementar uma sofisticada classe de teste, com heurísticas avançadas e métodos de estatística de alto padrão. Desta forma poderemos comparar com grande grau de precisão os resultados. Veja o código desta classe abaixo:
// TestDriver.java
package engine;
import java.util.Calendar;
public class TestDriver {
public static String obterHoraAtual() {
Calendar calendar = Calendar.getInstance();
return new String(
Integer.toString( calendar.get( Calendar.HOUR_OF_DAY ) ) + ":"+
Integer.toString( calendar.get( Calendar.MINUTE ) ) + ":" +
Integer.toString( calendar.get( Calendar.SECOND ) ) + ":" +
Integer.toString( calendar.get( Calendar.MILLISECOND ) )
);
}
public static void main(String args[]) {
System.out.println( obterHoraAtual() );
for(int i = 0; i < 100; i++) {
GenericDAO<Cliente> clienteDAO = new GenericDAO<Cliente>();
clienteDAO.create(
new Cliente( new Long( i+1 ) , "Cliente Nr " + Integer.toString( i+1 ) )
);
}
System.out.println( obterHoraAtual() );
}
}
Vale ressaltar que, para simular um acesso simultâneo, é realmente necessário instanciar o DAO a cada iteração do laço.
Resultados do prompt utilizando o DAO Genérico (100 inserções)
21:36:30:445
[TopLink Info]: 2009.03.25 09:36:31.408--ServerSession(3203712)--TopLink, version: Oracle TopLink Essentials - 2.0 (Build b41-beta2 (03/30/2007))
[TopLink Info]: 2009.03.25 09:36:31.895--ServerSession(3203712)--file:/mnt/arquivos/guilherme/desenvolvimento/java/eclipse/TesteDAO/build/classes/-default login successful
21:36:32:124
Observe que ele levou um pouco menos que 2 segundos. Vamos aos testes com o padrão Singleton.
Construindo um Singleton DAO
Assim como o GenericDAO, o pattern Singleton será simples. Veja o código:
package engine;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
import javax.persistence.PersistenceUnit;
@PersistenceUnit
public class SingletonDAO {
private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("default");
private static EntityManager em = emf.createEntityManager();
private static SingletonDAO dao;
private SingletonDAO() {}
public static SingletonDAO getInstance() {
if (dao == null)
dao = new SingletonDAO();
return dao;
}
public void create(Object object) throws PersistenceException {
try {
em.getTransaction().begin();
em.persist(object);
em.getTransaction().commit();
em.clear();
} catch (Exception e) {
throw new PersistenceException("Erro ao criar o objeto!");
}
}
}
Para testar, vamos realizar uma pequena adaptação no TestDriver:
// TestDriver.java
package engine;
import java.util.Calendar;
public class TestDriver {
public static String obterHoraAtual() {
Calendar calendar = Calendar.getInstance();
return new String(
Integer.toString( calendar.get( Calendar.HOUR_OF_DAY ) ) + ":"+
Integer.toString( calendar.get( Calendar.MINUTE ) ) + ":" +
Integer.toString( calendar.get( Calendar.SECOND ) ) + ":" +
Integer.toString( calendar.get( Calendar.MILLISECOND ) )
);
}
public static void main(String args[]) {
System.out.println( obterHoraAtual() );
for(int i = 0; i < 100; i++) {
SingletonDAO.getInstance().create(
new Cliente( new Long( i+1 ) , "Cliente Nr " + Integer.toString( i+1 ) )
);
}
System.out.println( obterHoraAtual() );
}
}
Resultados do prompt utilizando o DAO no padrão Singleton (100 inserções):
21:52:7:443
[TopLink Info]: 2009.03.25 09:52:08.353--ServerSession(3203712)--TopLink, version: Oracle TopLink Essentials - 2.0 (Build b41-beta2 (03/30/2007))
[TopLink Info]: 2009.03.25 09:52:08.848--ServerSession(3203712)--file:/mnt/arquivos/guilherme/desenvolvimento/java/eclipse/TesteDAO/build/classes/-default login successful
21:52:9:72
Observe que ao inserir 100 elementos no banco, houve um esforço de quase 2 segundos no framework. O esforço gerado pela instanciação do DAO foi executado apenas uma vez - devido ao comportamento padrão do pattern Singleton. O restante do tempo foi consumido por transações corriqueiras.
Podemos observar ainda que, ao inserirmos 100 registros, o ganho de performance do Singleton em relação ao Generic é desconsiderável. Vamos realizar os mesmos testes, só que agora com 100.000 inserções em cada caso.
Resultados do prompt utilizando o DAO Genérico (100.000 inserções)
21:59:43:179
[TopLink Info]: 2009.03.25 09:59:44.116--ServerSession(3203712)--TopLink, version: Oracle TopLink Essentials - 2.0 (Build b41-beta2 (03/30/2007))
[TopLink Info]: 2009.03.25 09:59:44.619--ServerSession(3203712)--file:/mnt/arquivos/guilherme/desenvolvimento/java/eclipse/TesteDAO/build/classes/-default login successful
22:0:42:222
Observe que este caso iniciou em 43 segundos e terminou nos 42 segundos do próximo minuto. Cerca de 59 segundos.
Resultados do prompt utilizando o DAO no padrão Singleton (100.000 inserções):
21:57:5:302
[TopLink Info]: 2009.03.25 09:57:06.225--ServerSession(3203712)--TopLink, version: Oracle TopLink Essentials - 2.0 (Build b41-beta2 (03/30/2007))
[TopLink Info]: 2009.03.25 09:57:06.719--ServerSession(3203712)--file:/mnt/arquivos/guilherme/desenvolvimento/java/eclipse/TesteDAO/build/classes/-default login successful
21:57:59:284
Veja que ele teve um custo de cerca de 54 segundos. Comparando este resultado com o do DAO Genérico, temos um ganho de 5 segundos. Para melhor representar o fator do custo envolvido, observe os dados abaixos:
Data Access Object | Milisegundos |
DAO Genérico (100 transições) | 1679 |
DAO Singleton (100 transições) | 1629 |
DAO Genérico (100.000 transições) | 59043 |
DAO Singleton (100.000 transições) | 53982 |
Esses dados geram o gráfico:
Observe no gráfico e tabela apresentados que o DAO Genérico precisou de mais milisegundos para executar determinada quantidade de transações, quando comparado ao DAO Singleton. Isso certamente não terá grande impacto no sistema, porém podemos confirmar que um DAO Singleton apresenta maior performance que um DAO Genérico.
Conclusões
Em primeiro plano, devemos observar que o Singleton definido neste arquivo não é thread-safety. Para criar um Singleton capaz de atender solicitações concorrentes, veja o artigo http://linubr.blogspot.com.br/2012/08/construindo-um-singleton-thread-safety.html . Não entrarei no mérito de explicar o motivo pelo qual este Singleton não é thread-safety.
Agora, sobre o DAO, devo indicar que, a utilização de um padrão Data Access Object, seja Singleton ou Generic, é sempre interessante. O fato de termos melhor performance - de pouca influência - não nos obriga a utilizar sempre o padrão Singleton. Cabe à equipe definir a melhor opção para seu projeto.
Como um típico programador coruja, eu quero o melhor para os meus códigos. Sempre acho que ao criar um novo sistema, ele será mundialmente famaso e dominará o mercado de software (e talvez o mundo!). Isso me faz escolher a melhor prática de recursos que conheço e portanto, adotar o padrão Singleton para os DAOs.
Mas lembre-se do seguinte: A programação de sistemas é igual a um copo de limonada, no início é docinha mas no final tem sempre um gosto azedo. Isso é comum no processo de software pois muitas vezes o desenvolvedor acha o processo de elaboração e concepção do software mais atrativo do que o processo de evolução. Em certos casos, principalmente quando não há análise, a evolução trata de corrigir problemas no código e requisitos. Acho que muitos analistas acham essa parte chata. Isso sgnifica que, se seu sistema for pequeno e não necessitar de recursos avançados, tome cuidado para não criar um monstro incontrolável, que demanda horas e horas de programação para mudar uma SQL (ou JPQL) no código. Sou a favor de usar código simples. Código simples não é código fácil. Código simples é aquele que antende a necessidade com mais coerência, sem muito conversa.
Então, use e abuse de patterns, apenas quando necessário.
"Uma vida sem desafios não vale a pena ser vivida." Sócrates
Espero ter ajudado!
Guilherme Pontes

Observe no gráfico e tabela apresentados que o DAO Genérico precisou de mais milisegundos para executar determinada quantidade de transações, quando comparado ao DAO Singleton. Isso certamente não terá grande impacto no sistema, porém podemos confirmar que um DAO Singleton apresenta maior performance que um DAO Genérico.
Conclusões
Em primeiro plano, devemos observar que o Singleton definido neste arquivo não é thread-safety. Para criar um Singleton capaz de atender solicitações concorrentes, veja o artigo http://linubr.blogspot.com.br/2012/08/construindo-um-singleton-thread-safety.html . Não entrarei no mérito de explicar o motivo pelo qual este Singleton não é thread-safety.
Agora, sobre o DAO, devo indicar que, a utilização de um padrão Data Access Object, seja Singleton ou Generic, é sempre interessante. O fato de termos melhor performance - de pouca influência - não nos obriga a utilizar sempre o padrão Singleton. Cabe à equipe definir a melhor opção para seu projeto.
Como um típico programador coruja, eu quero o melhor para os meus códigos. Sempre acho que ao criar um novo sistema, ele será mundialmente famaso e dominará o mercado de software (e talvez o mundo!). Isso me faz escolher a melhor prática de recursos que conheço e portanto, adotar o padrão Singleton para os DAOs.
Mas lembre-se do seguinte: A programação de sistemas é igual a um copo de limonada, no início é docinha mas no final tem sempre um gosto azedo. Isso é comum no processo de software pois muitas vezes o desenvolvedor acha o processo de elaboração e concepção do software mais atrativo do que o processo de evolução. Em certos casos, principalmente quando não há análise, a evolução trata de corrigir problemas no código e requisitos. Acho que muitos analistas acham essa parte chata. Isso sgnifica que, se seu sistema for pequeno e não necessitar de recursos avançados, tome cuidado para não criar um monstro incontrolável, que demanda horas e horas de programação para mudar uma SQL (ou JPQL) no código. Sou a favor de usar código simples. Código simples não é código fácil. Código simples é aquele que antende a necessidade com mais coerência, sem muito conversa.
Então, use e abuse de patterns, apenas quando necessário.
"Uma vida sem desafios não vale a pena ser vivida." Sócrates
Espero ter ajudado!
Guilherme Pontes
Nenhum comentário:
Postar um comentário