Fill in the TODO sections for the Locking and Caching chapters

This commit is contained in:
vladmihalcea 2016-01-21 16:50:53 +02:00
parent f58c8eba9c
commit d769308785
11 changed files with 1444 additions and 47 deletions

View File

@ -55,6 +55,7 @@ dependencies {
testCompile( project(':hibernate-core') )
testCompile( project(':hibernate-entitymanager') )
testCompile( project(':hibernate-ehcache') )
testCompile( project(':hibernate-testing') )
testCompile( project(path: ':hibernate-entitymanager', configuration: 'tests') )

View File

@ -1,81 +1,571 @@
[[caching]]
== Caching
:sourcedir: ../../../../../test/java/org/hibernate/jpa/test/userguide/caching
At runtime, Hibernate handles moving data into and out of the second-level cache in response to the operations performed by the `Session`, which acts as a transaction-level cache of persistent data.
Once an entity becomes managed, that object is added to the internal cache of the current persistence context (`EntityManager` or `Session`).
The persistence context is also called the first-level cache, and it's enabled by default.
It is possible to configure a JVM-level (`SessionFactory`-level) or even a cluster cache on a class-by-class and collection-by-collection basis.
[NOTE]
====
Be aware that caches are not aware of changes made to the persistent store by another applications.
They can, however, be configured to regularly expire cached data.
====
[[caching-config]]
=== Configuring second-level caching
Hibernate defines the ability to integrate with pluggable providers for the purpose of caching data outside the context of a particular `Session`.
This section defines the settings which control that behavior.
Hibernate can integrate with various caching providers for the purpose of caching data outside the context of a particular `Session`.
This section defines the settings which control this behavior.
[[caching-config-provider]]
=== RegionFactory
==== RegionFactory
`org.hibernate.cache.spi.RegionFactory` defines the integration between Hibernate and a pluggable caching provider.
`hibernate.cache.region.factory_class` is used to declare the provider to use. Hibernate comes with support for 2 popular caching libraries: Ehcache and Infinispan.
`hibernate.cache.region.factory_class` is used to declare the provider to use.
Hibernate comes with built-in support for two popular caching libraries: http://www.ehcache.org/[Ehcache] and http://infinispan.org/[Infinispan].
[[caching-config-provider-ehcache]]
=== Ehcache
===== Ehcache
[IMPORTANT]
[NOTE]
====
Use of the build-in integration for Ehcache requires that the hibernate-ehcache module jar (and all of its dependencies) are on the classpath.
Use of the build-in integration for Ehcache requires that the `hibernate-ehcache` module jar (and all of its dependencies) are on the classpath.
====
The hibernate-ehcache module defines 2 specific region factories: `EhCacheRegionFactory` and `SingletonEhCacheRegionFactory`.
The hibernate-ehcache module defines two specific region factories: `EhCacheRegionFactory` and `SingletonEhCacheRegionFactory`.
[[caching-config-provider-ehcache-region-factory]]
==== `EhCacheRegionFactory`
====== `EhCacheRegionFactory`
TODO
To use the `EhCacheRegionFactory`, you need to specify the following configuration property:
[[caching-config-provider-ehcache-region-factory-example]]
.`EhCacheRegionFactory` configuration
====
[source, XML, indent=0]
----
<property
name="hibernate.cache.region.factory_class"
value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
----
====
The `EhCacheRegionFactory` configures a `net.sf.ehcache.CacheManager` for each `SessionFactory`,
so the `CacheManager` is not shared among multiple `SessionFactory` instances in the same JVM.
[[caching-config-provider-ehcache-singleton-region-factory]]
==== `SingletonEhCacheRegionFactory`
====== `SingletonEhCacheRegionFactory`
TODO
To use the `SingletonEhCacheRegionFactory`, you need to specify the following configuration property:
[[caching-config-provider-ehcache-singleton-region-factory-example]]
.`SingletonEhCacheRegionFactory` configuration
====
[source, XML, indent=0]
----
<property
name="hibernate.cache.region.factory_class"
value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/>
----
====
The `SingletonEhCacheRegionFactory` configures a singleton `net.sf.ehcache.CacheManager` (see http://www.ehcache.org/apidocs/2.8.4/net/sf/ehcache/CacheManager.html#create%28%29[CacheManager#create()]),
shared among multiple `SessionFactory` instances in the same JVM.
[NOTE]
====
http://www.ehcache.org/documentation/2.8/integrations/hibernate#optional[Ehcache documentation] recommends using multiple non-singleton `CacheManager(s)` when there are multiple Hibernate `SessionFactory` instances running in the same JVM.
====
[[caching-config-provider-infinispan]]
=== Infinispan
===== Infinispan
[IMPORTANT]
[NOTE]
====
Use of the build-in integration for Infinispan requires that the hibernate-infinispan module jar (and all of its dependencies) are on the classpath.
Use of the build-in integration for Infinispan requires that the `hibernate-infinispan module` jar (and all of its dependencies) are on the classpath.
====
The hibernate-infinispan module defines 2 specific providers: `infinispan` and `infinispan-jndi`.
The hibernate-infinispan module defines two specific providers: `infinispan` and `infinispan-jndi`.
TODO
[[caching-config-provider-infinispan-region-factory]]
===== `InfinispanRegionFactory`
[[caching-config-behavior]]
=== Caching behavior
If Hibernate and Infinispan are running in a standalone environment, the `InfinispanRegionFactory` should be configured as follows:
Besides specific provider configuration, there are a number of configurations options on the Hibernate side of the integration that control various caching behavior:
[[caching-config-provider-infinispan-region-factory-example]]
.`InfinispanRegionFactory` configuration
====
[source, XML, indent=0]
----
<property
name="hibernate.cache.region.factory_class"
value="org.hibernate.cache.infinispan.InfinispanRegionFactory" />
----
====
`hibernate.cache.use_second_level_cache`:: Enable or disable second level caching overall. Default is true, although the default region factory is `NoCachingRegionFactory`.
`hibernate.cache.use_query_cache`:: Enable or disable second level caching of query results. Default is false.
`hibernate.cache.query_cache_factory`:: Query result caching is handled by a special contract that deals with staleness-based invalidation of the results.
The default implementation does not allow stale results at all. Use this for applications that would like to relax that.
Names an implementation of `org.hibernate.cache.spi.QueryCacheFactory`
`hibernate.cache.use_minimal_puts`:: Optimizes second-level cache operations to minimize writes, at the cost of more frequent reads. Providers typically set this appropriately.
`hibernate.cache.region_prefix`:: Defines a name to be used as a prefix to all second-level cache region names.
`hibernate.cache.default_cache_concurrency_strategy`:: In Hibernate second-level caching, all regions can be configured differently including the concurrency strategy to use when accessing the region.
This setting allows to define a default strategy to be used.
This setting is very rarely required as the pluggable providers do specify the default strategy to use.
Valid values include:
[[caching-config-provider-infinispan-jndi-region-factory]]
===== `JndiInfinispanRegionFactory`
If the Infinispan `CacheManager` is bound to JNDI, then the `JndiInfinispanRegionFactory` should be used as a region factory:
[[caching-config-provider-infinispan-jndi-region-factory-example]]
.`JndiInfinispanRegionFactory` configuration
====
[source, XML, indent=0]
----
<property
name="hibernate.cache.region.factory_class"
value="org.hibernate.cache.infinispan.JndiInfinispanRegionFactory" />
<property
name="hibernate.cache.infinispan.cachemanager"
value="java:CacheManager" />
----
====
For more information about Infinispan, see the http://infinispan.org/docs/8.0.x/user_guide/user_guide.html#_using_infinispan_as_jpa_hibernate_second_level_cache_provider[reference documentation].
[[caching-config-properties]]
==== Caching configuration properties
Besides specific provider configuration, there are a number of configurations options on the Hibernate side of the integration that control various caching behaviors:
`hibernate.cache.use_second_level_cache`::
Enable or disable second level caching overall. Default is true, although the default region factory is `NoCachingRegionFactory`.
`hibernate.cache.use_query_cache`::
Enable or disable second level caching of query results. Default is false.
`hibernate.cache.query_cache_factory`::
Query result caching is handled by a special contract that deals with staleness-based invalidation of the results.
The default implementation does not allow stale results at all. Use this for applications that would like to relax that.
Names an implementation of `org.hibernate.cache.spi.QueryCacheFactory`
`hibernate.cache.use_minimal_puts`::
Optimizes second-level cache operations to minimize writes, at the cost of more frequent reads. Providers typically set this appropriately.
`hibernate.cache.region_prefix`::
Defines a name to be used as a prefix to all second-level cache region names.
`hibernate.cache.default_cache_concurrency_strategy`::
In Hibernate second-level caching, all regions can be configured differently including the concurrency strategy to use when accessing that particular region.
This setting allows to define a default strategy to be used.
This setting is very rarely required as the pluggable providers do specify the default strategy to use.
Valid values include:
* read-only,
* read-write,
* nonstrict-read-write,
* transactional
hibernate.cache.use_structured_entries:: If `true`, forces Hibernate to store data in the second-level cache in a more human-friendly format.
Can be useful if you'd like to be able to "browse" the data directly in your cache, but does have a performance impact.
* hibernate.cache.auto_evict_collection_cache:: Enables or disables the automatic eviction of a bidirectional association's collection cache entry when the association is changed just from the owning side.
This is disabled by default, as it has a performance impact to track this state.
However if your application does not manage both sides of bidirectional association where the collection side is cached, the alternative is to have stale data in that collection cache.
`hibernate.cache.use_structured_entries`::
If `true`, forces Hibernate to store data in the second-level cache in a more human-friendly format.
Can be useful if you'd like to be able to "browse" the data directly in your cache, but does have a performance impact.
`hibernate.cache.auto_evict_collection_cache`::
Enables or disables the automatic eviction of a bidirectional association's collection cache entry when the association is changed just from the owning side.
This is disabled by default, as it has a performance impact to track this state.
However if your application does not manage both sides of bidirectional association where the collection side is cached,
the alternative is to have stale data in that collection cache.
`hibernate.cache.use_reference_entries`::
Enable direct storage of entity references into the second level cache for read-only or immutable entities.
[[caching-mappings]]
=== Configuring second-level cache mappings
The cache mappings can be configured via JPA annotations or XML descriptors or using the Hibernate-specific 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 file.
The following values are possible:
`ENABLE_SELECTIVE` (Default and recommended value)::
Entities are not cached unless explicitly marked as cacheable (with the https://docs.oracle.com/javaee/7/api/javax/persistence/Cacheable.html[`@Cacheable`] annotation).
`DISABLE_SELECTIVE`:
Entities are cached unless explicitly marked as not cacheable.
`ALL`:
Entities are always cached even if marked as non cacheable.
`NONE`:
No entity is 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 globally via the `hibernate.cache.default_cache_concurrency_strategy` configuration property.
The values for this property are:
read-only::
If your application needs to read, but not modify, instances of a persistent class, a read-only cache is the best choice.
This is the simplest and optimal performing strategy.
It is even safe for use in a cluster.
read-write::
If the application needs to update data, a read-write cache might be appropriate.
This cache strategy should never be used if serializable transaction isolation level is required.
If the cache is used in a JTA environment, you must specify the `hibernate.transaction.jta.platform` property.
In other environments, you should ensure that the transaction is completed when `Session.close()` or `Session.disconnect()` is called.
If you want to use this strategy in a cluster, you should ensure that the underlying cache implementation supports locking.
nonstrict-read-write::
If the application only occasionally needs to update data
(e.g. if it is extremely unlikely that two transactions would try to update the same item simultaneously)
and strict transaction isolation is not required, a nonstrict-read-write cache might be appropriate.
If the cache is used in a JTA environment, you must specify the `hibernate.transaction.jta.platform` property.
In other environments, you should ensure that the transaction is completed when `Session.close()` or `Session.disconnect()` is called.
transactional::
The transactional cache strategy provides support for fully transactional cache providers (e.g. Ehcache, Infinispan).
Such a cache can only be used in a JTA environment and you must specify `hibernate.transaction.jta.platform`.
[NOTE]
====
Rather than using a global cache concurrency strategy, it is recommended to define this setting on a per entity basis.
Use the https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/annotations/Cache.html[`@org.hibernate.annotations.Cache`] annotation for that.
====
The `@Cache` annotation define three attributes:
usage::
Defines the `CacheConcurrencyStrategy`
region::
Defines a cache region where entries will be stored
include::
If lazy properties should be included in the second level cache.
Default value is "all", so lazy properties are cacheable.
The other possible value is "non-lazy", so lazy properties are not cacheable.
[[caching-query]]
=== Entity cache
[[caching-entity-mapping-example]]
.Entity cache mapping
====
[source, JAVA, indent=0]
----
include::{sourcedir}/NonStrictReadWriteCacheTest.java[tags=caching-entity-mapping-example]
----
====
Hibernate stores cached entities in a dehydrated forms, which is similar to the database representation.
Aside from the foreign key column values of the `@ManyToOne` or `@OneToOne` child-side associations,
entity relationships are not stored in the cache,
Once an entity is stored in the second-level cache, you can avoid a database hit and load the entity from the cache alone:
[[caching-entity-jpa-example]]
.Loading entity using JPA
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-entity-jpa-example]
----
====
[[caching-entity-native-example]]
.Loading entity using Hibernate native API
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-entity-native-example]
----
====
The Hibernate second-level cache can also load entities by their <<chapters/domain/natural_id.adoc#naturalid,natural id>>:
[[caching-entity-natural-id-mapping-example]]
.Hibernate natural id entity mapping
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-entity-natural-id-mapping-example]
----
====
[[caching-entity-natural-id-example]]
.Loading entity using Hibernate native natural id API
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-entity-natural-id-example]
----
====
[[caching-collection]]
=== Collection cache
Hibernate can also cache collections, and the `@Cache` annotation must be on added to the collection property.
If the collection is made of value types (basic or embeddables mapped with `@ElementCollection`),
the collection is stored as such.
If the collection contains other entities (`@OneToMany` or `@ManyToMany`),
the collection cache entry will store the entity identifiers only.
[[caching-collection-mapping-example]]
.Collection cache mapping
====
[source, JAVA, indent=0]
----
include::{sourcedir}/NonStrictReadWriteCacheTest.java[tags=caching-collection-mapping-example]
----
====
Collections are read-through, meaning they are cached upon being accessed for the first time:
[[caching-collection-example]]
.Collection cache usage
====
[source, JAVA, indent=0]
----
include::{sourcedir}/NonStrictReadWriteCacheTest.java[tags=caching-collection-example]
----
====
Subsequent collection retrievals will use the cache instead of going to the database.
[NOTE]
====
The collection cache is not write-through, so any modification will trigger a collection cache entry invalidation.
On a subsequent access, the collection will be loaded from the database and re-cached.
====
[[caching-query]]
=== Query cache
Aside from caching entities and collections, Hibernate offers a query cache too.
This is useful for frequently executed queries with fixed parameter values.
[NOTE]
====
Caching of query results introduces some overhead in terms of your applications normal transactional processing.
For example, if you cache results of a query against `Person`,
Hibernate will need to keep track of when those results should be invalidated because changes have been committed against any `Person` entity.
That, coupled with the fact that most applications simply gain no benefit from caching query results,
leads Hibernate to disable caching of query results by default.
====
To use query caching, you will first need to enable it with the following configuration property:
[[caching-query-configuration]]
.Enabling query cache
====
[source, XML, indent=0]
----
<property
name="hibernate.cache.use_query_cache"
value="true" />
----
====
As mentioned above, most queries do not benefit from caching or their results.
So by default, individual queries are not cached even after enabling query caching.
Each particular query that needs to be cached must be manually set as cacheable.
This way, the query looks for existing cache results or adds the query results to the cache when being executed.
[[caching-query-jpa-example]]
.Caching query using JPA
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-query-jpa-example]
----
====
[[caching-query-native-example]]
.Caching query using Hibernate native API
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-query-native-example]
----
====
[NOTE]
====
The query cache does not cache the state of the actual entities in the cache;
it caches only identifier values and results of value type.
Just as with collection caching, the query cache should always be used in conjunction with the second-level cache for those entities expected to be cached as part of a query result cache.
====
[[caching-query-region]]
==== Query cache regions
This setting creates two new cache regions:
`org.hibernate.cache.internal.StandardQueryCache`::
Holding the cached query results
`org.hibernate.cache.spi.UpdateTimestampsCache`::
Holding timestamps of the most recent updates to queryable tables.
These are used to validate the results as they are served from the query cache.
[IMPORTANT]
====
If you configure your underlying cache implementation to use expiry or timeouts,
it's 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 `UpdateTimestampsCache` region not be configured for expiry at all.
Note that an LRU (Least Recently Used) cache expiry policy is never appropriate for this particular cache region.
====
If you require fine-grained control over query cache expiration policies,
you can specify a named cache region for a particular query.
[[caching-query-region-jpa-example]]
.Caching query in custom region using JPA
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-query-region-jpa-example]
----
====
[[caching-query-region-native-example]]
.Caching query in custom region using Hibernate native API
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-query-region-native-example]
----
====
If you want to force the query cache to refresh one of its regions (disregarding any cached results it finds there),
you can use custom cache modes.
[[caching-query-region-store-mode-jpa-example]]
.Using custom query cache mode with JPA
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-query-region-store-mode-jpa-example]
----
====
[[caching-query-region-native-example]]
.Using custom query cache mode with Hibernate native API
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-query-region-store-mode-native-example]
----
====
[NOTE]
====
When using http://docs.oracle.com/javaee/7/api/javax/persistence/CacheStoreMode.html#REFRESH[`CacheStoreMode.REFRESH`] or https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/CacheMode.html#REFRESH[`CacheMode.REFRESH`] in conjunction with the region you have defined for the given query,
Hibernate will selectively force the results cached in that particular region to be refreshed.
This is particularly useful in cases where underlying data may have been updated via a separate process
and is a far more efficient alternative to bulk eviction of the region via `SessionFactory` eviction which looks as follows:
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-query-region-native-evict-example]
----
====
[[caching-management]]
=== Managing the Cached Data
=== Managing the cached data
At runtime Hibernate handles moving data into and out of the second-level cache in response to the operations performed by the `Session`.
Traditionally, Hibernate defined the https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/CacheMode.html[`CacheMode`] enumeration to describe
the ways of interactions with the cached data.
JPA split cache modes by storage (http://docs.oracle.com/javaee/7/api/javax/persistence/CacheStoreMode.html[`CacheStoreMode`])
and retrieval (http://docs.oracle.com/javaee/7/api/javax/persistence/CacheRetrieveMode.html[`CacheRetrieveMode`]).
The `org.hibernate.Cache` interface (or the `javax.persistence.Cache` interface if using JPA) allow to clear data from the second-level cache.
The relationship between Hibernate and JPA cache modes can be seen in the following table:
TODO
.Cache modes relationships
[cols=",,,",options="header",]
|======================================
|Hibernate | JPA | Description
|`CacheMode.NORMAL` |`CacheStoreMode.USE` and `CacheRetrieveMode.USE` | Default. Reads/writes data from/into cache
|`CacheMode.REFRESH` |`CacheStoreMode.REFRESH` and `CacheRetrieveMode.BYPASS` | Doesn't read from cache, but writes to the cache upon loading from the database
|`CacheMode.PUT` |`CacheStoreMode.USE` and `CacheRetrieveMode.BYPASS` | Doesn't read from cache, but writes to the cache as it reads from the database
|`CacheMode.GET` |`CacheStoreMode.BYPASS` and `CacheRetrieveMode.USE` | Read from the cache, but doesn't write to cache
|`CacheMode.IGNORE` |`CacheStoreMode.BYPASS` and `CacheRetrieveMode.BYPASS` | Doesn't read/write data from/into cache
|======================================
Setting the cache mode can be done wither when loading entities directly or when executing a query.
[[caching-management-cache-mode-entity-jpa-example]]
.Using custom cache modes with JPA
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-management-cache-mode-entity-jpa-example]
----
====
[[caching-management-cache-mode-entity-native-example]]
.Using custom cache modes wit Hibernate native API
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-management-cache-mode-entity-native-example]
----
====
The custom cache modes can be set for queries as well:
[[caching-management-cache-mode-query-jpa-example]]
.Using custom cache modes for queries with JPA
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-management-cache-mode-query-jpa-example]
----
====
[[caching-management-cache-mode-query-native-example]]
.Using custom cache modes for queries with Hibernate native API
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-management-cache-mode-query-native-example]
----
====
[[caching-management-evict]]
==== Evicting cache entries
Because the second level cache is bound to the `EntityManagerFactory` or the `SessionFactory`,
cache eviction must be done through these two interfaces.
JPA only supports entity eviction through the https://docs.oracle.com/javaee/7/api/javax/persistence/Cache.html[`javax.persistence.Cache`] interface:
[[caching-management-evict-jpa-example]]
.Evicting entities with JPA
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-management-evict-jpa-example]
----
====
Hibernate is much more flexible in this regard as it offers a fine-grained control over what needs to be evicted.
The https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/Cache.html[`org.hibernate.Cache`] interface defines various evicting strategies:
- entities (by their class or region)
- entities stored using the natural-id (by their class or region)
- collections (by the region, and it might take the collection owner identifier as well)
- queries (by region)
[[caching-management-evict-native-example]]
.Evicting entities with Hibernate native API
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-management-evict-native-example]
----
====
[[caching-statistics]]
=== Caching statistics
If you enable the `hibernate.generate_statistics` configuration property,
Hibernate will expose a number of metrics via `SessionFactory.getStatistics()`.
Hibernate can even be configured to expose these statistics via JMX.
This way, you can get access to the https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/stat/Statistics.html[`Statistics`] class which comprises all sort of
second-level cache metrics.
[[caching-statistics-example]]
.Caching statistics
====
[source, JAVA, indent=0]
----
include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-statistics-example]
----
====

View File

@ -127,6 +127,7 @@ Others may also be unsafe for locking, because of lack of precision.
|generated |Whether the timestamp property value is generated by the database. Optional, defaults to `never`.
|=======================================================================
[[locking-pessimistic]]
=== Pessimistic
Typically, you only need to specify an isolation level for the JDBC connections and let the database handle locking issues.
@ -137,6 +138,7 @@ If you do need to obtain exclusive pessimistic locks or re-obtain locks at the s
Hibernate always uses the locking mechanism of the database, and never lock objects in memory.
====
[[locking-LockMode]]
=== The `LockMode` class
The `LockMode` class defines the different lock levels that Hibernate can acquire.
@ -168,4 +170,58 @@ In the case of `UPGRADE`, `UPGRADE_NOWAIT` or `UPGRADE_SKIPLOCKED`, the `SELECT
If the requested lock mode is not supported by the database, Hibernate uses an appropriate alternate mode instead of throwing an exception.
This ensures that applications are portable.
TODO: write about buildLockRequest and LockOptions and more about JPA LockType
[[locking-jpa-query-hints]]
=== JPA locking query hints
JPA 2.0 introduced two query hints:
javax.persistence.lock.timeout:: it gives the number of milliseconds a lock acquisition request will wait before throwing an exception
javax.persistence.lock.scope:: defines the http://docs.oracle.com/javaee/7/api/javax/persistence/PessimisticLockScope.html[_scope_] of the lock acquisition request.
The scope can either be `NORMAL` (default value) or `EXTENDED`. The `EXTENDED` scope will cause a lock acquisition request to be passed to other owned table structured (e.g. `@Inheritance(strategy=InheritanceType.JOINED)`, `@ElementCollection`)
[[locking-jpa-query-hints-timeout-example]]
.`javax.persistence.lock.timeout` example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/../../../../../../test/java/org/hibernate/jpa/test/userguide/locking/ExplicitLockingTest.java[tags=locking-jpa-query-hints-timeout-example]
----
[source, SQL, indent=0]
----
include::{sourcedir}/../../../../../../test/java/org/hibernate/jpa/test/userguide/locking/locking-jpa-query-hints-timeout-example.sql[]
----
====
[NOTE]
====
Not all JDBC database drivers support setting a timeout value for a locking request.
If not supported, the Hibernate dialect ignores this query hint.
====
[NOTE]
====
The `javax.persistence.lock.scope` is https://hibernate.atlassian.net/browse/HHH-9636[not yet supported] as specified by the JPA standard.
====
[[locking-buildLockRequest]]
=== The `buildLockRequest` API
Traditionally, Hibernate offered the `Session#lock()` method for acquiring an optimistic or a pessimistic lock on a given entity.
Because varying the locking options was difficult when using a single `LockMode` parameter, Hibernate has added the `Session#buildLockRequest()` method API.
The following example shows how to obtain shared database lock without waiting for the lock acquisition request.
[[locking-buildLockRequest-example]]
.`buildLockRequest` example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/../../../../../../test/java/org/hibernate/jpa/test/userguide/locking/ExplicitLockingTest.java[tags=locking-buildLockRequest-example]
----
[source, SQL, indent=0]
----
include::{sourcedir}/../../../../../../test/java/org/hibernate/jpa/test/userguide/locking/locking-buildLockRequest-example.sql[]
----
====

View File

@ -15,6 +15,20 @@ It may or may not physically exist in the database yet.
Much of the `org.hibernate.Session` and `javax.persistence.EntityManager` methods deal with moving entities between these states.
=== Accessing Hibernate APIs from JPA
JPA defines an incredibly useful method to allow applications access to the APIs of the underlying provider.
.Accessing Hibernate APIs from JPA
====
[source,java]
----
include::{sourcedir}/UnwrapWithEM.java[]
----
====
include::BytecodeEnhancement.adoc[]
=== Making entities persistent
Once you've created a new entity instance (using the standard `new` operator) it is in `new` state.
@ -291,16 +305,48 @@ include::{sourcedir}/CheckingLazinessWithJPA2.java[]
----
====
=== Accessing Hibernate APIs from JPA
=== Evicting entities
JPA defines an incredibly useful method to allow applications access to the APIs of the underlying provider.
When the `flush()` method is called, the state of the entity is synchronized with the database.
If you do not want this synchronization to occur, or if you are processing a huge number of objects and need to manage memory efficiently,
the `evict()` method can be used to remove the object and its collections from the first-level cache.
.Accessing Hibernate APIs from JPA
[[caching-management-jpa-detach-example]]
.Detaching an entity from the `EntityManager`
====
[source,java]
[source, JAVA, indent=0]
----
include::{sourcedir}/UnwrapWithEM.java[]
include::{sourcedir}/../../../../../../test/java/org/hibernate/jpa/test/userguide/caching/FirstLevelCacheTest.java[tags=caching-management-jpa-detach-example]
----
====
include::BytecodeEnhancement.adoc[]
[[caching-management-native-evict-example]]
.Evicting an entity from the Hibernate `Session`
====
[source, JAVA, indent=0]
----
include::{sourcedir}/../../../../../../test/java/org/hibernate/jpa/test/userguide/caching/FirstLevelCacheTest.java[tags=caching-management-native-evict-example]
----
====
To detach all entities from the current persistence context, both the `EntityManager` and the Hibernate `Session` define a `clear()` method.
[[caching-management-clear-example]]
.Clearing the persistence context
====
[source, JAVA, indent=0]
----
include::{sourcedir}/../../../../../../test/java/org/hibernate/jpa/test/userguide/caching/FirstLevelCacheTest.java[tags=caching-management-clear-example]
----
====
To verify if an entity instance is currently attached to the running persistence context, both the `EntityManager` and the Hibernate `Session` define a `contains(Object entity)` method.
[[caching-management-contains-example]]
.Verify if an entity is contained in a persistence context
====
[source, JAVA, indent=0]
----
include::{sourcedir}/../../../../../../test/java/org/hibernate/jpa/test/userguide/caching/FirstLevelCacheTest.java[tags=caching-management-contains-example]
----
====

View File

@ -0,0 +1,134 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.test.userguide.caching;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.Session;
import org.hibernate.cache.ehcache.EhCacheRegionFactory;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.jpa.test.util.TransactionUtil.doInJPA;
/**
* @author Vlad Mihalcea
*/
public class FirstLevelCacheTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( FirstLevelCacheTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Override
@SuppressWarnings( "unchecked" )
protected void addConfigOptions(Map options) {
options.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.TRUE.toString() );
options.put( AvailableSettings.CACHE_REGION_FACTORY, EhCacheRegionFactory.class.getName() );
}
@Test
public void testCache() {
Person aPerson = doInJPA( this::entityManagerFactory, entityManager -> {
entityManager.persist( new Person() );
entityManager.persist( new Person() );
Person person = new Person();
entityManager.persist( person );
return person;
});
doInJPA( this::entityManagerFactory, entityManager -> {
List<Object> dtos = new ArrayList<>( );
//tag::caching-management-jpa-detach-example[]
for(Person person : entityManager.createQuery("select p from Person p", Person.class)
.getResultList()) {
dtos.add(toDTO(person));
entityManager.detach( person );
}
//end::caching-management-jpa-detach-example[]
//tag::caching-management-clear-example[]
entityManager.clear();
//end::caching-management-clear-example[]
Person person = aPerson;
//tag::caching-management-contains-example[]
entityManager.contains( person );
//end::caching-management-contains-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
List<Object> dtos = new ArrayList<>( );
//tag::caching-management-native-evict-example[]
Session session = entityManager.unwrap( Session.class );
for(Person person : (List<Person>) session.createQuery("select p from Person p").list()) {
dtos.add(toDTO(person));
session.evict( person );
}
//end::caching-management-native-evict-example[]
//tag::caching-management-clear-example[]
session.clear();
//end::caching-management-clear-example[]
Person person = aPerson;
//tag::caching-management-contains-example[]
session.contains( person );
//end::caching-management-contains-example[]
});
}
private Object toDTO(Person person) {
return person;
}
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@ -0,0 +1,173 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.test.userguide.caching;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Version;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.cache.ehcache.EhCacheRegionFactory;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.jpa.test.util.TransactionUtil.doInJPA;
/**
* @author Vlad Mihalcea
*/
public class NonStrictReadWriteCacheTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( NonStrictReadWriteCacheTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Phone.class
};
}
@Override
@SuppressWarnings( "unchecked" )
protected void addConfigOptions(Map options) {
options.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.TRUE.toString() );
options.put( AvailableSettings.CACHE_REGION_FACTORY, EhCacheRegionFactory.class.getName() );
}
@Test
public void testCache() {
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person();
entityManager.persist( person );
Phone home = new Phone( "123-456-7890" );
Phone office = new Phone( "098-765-4321" );
person.addPhone( home );
person.addPhone( office );
});
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = entityManager.find( Person.class, 1L );
person.getPhones().size();
});
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Log collection from cache" );
//tag::caching-collection-example[]
Person person = entityManager.find( Person.class, 1L );
person.getPhones().size();
//end::caching-collection-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Load from cache" );
entityManager.find( Person.class, 1L ).getPhones().size();
});
}
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public static class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
//tag::caching-collection-mapping-example[]
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private List<Phone> phones = new ArrayList<>( );
//end::caching-collection-mapping-example[]
@Version
private int version;
public Person() {}
public Person(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Phone> getPhones() {
return phones;
}
public void addPhone(Phone phone) {
phones.add( phone );
phone.setPerson( this );
}
}
//tag::caching-entity-mapping-example[]
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public static class Phone {
@Id
@GeneratedValue
private Long id;
private String mobile;
@ManyToOne
private Person person;
@Version
private int version;
public Phone() {}
public Phone(String mobile) {
this.mobile = mobile;
}
public Long getId() {
return id;
}
public String getMobile() {
return mobile;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
//end::caching-entity-mapping-example[]
}

View File

@ -0,0 +1,299 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.test.userguide.caching;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.CacheRetrieveMode;
import javax.persistence.CacheStoreMode;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.CacheMode;
import org.hibernate.Session;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.NaturalId;
import org.hibernate.cache.ehcache.EhCacheRegionFactory;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.stat.SecondLevelCacheStatistics;
import org.hibernate.stat.Statistics;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.jpa.test.util.TransactionUtil.doInJPA;
import static org.junit.Assert.assertNotNull;
/**
* @author Vlad Mihalcea
*/
public class SecondLevelCacheTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( SecondLevelCacheTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class
};
}
@Override
@SuppressWarnings( "unchecked" )
protected void addConfigOptions(Map options) {
options.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.TRUE.toString() );
options.put( AvailableSettings.CACHE_REGION_FACTORY, EhCacheRegionFactory.class.getName() );
options.put( AvailableSettings.USE_QUERY_CACHE, Boolean.TRUE.toString() );
options.put( AvailableSettings.GENERATE_STATISTICS, Boolean.TRUE.toString() );
options.put( AvailableSettings.CACHE_REGION_PREFIX, "" );
}
@Test
public void testCache() {
doInJPA( this::entityManagerFactory, entityManager -> {
entityManager.persist( new Person() );
entityManager.persist( new Person() );
Person aPerson= new Person();
aPerson.setName( "John Doe" );
aPerson.setCode( "unique-code" );
entityManager.persist( aPerson );
return aPerson;
});
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Jpa load by id" );
//tag::caching-entity-jpa-example[]
Person person = entityManager.find( Person.class, 1L );
//end::caching-entity-jpa-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Native load by id" );
Session session = entityManager.unwrap( Session.class );
//tag::caching-entity-native-example[]
Person person = session.get( Person.class, 1L );
//end::caching-entity-native-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Native load by natural-id" );
Session session = entityManager.unwrap( Session.class );
//tag::caching-entity-natural-id-example[]
Person person = session
.byNaturalId( Person.class )
.using( "code", "unique-code")
.load();
//end::caching-entity-natural-id-example[]
assertNotNull(person);
});
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Jpa query cache" );
//tag::caching-query-jpa-example[]
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.name = :name", Person.class)
.setParameter( "name", "John Doe")
.setHint( "org.hibernate.cacheable", "true")
.getResultList();
//end::caching-query-jpa-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Native query cache" );
Session session = entityManager.unwrap( Session.class );
//tag::caching-query-native-example[]
List<Person> persons = session.createQuery(
"select p " +
"from Person p " +
"where p.name = :name")
.setParameter( "name", "John Doe")
.setCacheable(true)
.list();
//end::caching-query-native-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Jpa query cache region" );
//tag::caching-query-region-jpa-example[]
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.id > :id", Person.class)
.setParameter( "id", 0L)
.setHint( "org.hibernate.cacheable", "true")
.setHint( "org.hibernate.cacheRegion", "query.cache.person" )
.getResultList();
//end::caching-query-region-jpa-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Native query cache" );
Session session = entityManager.unwrap( Session.class );
//tag::caching-query-region-native-example[]
List<Person> persons = session.createQuery(
"select p " +
"from Person p " +
"where p.id > :id")
.setParameter( "id", 0L)
.setCacheable(true)
.setCacheRegion( "query.cache.person" )
.list();
//end::caching-query-region-native-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Jpa query cache store mode " );
//tag::caching-query-region-store-mode-jpa-example[]
List<Person> persons = entityManager.createQuery(
"select p " +
"from Person p " +
"where p.id > :id", Person.class)
.setParameter( "id", 0L)
.setHint( "org.hibernate.cacheable", "true")
.setHint( "org.hibernate.cacheRegion", "query.cache.person" )
.setHint( "javax.persistence.cache.storeMode", CacheStoreMode.REFRESH )
.getResultList();
//end::caching-query-region-store-mode-jpa-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Native query cache store mode" );
Session session = entityManager.unwrap( Session.class );
//tag::caching-query-region-store-mode-native-example[]
List<Person> persons = session.createQuery(
"select p " +
"from Person p " +
"where p.id > :id")
.setParameter( "id", 0L)
.setCacheable(true)
.setCacheRegion( "query.cache.person" )
.setCacheMode( CacheMode.REFRESH )
.list();
//end::caching-query-region-store-mode-native-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
//tag::caching-statistics-example[]
Statistics statistics = session.getSessionFactory().getStatistics();
SecondLevelCacheStatistics secondLevelCacheStatistics =
statistics.getSecondLevelCacheStatistics( "query.cache.person" );
long hitCount = secondLevelCacheStatistics.getHitCount();
long missCount = secondLevelCacheStatistics.getMissCount();
double hitRatio = (double) hitCount / ( hitCount + missCount );
//end::caching-statistics-example[]
return hitRatio;
} );
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "Native query cache store mode" );
Session session = entityManager.unwrap( Session.class );
//tag::caching-query-region-native-evict-example[]
session.getSessionFactory().getCache().evictQueryRegion( "query.cache.person" );
//end::caching-query-region-native-evict-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::caching-management-cache-mode-entity-jpa-example[]
Map<String, Object> hints = new HashMap<>( );
hints.put( "javax.persistence.cache.retrieveMode " , CacheRetrieveMode.USE );
hints.put( "javax.persistence.cache.storeMode" , CacheStoreMode.REFRESH );
Person person = entityManager.find( Person.class, 1L , hints);
//end::caching-management-cache-mode-entity-jpa-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
//tag::caching-management-cache-mode-entity-native-example[]
session.setCacheMode( CacheMode.REFRESH );
Person person = session.get( Person.class, 1L );
//end::caching-management-cache-mode-entity-native-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::caching-management-cache-mode-query-jpa-example[]
List<Person> persons = entityManager.createQuery(
"select p from Person p", Person.class)
.setHint( "org.hibernate.cacheable", "true")
.setHint( "javax.persistence.cache.retrieveMode " , CacheRetrieveMode.USE )
.setHint( "javax.persistence.cache.storeMode" , CacheStoreMode.REFRESH )
.getResultList();
//end::caching-management-cache-mode-query-jpa-example[]
//tag::caching-management-evict-jpa-example[]
entityManager.getEntityManagerFactory().getCache().evict( Person.class );
//end::caching-management-evict-jpa-example[]
});
doInJPA( this::entityManagerFactory, entityManager -> {
Session session = entityManager.unwrap( Session.class );
//tag::caching-management-cache-mode-query-native-example[]
List<Person> persons = session.createQuery(
"select p from Person p" )
.setCacheable( true )
.setCacheMode( CacheMode.REFRESH )
.list();
//end::caching-management-cache-mode-query-native-example[]
//tag::caching-management-evict-native-example[]
session.getSessionFactory().getCache().evictQueryRegion( "query.cache.person" );
//end::caching-management-evict-native-example[]
});
}
//tag::caching-entity-natural-id-mapping-example[]
@Entity(name = "Person")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public static class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@NaturalId
@Column(name = "code", unique = true)
private String code;
public Person() {}
public Person(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
//end::caching-entity-natural-id-mapping-example[]
}

View File

@ -0,0 +1,179 @@
package org.hibernate.jpa.test.userguide.locking;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.LockModeType;
import javax.persistence.PessimisticLockScope;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.jpa.test.util.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
/**
* <code>BuildLockRequestTest</code> - Build Lock Request Test
*
* @author Vlad Mihalcea
*/
public class ExplicitLockingTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( ExplicitLockingTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Phone.class,
};
}
@Test
public void testJPALockTimeout() {
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person( "John Doe" );
entityManager.persist( person );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "testJPALockTimeout" );
Long id = 1L;
//tag::locking-jpa-query-hints-timeout-example[]
entityManager.find(
Person.class, id, LockModeType.PESSIMISTIC_WRITE,
Collections.singletonMap( "javax.persistence.lock.timeout", 200 )
);
//end::locking-jpa-query-hints-timeout-example[]
} );
}
@Test
public void testJPALockScope() {
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person( "John Doe" );
entityManager.persist( person );
Phone home = new Phone( "123-456-7890" );
Phone office = new Phone( "098-765-4321" );
person.getPhones().add( home );
person.getPhones().add( office );
entityManager.persist( person );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "testJPALockScope" );
Long id = 1L;
//tag::locking-jpa-query-hints-scope-example[]
Person person = entityManager.find(
Person.class, id, LockModeType.PESSIMISTIC_WRITE,
Collections.singletonMap(
"javax.persistence.lock.scope",
PessimisticLockScope.EXTENDED )
);
//end::locking-jpa-query-hints-scope-example[]
assertEquals( 2, person.getPhones().size() );
} );
}
@Test
public void testBuildLockRequest() {
doInJPA( this::entityManagerFactory, entityManager -> {
log.info( "testBuildlLockRequest" );
Person person = new Person( "John Doe" );
Phone home = new Phone( "123-456-7890" );
Phone office = new Phone( "098-765-4321" );
person.getPhones().add( home );
person.getPhones().add( office );
entityManager.persist( person );
entityManager.flush();
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Long id = 1L;
//tag::locking-buildLockRequest-example[]
Person person = entityManager.find( Person.class, id );
Session session = entityManager.unwrap( Session.class );
session
.buildLockRequest( LockOptions.NONE )
.setLockMode( LockMode.PESSIMISTIC_READ )
.setTimeOut( LockOptions.NO_WAIT )
.lock( person );
//end::locking-buildLockRequest-example[]
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Long id = 1L;
//tag::locking-buildLockRequest-scope-example[]
Person person = entityManager.find( Person.class, id );
Session session = entityManager.unwrap( Session.class );
session
.buildLockRequest( LockOptions.NONE )
.setLockMode( LockMode.PESSIMISTIC_READ )
.setTimeOut( LockOptions.NO_WAIT )
.setScope( true )
.lock( person );
//end::locking-buildLockRequest-scope-example[]
} );
}
//tag::locking-jpa-query-hints-scope-entity-example[]
@Entity(name = "Person")
public static class Person {
@Id
@GeneratedValue
private Long id;
@Column(name = "`name`")
private String name;
@ElementCollection
@JoinTable(name = "person_phone", joinColumns = @JoinColumn(name = "person_id"))
private List<Phone> phones = new ArrayList<>();
public Person() {
}
public Person(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public List<Phone> getPhones() {
return phones;
}
}
@Embeddable
public static class Phone {
@Column
private String mobile;
public Phone() {}
public Phone(String mobile) {
this.mobile = mobile;
}
}
//end::locking-jpa-query-hints-scope-entity-example[]
}

View File

@ -0,0 +1,9 @@
SELECT p.id AS id1_0_0_ ,
p.name AS name2_0_0_
FROM Person p
WHERE p.id = 1
SELECT id
FROM Person
WHERE id = 1
FOR SHARE NOWAIT

View File

@ -0,0 +1,5 @@
SELECT explicitlo0_.id AS id1_0_0_,
explicitlo0_."name" AS name2_0_0_
FROM person explicitlo0_
WHERE explicitlo0_.id = 1
FOR UPDATE wait 2

View File

@ -0,0 +1,5 @@
SELECT explicitlo0_.id AS id1_0_0_,
explicitlo0_."name" AS name2_0_0_
FROM person explicitlo0_
WHERE explicitlo0_.id = 1
FOR UPDATE wait 2