JPA: anotações nos getters ou atributos?

Por Paulo Silveira em 25/06/07

A especificação da JPA permite que você utilize as anotações em uma Entity de duas formasou nos atributos, ou nos getters. Também diz que não devemos misturar isso, pois o comportamento resultante não é especificado. Na época em que a JPA estava em draft, havia até um atributo na anotação @Entity em que você explicitava onde iria usar as anotações.

Se temos essas duas opções, qual devemos usar? Usando o hibernate, há uma diferença fundamental entre as duas abordagens: quando e se o objeto será carregado.

Considere a seguinte classe, anotada pelos atributos:

@Entity
public class Funcionario {
  @Id
  @GeneratedValue
  private Long id;

  private String nome;

  // getters e setters
}

E agora um pequeno código para carregar, de maneira lazy, um Funcionario de id 1:

  Funcionario f = manager.getReference(Funcionario.class, 1l);
  System.out.println(f.getId());      
  System.out.println(f.getNome());

Com a propriedade show_sql valendo true, o código ao rodar produz:

--- query select executada ---
1
Paulo Silveira

Se em vez de anotarmos o atributo id com @Id e @GeneratedValue, anotarmos o método getId, obteríamos:

1
--- query select executada ---
Paulo Silveira

Em outras palavras, se no final você só usasse a chave primária (como poderia acontecer se a view fosse uma página web e caísse em uma condição particular), a query nem mesmo teria sido executada! Com a anotação diretamente no atributo, qualquer invocação de método em um objeto carregado mesmo com o getReference vai disparar a query.

É interessante sempre anotar os getters em vez dos atributos, mas vale reparar que na maioria absoluta dos casos isso não vai trazer benefícios. Até mesmo porque, em uma aplicação real, estaremos com o cache de segundo nível habilitado, tornando desnecessárias essas pequenas otimizações.

Gerenciamento de Projetos com Scrum

Por Guilherme Silveira em 20/06/07

Scrum é sem dúvida a metodologia ágil que vem ganhando mais destaque ultimamente, especialmente depois de casos de sucesso com o Google, Yahoo! e o recente e forte apoio da Microsoft à metodologia.

Depois de muito estudo e preparativos, montamos, juntamente com Alexandre Magno, um treinamento de Gerenciamento de Projetos de Software com Scrum de 20 horas, o PM-81. O PM-81 aborda os conceitos teóricos, o timebox do Sprint, até a prática com diversos exercícios e dinâmicas. Ferramentas de gerenciamento ágil e a relação da metologia com XP, UP, FDD e CMMI também são tópicos desse treinamento.

Scrum Sprint Caelum

Alexandre Magno é consultor conhecido da área de gerenciamento de projetos. Possui perfil adequado como nossos outros instrutores: experiência prática no dia-a-dia com consultoria, excelente didática e amplo embasamento teórico. Ele é o coordenador responsável pelo treinamento e instrutor confirmado na turma que começa dia 23/07/2007. Com certeza excelente oportunidade para os diversos gerentes de projetos e aspirantes que já fizeram treinamento conosco. O Alexandre estará presente no Falando em Java 2007, onde você poderá conversar conosco a respeito de metodologias ágeis, além de muito Java, claro.

Hibernate Search com Lucene

Por Nico Steppat em 13/06/07

Há alguns meses tem um novo projeto na página do Hibernate. O Hibernate Search é uma “full text search engine” que tem sua implementação escrita em cima do Apache Lucene.

O Lucene por si só já é bem famoso pelas funcionalidades oferecidas, como buscas aproximadas, índices em memória, e velocidade. A parte mais complicada é a configuração e preparação (indexação) dos dados que são necessários para executar a busca. Aqui entra o Hibernate Search: ele te poupa desse trabalho e integra o Lucene muito bem com o Hibernate, dada a arquitetura de listeners e callbacks deste projeto. Com ele podemos buscar textualmente pelas entidades, usando as funcionalidades do Lucene sem a dor de cabeça da criação de Documents, definição dos tipos de Fields, controle de acesso concorrente aos índices e outros pequenos detalhes que aparecem ao usar o projeto diretamente. Vamos ver um exemplo simples como configurar e usar o Hibernate Search.

Anotações

Tanto nas Hibernate Annotations quanto no Hibernate Search você passa a maioria das configurações usando anotações no seu modelo de domínio. Para poder busca uma entidade você deve anota-lá com @Indexed. Usando esta anotação o Lucene irá analisar os dados da entidade e indexa-los, pois um interceptador do Hibernate fará esse trabalho por de trás dos panos. Quais dados você pretende indexar e como deve ser especificado anotando o atributo com @Field. Se você não usar nenhuma anotação nenhum dado será indexado. Além disso, é preciso anotar a chave primária com @DocumentId, como segue:


@Entity
@Indexed
public class Dvd {

  @Id  @GeneratedValue
  @DocumentId
  private Long id;

  @Field(index=Index.TOKENIZED,store=Store.YES)
  private String title;

  @Field(index=Index.TOKENIZED)
  private String description; 

        // possivelmente alguns getters e setters
}

Index

Usamos no exemplo os atributos index e store na anotação @Field. Quando Lucene indexa um campo, ele salva os dados num formato especial para ele poder executar suas buscas. Por exemplo, se você usa o título “Rambo 2″, o Lucene indexa Rambo e 2 separado. Esse tipo de indexação se chama TOKENIZED. Outros tipos são Index.NO (não será indexado e não tem como buscar) e Index.UN_TOKENIZED (não será “separado”, útil para campos considerado chaves). Essa nomenclatura é bem familiar para quem já usou o Lucene.

Store

Indica que Lucene deve gravar os dados (o título do DVD, no caso) no índice, de tal forma que esse valor possa ser recuperado integralmente diretamente pelos indices. Quando Lucene procura o título, ele pode devolver diretamente o valor, porque usamos Store.YES. É mais rápido, pois o hibernate não precisará consultar o banco para buscar aquele atributo, mas precisa mais espaço no disco e de certo forma é uma redundância de informação.

Eventos e diretórios

Hibernate Search encapsula o trabalho de indexação. Somente precisamos avisar o Hibernate Search em que momento nós desejaremos indexar as entidades em questão. Para isso existem listeners que podem ser configurados no hibernate.cfg.xml:

<session-factory>
    ...
    <event type="post-update">
            <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
    </event>
    <event type="post-insert">
            <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
    </event>
        <event type="post-delete">
            <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
    </event>
    ...
</session-factory>

Definimos 3 listeners globais. Hibernate os ativa automaticamente quando o tipo de evento acontecer. Nesse caso qualquer alteração numa entidade faz com que o Lucene altere o índice.

Além disso temos que definir um provedor que cria os arquivos e diretórios que serão usados para os índices do Lucene:

<session-factory>
   ...
   <property name="hibernate.search.default.directory_provider">
       org.hibernate.search.store.FSDirectoryProvider
   </property>
   <property name="hibernate.search.default.indexBase">
       /home/nico/workspace/mydvds/webapp/indexes
   </property>
   ...
</session-factory>

Existem outros provedores como RAMDirectoryProvider que somente grava na memória.

A busca

Configurado o Hibernate Search, vamos criar a busca. Você também usa um objeto session/query para fazer suas buscas, na verdade a sessão do Hibernate Search encapsula uma do hibernate. Para criar uma sessão usamos o método estático createFullTextSession da classe org.hibernate.search.Search e passamos a sessão do Hibernate como argumento:

FullTextSession LuceneSession = Search.createFullTextSession(hibernateSession);

Com a sessão na mão podemos criar um org.hibernate.search.FullTextQuery do hibernate search:

FullTextQuery fullTextQuery = LuceneSession.createFullTextQuery(LuceneQuery);

FullTextQuery estende Query do hibernate. Podemos invocar fullTextQuery.list() para receber a lista de objetos (neste caso dvds) baseado na query do Lucene.

Para criar essa query, usamos a api do Lucene. Ela oferece uma sintaxe simples e muito poderosa para definir nosso critério de busca. Essa sintaxe é verificada pelo QueryParser do Lucene:

String[] stopWords = new String[]{"de","do","da","dos","das","a","o","na","no","em"};    
QueryParser parser = new QueryParser("title",new StopAnalyzer(stopWords));));

Queremos procurar no campo title e passamos um objeto Analyzer. Ele é responsável por filtrar palavras insignificante na busca que foram definidos no array stopWords. Por padrão o StopAnalyzer somente conhece “stop words” do idioma inglês. É claro que o array deveria ser bem maior. Existem outros analyzers, como o BrazilianAnalyzer, que podem te ajudar bastante na tarefa de separar o que deve e como deve ser indexado.

Com o parser na mão podemos finalmente criar a query:

Query LuceneQuery = parser.parse("Deus");

procura por “deus” ou “Deus” e encontra o título “Cidade de Deus” por exemplo. Uma busca por “de” encontra nada, porque definimos “de” como uma stopword. Você pode fazer buscas muito mais interessantes, como “+cidade -Deus”, que busca por todos os dvds que possuem “cidade” mas não possuem “Deus” como título.

Interessante também é a possibilidade de fazer uma busca aproximada, as tão faladas fuzzy searches. Se você buscar por “cidadi~”, a query também devolverá o mesmo dvd como resultado da query!

O código final ficaria:

  public void search(Dvd dvdthrows ParseException {

    Session session = this.factory.getSession();
    
    FullTextSession LuceneSession = Search.createFullTextSession(session);
    
    String[] stopWords = {"de","do","da","dos","das","a","o","na","no","em"};    
    QueryParser parser = new QueryParser("title",new StopAnalyzer(stopWords));

    Query LuceneQuery = parser.parse(dvd.getTitle());
    FullTextQuery fullTextQuery = LuceneSession.createFullTextQuery(LuceneQuery, Dvd.class);    
  
    this.dvds = fullTextQuery.list();
  }

Repare que mesmo a query sendo do Hibernate Search, ela devolve uma lista de objetos managed de DVDs. Qualquer alteração deles acarretará em uma mudança no banco de dados e reindexação dos campos!

Conclusão

Essa foi simente uma introdução do Hibernate Search e Lucene. Hibernate Search facilita ainda mais a configuração e indexação pelo Lucene: possibilita, por exemplo, usar JMS para guardar os índices de maneira clusterizada, além de indexar os documentos assíncronamente. Também é simples fazer ordenações e projeções, combinações com criteria, mapeamento de relacionamento, boost factors, otimização dos índices, e muitos mais. O Paulo Silveira vai dar uma palestra entitulada “Organizando conteúdos com tags e Lucene” no evento Falando em Java da Caelum, que aborda alguns desses temas.

Obs: O codigo (sem jars) está disponível como download. Alterei a busca AJAX de DVDs da aplicação mydvds do vraptor para Hibernate Search.

Repository: seu modelo mais Orientado a Objeto

Por Fabio Kung em 09/06/07

Já tem algum tempo que a excelente discussão no GUJ estava me motivando a escrever a respeito.

Para ambientar, a principal discussão é usar:

Fornecedor fornecedor = ...;
List contas = dao.carregaContasPagasDesde1999(fornecedor);


Ou:

Fornecedor fornecedor = ...;
List contas = fornecedor.getContasPagasDesde1999()


Já que posts na forma de diálogos costumam ser muito interessantes, aproveito um papo que eu e o Paulo Silveira tivemos.

Paulo: credo, achei horroroso isso da classe de domínio acessar o repositório
Paulo: mas fica bonitinha a sentença

Fabio: horroroso pq? vc prefere procedural?
Fabio: repositório é domínio tb
Fabio: um List, um Map são repositórios
Fabio: vc acha acessar List e Map horroroso?

Paulo: tem certa razão
Paulo: mas entao poderia chamar o DAO diretamente de repositório

Fabio: em muitos casos poderíamos
Fabio: eu só estava pensando num jeito legal de injetar

Paulo: desde que o cara não receba os objetos de dominio

Fabio: talvez com interceptor de session, ou listener
Fabio: ou então injeta nos métodos load()

Paulo: então. Complicado um Usuario sempre precisar de Repositorio!

Fabio: todo usuário não
Fabio: só Usuário Managed
Fabio: quando vc dá new num Usuário, vc tb não consegue navegar em relacionamentos
Fabio: só faz sentido pegar relacionamento em managed

Paulo: certo
Paulo: mas olha só
Paulo: vai ter Usuario com repositório e outro sem
Paulo: em alguns vc vai poder chamar o método que faz buscas, em outros não

Fabio: mas isso já é assim

Paulo: não com o dao

Fabio: imagina Usuario @OneToMany Categoria

Paulo: certo

Fabio: vc só consegue chamar usuario.getCategorias() no managed, mesma coisa
Fabio: só vai poder chamar usuario.getCategoriasEspeciais() no managed
Fabio: não adianta dar new Usuario().getCategoriasEspecias();

Paulo: hum… entendi! Podia ser um interceptor como fizemos no caelumweb
Paulo: aí ele setava um atributo privado

Fabio: Ou então o próprio load do dao/repository pode fazer isso.

Paulo: mas sei la, baita arquiteturazinha complicada

Fabio: nada! só injetar o repositório
Fabio: o resto é simples
Fabio: em vez de chamar no dao, que é procedural, chama via getter

Paulo: não não, você tem razão. É facílimo de implementar
Paulo: mas tipo, é muita coisa

Fabio: muita coisa do jeito que estão falando no tópico do GUJ! 500 camadas…
Fabio: Repository não precisa nem ser interface

Paulo: aquele interceptador que fizemos para gravar no lucene já não gosto muuuuito

Fabio: Repository pode ter referência para Session
Fabio: e não precisa injetar o repositorio na entidade com listener/interceptor
Fabio: poderia ser:

public class Repository {
  private final Session session;
  public Repository(Session session) {
    this.session = session;
  }
  
  public void get(Long id) {
    // poderia delegar para um dao, se fosse necessário
    Usuario u =  session.load(id);
    u.setRepository(this);
  }
}

Fabio: ou seja, na hora que você recupera uma Entidade managed, ela já vem com o repositório embutido…

Paulo: dessa maneira dá um pouco mais de responsabilidade para os beanzinhos entidades né? fica legal

Fabio: isso!

Paulo: proximo projeto vamos tentar essa abordagem?

Fabio: demorou… ;)
Fabio: hoje em dia, se vc quiser mostrar um objeto e alguma pesquisa personalizada, vc tem que consultar o objeto e a lista nas lógicas
Fabio: separados! E aí ejetar os dois
Fabio: muito feio
Fabio: geralmente faz-se isso (código usando algum controlador ruinzinho):

public class Logica // extends Action? ;)
  public void mostraFornecedorEContasPagas() {
    Long id = request.getParameter("id")// século passado...
    Fornecedor f = fornecedorDAO.load(id)
    List<Conta> contasPagas = contasDAO.listaContasPagasDoMes(fornecedor);
    // ejeta tudo para mostrar na view. No século passado seria:
    request.setAttribute("fornecedor", fornecedor);
    request.setAttribute("contasPagas", contasPagas);
  }
}

Fabio: MUITO procedural!

Paulo: cara se aplicarmos essas idéias, com velocity isso ia ficar ANIMAL
Paulo: Ia dar para chamar os métodos a la DAO direto no beanzinho

Fabio: ISSO!

Paulo: #foreach contas in contas.desde(1994)

Fabio: é meu, legal né?
Fabio: com JSP EL dá tb

Paulo: mais ou menos, teriamos de mexer nos evaluators

Fabio: não, soh usar padrão javabean
Fabio: ahhh vc quer passar parâmetro, aí EL não rola mesmo
Fabio: mas é puro DDD, já fiquei pensando bastante sobre o assunto
Fabio: ultimamente que tenho enxergado um jeito legal de aplicar

Paulo: bacana
Paulo: acho q vale a gente testar
Paulo: parava de ficar enfiando getters para expor as coisas para view.
Paulo: FABIO TODO: blogar sobre isso

Feito! ;)