Hibernate.orgCommunity Documentation
Uma estratégia de busca é a estratégia que o Hibernate irá usar para recuperar objetos associados se a aplicação precisar navegar pela associação. Estratégias de Busca podem ser declaradas nos metadados de mapeamento O/R, ou sobrescritos por uma consulta HQL ou consulta com Criteria
.
Hibernate3 define as seguintes estratégias de busca:
Join fetching - o Hibernate busca o objeto ou coleção associada no mesmo SELECT
, usando um OUTER JOIN
.
Select fetching - um segundo SELECT
é usado para buscar a entidade ou coleção associada. A menos que você desabilite a busca lazy, especificando lazy="false"
, esse segundo SELECT será executado apenas quando você acessar a associação.
Subselect fetching - um segundo SELECT
será usado para recuperar as coleções associadas de todas as entidades recuperadas em uma consulta ou busca anterior. A menos que você desabilite a busca lazy especificando lazy="false"
, esse segundo SELECT será executado apenas quando você acessar a associação.
Batch fetching - uma opção de otimização para selecionar a busca. O Hibernate recupera um lote de instâncias ou entidades usando um único SELECT
, especificando uma lista de chaves primárias ou chaves externas.
O Hibernate distingue também entre:
Immediate fetching - uma associação, coleção ou função é imediatamente recuperada, quando o proprietário for carregado.
Lazy collection fetching - a coleção é recuperada quando a aplicação invoca uma operação sobre aquela coleção. Esse é o padrão para coleções.
"Extra-lazy" collection fetching - elementos individuais de uma coleção são acessados a partir do banco de dados quando necessário. O Hibernate tenta não buscar a coleção inteira dentro da memória a menos que seja absolutamente necessário. Isto é indicado para coleções muito grandes.
Proxy fetching: uma associação de um valor é carregada quando um método diferente do getter do identificador é invocado sobre o objeto associado.
"No-proxy" fetching - uma associação de um único valor é recuperada quando a variável da instância é acessada. Comparada à busca proxy, esse método é menos preguiçoso (lazy); a associação é buscada até mesmo quando somente o identificador é acessado. Ela é mais transparente, já que não há proxies visíveis para a aplicação. Esse método requer instrumentação de bytecodes em build-time e é raramente necessário.
Lazy attribute fetching: um atributo ou associação de um valor é buscado quanto a varíavel da instância é acessada. Esse método requer instrumentação de bytecodes em build-time e é raramente necessário.
Nós temos aqui duas noções ortogonais: quando a associação é buscada e como ela é buscada. É importante que você não os confuda. Nós usamos fetch
para ajustar o desempenho. Podemos usar lazy
para definir um contrato para qual dado é sempre disponível em qualquer instância desconectada de uma classe particular.
Por padrão, o Hibernate3 usa busca preguiçosa para coleções e busca preguiçosa com proxy para associações de um valor. Esses padrões fazem sentido para quase todas as associações em quase todas a aplicações.
Se você ajustar hibernate. default_batch_fetch_size
, o Hibernate irá usar otimização de busca em lote para a busca preguiçosa. Essa otimização pode ser também habilitada em um nível mais fino.
Perceba que o acesso a associações preguiçosas fora do contexto de uma sessão aberta do Hibernate irá resultar numa exceção. Por exemplo:
s = sessions.openSession();
Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName")
.setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();
tx.commit();
s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!
Como a coleção de permissões não foi inicializada quando a Session
for fechada, a coleção não poderá carregar o seu estado. O Hibernate não suporta inicialização preguiçosa para objetos desconectados. Para consertar isso, é necessário mover o código que carrega a coleção para logo antes da transação ser submetida.
Alternativamente, nós podemos usar uma coleção ou associação não preguiçosa, especificando lazy="false"
para o mapeamento da associação. Porém, é pretendido que a inicialização preguiçosa seja usada por quase todas as coleções e associações. Se você definir muitas associações não preguiçosas em seu modelo de objetos, o Hibernate irá precisar buscar no banco de dados inteiro da memória em cada transação.
Por outro lado, nós geralmente escolhemos a busca de união (que não é preguiçosa por natureza) ao invés do selecionar busca em uma transação particular. Nós agora veremos como customizar a estratégia de busca. No Hibernate3, os mecanismos para escolher a estratégia de busca são idênticos para as associações de valor único e para coleções.
O padrão selecionar busca, é extremamente vunerável aos problemas de seleção N+1, então habilitaremos a busca de união no documento de mapeamento:
<set name="permissions"
fetch="join">
<key column="userId"/>
<one-to-many class="Permission"/>
</set
<many-to-one name="mother" class="Cat" fetch="join"/>
A estratégia de fetch
definida no documento de mapeamento afeta:
recupera via get()
ou load()
Recuperações que acontecem implicitamente quando navegamos por uma associação
consultas por Criteria
consultas HQL se a busca por subselect
for usada
Independentemente da estratégia de busca que você usar, o gráfico não preguiçoso definido será certamente carregado na memória. Note que isso irá resultar em diversas seleções imediatas sendo usadas para rodar uma consulta HQL em particular.
Geralmente, não usamos documentos de mapeamento para customizar as buscas. Ao invés disso, nós deixamos o comportamento padrão e sobrescrevemos isso em uma transação em particular, usando left join fetch
no HQL. Isso diz ao Hibernate para buscar a associação inteira no primeiro select, usando uma união externa. Na API de busca Criteria
, você irá usar setFetchMode(FetchMode.JOIN)
.
Se você quiser mudar a estratégia de busca usada pelo get()
ou load()
, simplesmente use uma consulta por Criteria
, por exemplo:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
Isto é o equivalente do Hibernate para o que algumas soluções ORM chamam de "plano de busca".
Um meio totalmente diferente de evitar problemas com selects N+1 é usar um cache de segundo nível.
A recuperação preguiçosa para coleções é implementada usando uma implementação própria do Hibernate para coleções persistentes. Porém, é necessário um mecanismo diferente para comportamento preguiçoso em associações de final único. A entidade alvo da associação precisa usar um proxy. O Hibernate implementa proxies para inicialização preguiçosa em objetos persistentes usando manipulação de bytecode, através da excelente biblioteca CGLIB.
Por padrão, o Hibernate3 gera proxies (na inicialização) para todas as classes persistentes que os usem para habilitar recuperação preguiçosa de associações many-to-one
e one-to-one
.
O arquivo de mapeamento deve declarar uma interface para usar como interface de proxy para aquela classe, com a função proxy
. Por padrão, o Hibernate usa uma subclasse dessa classe. Note que a classe a ser usada via proxy precisa implementar o construtor padrão com pelo menos visibilidade de package. Nós recomendamos esse construtor para todas as classes persistentes.
Existe alguns truques que você deve saber quando estender esse comportamento para classes polimórficas. Por exemplo:
<class name="Cat" proxy="Cat">
......
<subclass name="DomesticCat">
.....
</subclass>
</class>
Primeiramente, instâncias de Cat
nunca serão convertidas para DomesticCat
, mesmo que a instância em questão seja uma instância de DomesticCat
:
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db)
if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy
DomesticCat dc = (DomesticCat) cat; // Error!
....
}
E, segundo, é possível quebrar o proxy ==
:
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy
DomesticCat dc =
(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!
System.out.println(cat==dc); // false
Porém a situação não é tão ruim como parece. Mesmo quando temos duas referências para objetos proxies diferentes, a instância adjacente será do mesmo objeto:
cat.setWeight(11.0); // hit the db to initialize the proxy
System.out.println( dc.getWeight() ); // 11.0
E por terceiro, você não pode usar um proxy CGLIB em uma classe final
ou com quaisquer métodos final
.
Finalmente, se o seu objeto persistente adquirir qualquer recurso durante a instanciação (ex. em inicializadores ou construtor padrão), então esses recursos serão adquiridos pelo proxy também. A classe de proxy é uma subclasse da classe persistente.
Esses problemas se dão devido à limitação originária do modelo de herança simples do Java. Se você quiser evitar esses problemas em suas classes persistentes você deve implementar uma interface que declare seus métodos comerciais. Você deve especificar essas interfaces no arquivo de mapeamento onde CatImpl
implementa a interface Cat
e DomesticCatImpl
implementa a interface DomesticCat
. Por exemplo:
<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>
Então, os proxies para instâncias de Cat
e DomesticCat
podem ser retornadas pelo load()
ou iterate()
.
Cat cat = (Cat) session.load(CatImpl.class, catid);
Iterator iter = session.createQuery("from CatImpl as cat where cat.name='fritz'").iterate();
Cat fritz = (Cat) iter.next();
list()
normalmente retorna proxies.
Relacionamentos são também inicializados de forma preguiçosa. Isso significa que você precisa declarar qualquer propriedade como sendo do tipo Cat
, e não CatImpl
.
Algumas operações não requerem inicialização por proxy:
equals()
: se a classe persistente não sobrescrever equals()
hashCode()
: se a classe persistente não sobrescrever hashCode()
O método getter do identificador
O Hibernate irá detectar classes persistentes que sobrescrevem equals()
ou hashCode()
.
Escolhendo lazy="no-proxy"
ao invés do padrão lazy="proxy"
, podemos evitar problemas associados com typecasting. Porém, iremos precisar de instrumentação de bytecode em tempo de compilação e todas as operações irão resultar em inicializações de proxy imediatas.
Será lançada uma LazyInitializationException
se uma coleção não inicializada ou proxy for acessado fora do escopo da Session
, isto é, quando a entidade que contém a coleção ou que possua a referência ao proxy estiver no estado desanexado.
Algumas vezes precisamos garantir que o proxy ou coleção é inicializado antes de fechar a Session
. Claro que sempre podemos forçar a inicialização chamando cat.getSex()
ou cat.getKittens().size()
, por exemplo. Mas isto parece confuso para quem lê o código e não é conveniente para códigos genéricos.
Os métodos estáticos Hibernate.initialize()
e Hibernate.isInitialized()
favorecem a aplicação para trabalhar com coleções ou proxies inicializados de forma preguiçosa. O Hibernate.initialize(cat)
irá forçar a inicialização de um proxy, cat
, contanto que a Session
esteja ainda aberta. Hibernate.initialize (cat.getKittens() )
tem um efeito similar para a coleção de kittens.
Uma outra opção é manter a Session
aberta até que todas as coleções e os proxies necessários sejam carregados. Em algumas arquiteturas de aplicações, particularmente onde o código que acessa os dados usando Hibernate e o código que os usa, se encontram em diferentes camadas da aplicação ou diferentes processos físicos, será um problema garantir que a Session
esteja aberta quando uma coleção for inicializada. Existem dois caminhos básicos para lidar com esse problema:
Em uma aplicações web, um filtro servlet pode ser usado para fechar a Session
somente no final da requisição do usuário, quando a renderização da view estiver completa (o modelo Abrir Sessão em View). Claro, que isto demanda uma exatidão no manuseio de exceções na infraestrutura de sua aplicação. É extremamente importante que a Session
seja fechada e a transação terminada antes de retornar para o usuário, mesmo que uma exceção ocorra durante a renderização da view. Veja o Wiki do Hibernate para exemplos do pattern "Abrir Sessão em View".
Em uma aplicação com uma camada de negócios separada, a lógica de negócios deve "preparar" todas as coleções que serão usadas pela camada web antes de retornar. Isto sgnifica que a camada de negócios deve carregar todos os dados e retorná-los já inicializados para a camada de apresentação que é representada para um caso de uso particular. Geralmente, a aplicação chama Hibernate.initialize()
para cada coleção que será usada pela camada web (essa chamada deve ocorrer antes da sessão ser fechada) ou retorna a coleção usando uma consulta Hibernate com uma cláusula FETCH
ou um FetchMode.JOIN
na Criteria
. Fica muito mais fácil se você adotar o modelo Command ao invés do Session Facade.
Você também pode anexar um objeto previamente carregado em uma nova Session
merge()
ou lock()
antes de acessar coleções não inicializadas (ou outros proxies). O Hibernate não faz e certamente não deve
fazer isso automaticamente, pois isso introduziria semântica em transações impromptu.
Às vezes você não quer inicializar uma coleção muito grande, mas precisa de algumas informações, como o mesmo tamanho, ou um subconjunto de seus dados.
Você pode usar um filtro de coleção para saber seu tamanho sem inicializá-la:
( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()
O método createFilter()
é usado também para retornar algus dados de uma coleção eficientemente sem precisar inicializar a coleção inteira:
s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();
O Hibernate pode fazer uso eficiente de busca em lote, ou seja o Hibernate pode carregar diversos proxies não inicializados, se um proxy for acessado (ou coleções). A busca em lote é uma otimização da estratégia da busca de seleção lazy. Existem duas maneiras para você usar a busca em lote: no nível da classe ou no nível da coleção.
A recuperação em lote para classes/entidades é mais fácil de entender. Imagine que você tem a seguinte situação em tempo de execução: você tem 25 instâncias de Cat
carregadas em uma Session
, cada Cat
possui uma referência ao seu owner
, que é da classe Person
. A classe Person
é mapeada com um proxy, lazy="true"
. Se você interar sobre todos os Cat's e chamar getOwner()
em cada, o Hibernate irá por padrão executar 25 comandos SELECT()
, para buscar os proxies de owners. Você pode melhorar esse comportamento especificando um batch-size
no mapeamento da classe Person
:
<class name="Person" batch-size="10">...</class>
O Hibernate irá executar agora apenas três consultas; o padrão é 10, 10, 5.
Você também pode habilitar busca em lote de uma coleção. Por exemplo, se cada Person
tem uma coleção preguiçosa de Cat
s e 10 persons estão já carregadas em uma Session
, serão gerados 10 SELECT
s ao se interar todas as persons, um para cada chamada de getCats()
. Se você habilitar busca em lote para a coleção de cats
no mapeamento da classe Person
, o Hibernate pode fazer uma pré carga das coleções:
<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>
Com um batch-size
de 3, o Hibernate irá carregar 3, 3, 3, 1 coleções em 4 SELECT
s. Novamente, o valor da função depende do número esperado de coleções não inicializadas em determinada Session
.
A busca em lote de coleções é particularmente útil quando você tem uma árvore encadeada de ítens, ex.: o típico padrão bill-of-materials (Se bem que um conjunto encadeado ou caminho materializado pode ser uma opção melhor para árvores com mais leitura.
Se uma coleção ou proxy simples precisa ser recuperado, o Hibernate carrega todos eles rodando novamente a consulta original em uma subseleção. Isso funciona da mesma maneira que busca em lote, sem carregar tanto.
Another way to affect the fetching strategy for loading associated objects is through something called a fetch profile, which is a named configuration associated with the org.hibernate.SessionFactory
but enabled, by name, on the org.hibernate.Session
. Once enabled on a org.hibernate.Session
, the fetch profile will be in affect for that org.hibernate.Session
until it is explicitly disabled.
So what does that mean? Well lets explain that by way of an example which show the different available approaches to configure a fetch profile:
Exemplo 21.1. Specifying a fetch profile using @FetchProfile
@Entity
@FetchProfile(name = "customer-with-orders", fetchOverrides = {
@FetchProfile.FetchOverride(entity = Customer.class, association = "orders", mode = FetchMode.JOIN)
})
public class Customer {
@Id
@GeneratedValue
private long id;
private String name;
private long customerNumber;
@OneToMany
private Set<Order> orders;
// standard getter/setter
...
}
Exemplo 21.2. Specifying a fetch profile using <fetch-profile>
outside <class>
node
<hibernate-mapping>
<class name="Customer">
...
<set name="orders" inverse="true">
<key column="cust_id"/>
<one-to-many class="Order"/>
</set>
</class>
<class name="Order">
...
</class>
<fetch-profile name="customer-with-orders">
<fetch entity="Customer" association="orders" style="join"/>
</fetch-profile>
</hibernate-mapping>
Exemplo 21.3. Specifying a fetch profile using <fetch-profile>
inside <class>
node
<hibernate-mapping>
<class name="Customer">
...
<set name="orders" inverse="true">
<key column="cust_id"/>
<one-to-many class="Order"/>
</set>
<fetch-profile name="customer-with-orders">
<fetch association="orders" style="join"/>
</fetch-profile>
</class>
<class name="Order">
...
</class>
</hibernate-mapping>
Now normally when you get a reference to a particular customer, that customer's set of orders will be lazy meaning we will not yet have loaded those orders from the database. Normally this is a good thing. Now lets say that you have a certain use case where it is more efficient to load the customer and their orders together. One way certainly is to use "dynamic fetching" strategies via an HQL or criteria queries. But another option is to use a fetch profile to achieve that. The following code will load both the customer andtheir orders:
Exemplo 21.4. Activating a fetch profile for a given Session
Session session = ...;
session.enableFetchProfile( "customer-with-orders" ); // name matches from mapping
Customer customer = (Customer) session.get( Customer.class, customerId );
@FetchProfile
definitions are global and it does not matter on which class you place them. You can place the @FetchProfile
annotation either onto a class or package (package-info.java). In order to define multiple fetch profiles for the same class or package @FetchProfiles
can be used.
Apenas os perfis de busca em estilo são suportados, mas planeja-se o suporte de estilos adicionais. Consulte HHH-3414 para maiores detalhes.
O Hibernate3 suporta a busca lazy de propriedades individuais. Essa técnica de otimização é também conhecida como grupos de busca. Veja que esta é mais uma característica de marketing já que na prática, é mais importante a otimização nas leituras dos registros do que na leitura das colunas. Porém, carregar apenas algumas propriedades de uma classe pode ser útil em casos extremos, onde tabelas legadas podem ter centenas de colunas e o modelo de dados não pode ser melhorado.
Para habilitar a carga de propriedade lazy, é preciso ajustar a função lazy
no seu mapeamento de propriedade:
<class name="Document">
<id name="id">
<generator class="native"/>
</id>
<property name="name" not-null="true" length="50"/>
<property name="summary" not-null="true" length="200" lazy="true"/>
<property name="text" not-null="true" length="2000" lazy="true"/>
</class>
A carga de propriedades lazy requer instrumentação de bytecode. Se suas classes persistentes não forem melhoradas, o Hibernate irá ignorar silenciosamente essa configuração e usará a busca imediata.
Para instrumentação de bytecode, use a seguinte tarefa do Ant:
<target name="instrument" depends="compile">
<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
<classpath path="${jar.path}"/>
<classpath path="${classes.dir}"/>
<classpath refid="lib.class.path"/>
</taskdef>
<instrument verbose="true">
<fileset dir="${testclasses.dir}/org/hibernate/auction/model">
<include name="*.class"/>
</fileset>
</instrument>
</target>
Uma forma diferente de evitar leitura de coluna desnecessária, ao menos para transações de somente leitura, deve-se usar os recursos de projeção do HQL ou consultas por Critério. Isto evita a necessidade de processamento de bytecode em build-time e é certamente uma melhor solução.
Você pode forçar a busca antecipada comum de propriedades usando buscar todas as propriedades
no HQL.
Uma Session
do Hibernate é um cache de nível transacional de dados persistentes. É possível configurar um cluster ou um cache de nível JVM (nível SessionFactory
) em uma estrutura classe por classe e coleção por coleção. Você pode até mesmo plugar em um cache em cluster. Tenha cuidado, pois os caches nunca sabem das mudanças feitas em armazenamento persistente por um outro aplicativo. No entanto, eles podem ser configurados para dados em cache vencido regularmente.
You have the option to tell Hibernate which caching implementation to use by specifying the name of a class that implements org.hibernate.cache.CacheProvider
using the property hibernate.cache.provider_class
. Hibernate is bundled with a number of built-in integrations with the open-source cache providers that are listed in Tabela 21.1, “Provedores de Cache ”. You can also implement your own and plug it in as outlined above. Note that versions prior to Hibernate 3.2 use EhCache as the default cache provider.
Tabela 21.1. Provedores de Cache
Cache | Classe de provedor | Tipo | Segurança de Cluster | Cache de Consulta Suportado |
---|---|---|---|---|
Hashtable (não recomendado para uso de produção) | org.hibernate.cache.HashtableCacheProvider | memória | yes | |
EHCache | org.hibernate.cache.EhCacheProvider | memory, disk, transactional, clustered | yes | yes |
OSCache | org.hibernate.cache.OSCacheProvider | memória, disco | yes | |
SwarmCache | org.hibernate.cache.SwarmCacheProvider | clustered (ip multicast) | sim (invalidação em cluster) | |
JBoss Cache 1.x | org.hibernate.cache.TreeCacheProvider | (ip multicast) em cluster, transacional | sim (replicação) | sim (solicitação de sync. de relógio) |
JBoss Cache 2 | org.hibernate.cache.jbc.JBossCacheRegionFactory | (ip multicast) em cluster, transacional | sim (invalidação ou replicação) | sim (solicitação de sync. de relógio) |
As we have done in previous chapters we are looking at the two different possibiltites to configure caching. First configuration via annotations and then via Hibernate mapping files.
By default, entities are not part of the second level cache and we recommend you to stick to this setting. However, you can override this by setting the shared-cache-mode
element in your persistence.xml
file or by using the javax.persistence.sharedCache.mode
property in your configuration. The following values are possible:
ENABLE_SELECTIVE
(Default and recommended value): entities are not cached unless explicitly marked as cacheable.
DISABLE_SELECTIVE
: entities are cached unless explicitly marked as not cacheable.
ALL
: all entities are always cached even if marked as non cacheable.
NONE
: no entity are cached even if marked as cacheable. This option can make sense to disable second-level cache altogether.
The cache concurrency strategy used by default can be set globaly via the hibernate.cache.default_cache_concurrency_strategy
configuration property. The values for this property are:
read-only
read-write
nonstrict-read-write
transactional
It is recommended to define the cache concurrency strategy per entity rather than using a global one. Use the @org.hibernate.annotations.Cache
annotation for that.
Exemplo 21.5. Definition of cache concurrency strategy via @Cache
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Forest { ... }
Hibernate also let's you cache the content of a collection or the identifiers if the collection contains other entities. Use the @Cache
annotation on the collection property.
Exemplo 21.6. Caching collections using annotations
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public SortedSet<Ticket> getTickets() {
return tickets;
}
Exemplo 21.7, “@Cache annotation with attributes”shows the @org.hibernate.annotations.Cache
annotations with its attributes. It allows you to define the caching strategy and region of a given second level cache.
Exemplo 21.7. @Cache
annotation with attributes
@Cache( CacheConcurrencyStrategy usage(); String region() default ""; String include() default "all"; )
usage: the given cache concurrency strategy (NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL) | |
region (optional): the cache region (default to the fqcn of the class or the fq role name of the collection) | |
|
Let's now take a look at Hibernate mapping files. There the <cache>
element of a class or collection mapping is used to configure the second level cache. Looking at Exemplo 21.8, “The Hibernate <cache> mapping element” the parallels to anotations is obvious.
Exemplo 21.8. The Hibernate <cache>
mapping element
<cache usage="transactional|read-write|nonstrict-read-write|read-only" region="RegionName" include="all|non-lazy" />
| |
| |
|
Alternatively to <cache>
, you can use <class-cache>
and <collection-cache>
elements in hibernate.cfg.xml
.
Let's now have a closer look at the different usage strategies
Se sua aplicação precisar ler mas nunca modificar instâncias de uma classe persistente, pode-se utilizar um cache de read-only
. Esta é a estratégia de desempenho mais simples e melhor. É também perfeitamente seguro para uso em um cluster.
Se a aplicação precisar atualizar dados, um cache de read-write
pode ser mais apropriado. Esta estratégia de cache nunca deve ser usada se solicitado um nível de isolação de transação serializável. Se o cache for usado em um ambiente JTA, você deve especificar a propriedade hibernate.transaction.manager_lookup_class
, nomeando uma estratégia por obter o TransactionManager
JTA. Em outros ambientes, você deve assegurar que a transação está completa quando a Session.close()
ou Session.disconnect()
for chamada. Se desejar utilizar esta estratégia em um cluster, você deve assegurar que a implementação de cache adjacente suporta o bloqueio. Os provedores de cache built-in não suportam o bloqueamento.
Se a aplicação somente precisa atualizar dados ocasionalmente (ou seja, se for extremamente improvável que as duas transações tentem atualizar o mesmo ítem simultaneamente) e não for requerido uma isolação de transação estrita, o uso deum cache de nonstrict-read-write
pode ser mais apropriado. Se um cache é usado em ambiente JTA, você deverá especificar o hibernate.transaction.manager_lookup_class
. Em outros ambientes, você deve assegurar que a transação está completa quando a Session.close()
ou Session.disconnect()
for chamada.
A estratégia de cache transactional
provê suporte para provedores de cache transacional completo como o JBoss TreeCache. Tal cache, deve ser usado somente em um ambiente JTA e você deverá especificar o hibernate.transaction.manager_lookup_class
.
Nenhum provedor de cache suporta todas as estratégias de concorrência de cache.
A seguinte tabela mostra qual provedor é compatível com qual estratégia de concorrência.
Tabela 21.2. Suporte de Estratégia de Concorrência de Cache
Cache | read-only | nonstrict-read-write | read-write | transactional |
---|---|---|---|---|
Hashtable (não recomendado para uso de produção) | yes | yes | yes | |
EHCache | yes | yes | yes | yes |
OSCache | yes | yes | yes | |
SwarmCache | yes | yes | ||
JBoss Cache 1.x | yes | yes | ||
JBoss Cache 2 | yes | yes |
Quando passar um objeto para save()
, update()
ou saveOrUpdate()
e quando recuperar um objeto usando um load()
, get()
, list()
, iterate()
ou scroll()
, este objeto será adicionado ao cache interno da Session
.
Quando o flush()
for subsequentemente chamado, o estado deste objeto será sincronizado com o banco de dados. Se você não desejar que esta sincronização aconteça ou se você estiver processando uma grande quantidade de objetos e precisar gerenciar a memória de forma eficiente, o método evict()
pode ser usado para remover o objeto de suas coleções de cache de primeiro nível.
Exemplo 21.9. Explcitly evicting a cached instance from the first level cache using Session.evict()
ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
while ( cats.next() ) {
Cat cat = (Cat) cats.get(0);
doSomethingWithACat(cat);
sess.evict(cat);
}
A Session
também oferece um métodocontains()
para determinar se uma instância pertence ao cache de sessão.
Para despejar completamente todos os objetos do cache de Sessão, chame Session.clear()
Para o cache de segundo nível, existem métodos definidos na SessionFactory
para despejar o estado de cache de uma instância, classe inteira, instância de coleção ou papel de coleção inteiro.
Exemplo 21.10. Second-level cache eviction via SessionFactoty.evict()
and SessionFacyory.evictCollection()
sessionFactory.evict(Cat.class, catId); //evict a particular Cat
sessionFactory.evict(Cat.class); //evict all Cats
sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections
O CacheMode
controla como uma sessão em particular interage com o cache de segundo nível:
CacheMode.NORMAL
- lê e escreve itens ao cache de segundo nível.
CacheMode.GET
: itens de leitura do cache de segundo nível. Não escreve ao cache de segundo nível, exceto quando atualizar dados.
CacheMode.PUT
: escreve itens ao cache de segundo nível. Não lê a partir do cache de segundo nível.
CacheMode.REFRESH
: escreve itens ao cache de segundo nível, mas não lê a partir do cache de segundo nível. Passa o efeito de hibernate.cache.use_minimal_puts
, forçando uma atualização do cache de segundo nível para que todos os itens leiam a partir do banco de dados.
Para navegar o conteúdo do segundo nível ou região de cache de consulta, use oStatistics
API:
Exemplo 21.11. Browsing the second-level cache entries via the Statistics
API
Map cacheEntries = sessionFactory.getStatistics()
.getSecondLevelCacheStatistics(regionName)
.getEntries();
Você precisará habilitar estatísticas e, opcionalmente, forçar o Hibernate a manter as entradas de cache em um formato mais compreensível:
Exemplo 21.12. Enabling Hibernate statistics
hibernate.generate_statistics true hibernate.cache.use_structured_entries true
O conjunto de resultado de consulta pode também estar em cache. Isto é útil, somente para consultas que são rodadas freqüentemente com os mesmos parâmetros.
A aplicação do cache nos resultados de consulta introduz alguns resultados referentes o seu processamento transacional normal de aplicações. Por exemplo, se você realizar o cache nos resultados de uma consulta do Person Hibernate, você precisará acompanhar quando estes resultados deverão ser inválidos devido alterações salvas no Person. Tudo isto, acompanhado com o fato de que a maioria dos aplicativos não recebem benefício algum ao realizar o cache nos resultados da consulta, levando o Hibernate a desativar o cache de resultados de consulta por padrão. Para uso do cache de consulta, você primeiro precisa ativar o cache de consulta:
hibernate.cache.use_query_cache true
Esta configuração cria duas novas regiões de cache:
org.hibernate.cache.StandardQueryCache
, mantendo os resultados da consulta com cache.
org.hibernate.cache.UpdateTimestampsCache
, mantém os timestamps das atualizações mais recentes para tabelas consultáveis. Elas são usadas para validar os resultados uma vez que elas são servidas a partir do cache de consulta.
If you configure your underlying cache implementation to use expiry or timeouts is very important that the cache timeout of the underlying cache region for the UpdateTimestampsCache be set to a higher value than the timeouts of any of the query caches. In fact, we recommend that the the UpdateTimestampsCache region not be configured for expiry at all. Note, in particular, that an LRU cache expiry policy is never appropriate.
Conforme mencionado acima, a maioria das consultas não se beneficiam do cache ou de seus resultados. Portanto por padrão, as consultas individuais não estão em cache mesmo depois de ativar o cache de consulta. Para habilitar o caching de resultados, chame org.hibernate.Query.setCacheable(true)
. Esta chamada permite que a consulta procure por resultados de caches existentes ou adicione seus resultados ao cache quando for executado.
O cache de consulta não realiza o cache ao estado de entidades atuais no cache, ele apenas realiza o cache nos valores identificadores e resultados do tipo de valor. Por esta razão, o cache de consulta deve sempre ser usado em conjunção com o cache de segundo nível para as entidades esperadas a sofrerem o cache como parte de um cache de resultado de consulta (apenas com o cache de coleção).
Se você solicitar um controle de granulado fino com políticas de validade do cache de consulta, você poderá especificar uma região de cache nomeada para uma consulta em particular, chamando Query.setCacheRegion()
.
List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
.setEntity("blogger", blogger)
.setMaxResults(15)
.setCacheable(true)
.setCacheRegion("frontpages")
.list();
Se você quiser forçar um cache de consulta para uma atualização de sua região (independente de quaisquer resultados com cache encontrados nesta região), você poderá usar org.hibernate.Query.setCacheMode(CacheMode.REFRESH)
. Juntamente com a região que você definiu para o cache gerado, o Hibernate seletivamente forçará os resultados com cache, naquela região particular a ser atualizada. Isto é particularmente útil em casos onde dados adjacentes podem ter sido atualizados através de um processo em separado , além de ser uma alternativa mais eficiente se aplicada ao despejo de uma região de cache através de SessionFactory.evictQueries()
.
Nas seções anteriores nós descrevemos as coleções e seus aplicativos. Nesta seção nós exploraremos mais problemas em relação às coleções no período de execução.
O Hibernate define três tipos básicos de coleções:
Coleções de valores
Associações um-para-muitos
Associações muitos-para-muitos
A classificação distingue as diversas tabelas e relacionamento de chave externa, mas não nos diz tudo que precisamos saber sobre o modelo relacional. Para entender completamente a estrutura relacional e as características de desempenho, devemos também considerar a estrutura da chave primária que é usada pelo Hibernate para atualizar ou deletar linhas de coleções. Isto sugere a seguinte classificação:
Coleções indexadas
conjuntos
Bags
Todas as coleções indexadas (mapas, listas, matrizes) possuem uma chave primária, que consiste em colunas <key>
e <index>
. Neste caso, as atualizações de coleção são geralmente muito eficientes. A chave primária pode ser indexada de forma eficiente e uma linha em particular pode ser localizada de forma eficiente quando o Hibernate tentar atualizar ou deletá-la.
Os conjuntos possuem uma chave primária que consiste em <key>
e colunas de elemento. Isto pode ser menos eficiente para alguns tipos de elementos de coleções, especialmente elementos compostos ou textos grandes ou ainda campos binários. O banco de dados pode não ser capaz de indexar uma chave primária complexa de forma tão eficiente. Por um outro lado, para associações um-para-muitos ou muitos-para-muitos, especialmente no caso de identificadores sintáticos, é bem provável que seja tão eficiente quanto. Se você quiser que o SchemaExport
crie para você uma chave primária de um <set>
você deverá declarar todas as colunas como not-null="true"
.
Os mapeamentos <idbag>
definem uma chave substituta, para que elas sejam sempre muito eficientes ao atualizar. Na verdade, este é o melhor caso.
As Bags são os piores casos. Como uma bag permite duplicar valores de elementos e não possui coluna de índice, não se deve definir nenhuma chave primária. O Hibernate não tem como distinguir entre linhas duplicadas. O Hibernate resolve este problema, removendo completamente em um único DELETE
e recria a coleção quando mudar. Isto pode ser bastante ineficiente.
Note que para uma associação um-para-muitos, a chave primária pode não ser a chave primária física da tabela do banco de dados, mas mesmo neste caso, a classificação acima é ainda útil. Isto reflete como o Hibernate "localiza" linhas individuais da coleção.
A partir da discussão acima, deve ficar claro que as coleções indexadas e conjuntos (geralmente) permitem uma operação mais eficente em termos de adição, remoção e atualização de elementos.
Existe ainda, mais uma vantagem, das coleções indexadas sob conjuntos para associações muitos-para-muitos. Por causa da estrutura de um Set
, o Hibernate nunca utiliza o comando UPDATE
em uma linha quando um elemento é "modificado". As mudanças para o Conjunto
funcionam sempre através do comando INSERT
e DELETE
de linhas individuais. Novamente, esta consideração não se aplica às associações um para muitos.
Após observar que as matrizes não podem ser preguiçosas, nós concluimos que as listas, mapas e bags de id são tipos de coleções com maior desempenho (não inverso), com conjuntos que não ficam atrás. Espera-se que os conjuntos sejam um tipo mais comum de coleção nas aplicações Hibernate. Isto porque as semânticas "conjunto" são mais naturais em modelos relacionais.
No entanto, em modelos de domínio de Hibernate bem criados, geralmente vemos que a maioria das coleções são de fato, associações um-para-muitos com inverse="true"
. Para estas associações, a atualização é manipulada pelo lado muitos-para-um de uma associação e portanto considerações de desempenho de atualização de coleção simplesmente não se aplicam a este caso.
Existe um caso em particular no qual as bags (e também as listas) possuem um desempenho muito maior do que conjuntos. Para uma coleção com inverse="true"
, o idioma de relacionamento um-para-um bidirecional padrão, por exemplo, podemos adicionar elementos a uma bag ou uma lista sem precisar inicializar (buscar) os elementos da bag. Isto acontece porque a Collection.add()
ou Collection.addAll()
deve sempre retornar verdadeira para uma bag ou List
. Isto pode fazer que o código comum seguinte seja muito mais rápido:
Parent p = (Parent) sess.load(Parent.class, id);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c); //no need to fetch the collection!
sess.flush();
Às vezes, deletar elementos de coleção um por um pode ser extremamente ineficiente. O Hibernate não é completamente burro, portanto ele sabe que não deve fazer isso no caso de uma coleção que tenha sido esvaziada recentemente (se você chamou list.clear()
, por exemplo). Neste caso, o Hibernate irá editar um único DELETE
.
Vamos supor que tenha adicionado um elemento único à uma coleção de tamanho vinte e então remove dois elementos. O Hibernate irá editar uma instrução INSERT
e duas instruções DELETE
, a não ser que a coleção seja uma bag. Isto é certamente desejável.
No entanto, suponha que removamos dezoito elementos, deixando dois e então adicionando três novos elementos. Existem duas formas possíveis de se proceder:
delete dezoito linhas uma por uma e então insira três linhas
remova toda a coleção em um SQL DELETE
e insira todos os cinco elementos atuais, um por um
O Hibernate não sabe que a segunda opção é provavelmente mais rápida neste caso. O Hibernate não deseha saber a opção, uma vez que tal comportamento deve confundir os triggers do banco de dados, etc.
Felizmente, você pode forçar este comportamento (ou seja, uma segunda estratégia) a qualquer momento, descartando (ou seja, desreferenciando) a coleção original e retornando uma coleção recentemente instanciada com todos os elementos atuais.
É claro que, deletar somente uma vez, não se aplica às coleções mapeadas inverse="true"
.
A otimização não é muito usada sem o monitoramento e acesso ao número de desempenho. O Hibernate oferece uma grande variedade de números sobre suas operações internas. Estatísticas em Hibernate estão disponíveis através do SessionFactory
.
Você poderá acessar as métricas da SessionFactory
de duas formas. Sua primeira opção é chamar a sessionFactory.getStatistics()
e ler ou dispôr as Estatísticas
você mesmo.
O Hibernate também usa o JMX para publicar métricas se você habilitar o MBean de StatisticsService
. Você deve habiliar um MBean único para todas as suas SessionFactory
ou uma por factory. Veja o seguinte código para exemplos de configurações minimalísticos:
// MBean service registration for a specific SessionFactory
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "myFinancialApp");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory
server.registerMBean(stats, on); // Register the Mbean on the server
// MBean service registration for all SessionFactory's
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "all");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
server.registerMBean(stats, on); // Register the MBean on the server
Você pode (des)ativar o monitoramento para uma SessionFactory
:
no tempo de configuração, ajuste hibernate.generate_statistics
para falso
em tempo de espera: sf.getStatistics().setStatisticsEnabled(true)
ou hibernateStatsBean.setStatisticsEnabled(true)
As estatísticas podem ser reajsutadas de forma programática, usando o método clear()
. Um resumo pode ser enviado para o usuário (nível de info) usando o método logSummary()
.
O Hibernate oferece um número de métricas, desde informações bem básicas até especializadas, somente relevantes a certos cenários. Todos os contadores disponíveis estão descritos na API da interface Statistics
, em três categorias:
As métricas relacionadas ao uso da Sessão
, tal como um número de sessões em aberto, conexões JDBC recuperadas, etc.
As métricas relacionadas às entidades, coleções, consultas e caches como um todo (mais conhecido como métricas globais).
Métricas detalhadas relacionadas à uma entidade em particular, coleção, consulta ou região de cache.
Por exemplo, você pode verificar a coincidência de um cache, perder e colocar a relação entre as entidades, colações e consultas e tempo médio que uma consulta precisa. Esteja ciente de que o número de milisegundos é sujeito a aproximação em Java. O Hibernate é preso à precisão do JVM, em algumas plataformas a precisão chega a ser de 10 segundos.
Os Getters simples são usados para acessar métricas globais (ou seja, não presos à uma entidade em particular, coleção, região de cache, etc.) Você pode acessar as métricas de uma entidade em particular, coleção ou região de cache através de seu nome e através de sua representação de HQL ou SQL para consultas. Por favor consulte a Javadoc API Statistics
, EntityStatistics
, CollectionStatistics
, SecondLevelCacheStatistics
, e QueryStatistics
para maiores informações. O seguinte código mostra um exemplo simples:
Statistics stats = HibernateUtil.sessionFactory.getStatistics();
double queryCacheHitCount = stats.getQueryCacheHitCount();
double queryCacheMissCount = stats.getQueryCacheMissCount();
double queryCacheHitRatio =
queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
log.info("Query Hit ratio:" + queryCacheHitRatio);
EntityStatistics entityStats =
stats.getEntityStatistics( Cat.class.getName() );
long changes =
entityStats.getInsertCount()
+ entityStats.getUpdateCount()
+ entityStats.getDeleteCount();
log.info(Cat.class.getName() + " changed " + changes + "times" );
Para trabalhar em todas as entidades, coleções, consultas e caches regionais, você poderá recuperar os nomes de lista de entidades, coleções, consultas e caches regionais com os seguintes métodos: getQueries()
, getEntityNames()
, getCollectionRoleNames()
, e getSecondLevelCacheRegionNames()
.
Copyright © 2004 Red Hat, Inc.