From d769308785951e2d1b2bb1360c8a834ac3c8a640 Mon Sep 17 00:00:00 2001 From: vladmihalcea Date: Thu, 21 Jan 2016 16:50:53 +0200 Subject: [PATCH] Fill in the TODO sections for the Locking and Caching chapters --- documentation/documentation.gradle | 1 + .../userguide/chapters/caching/Caching.adoc | 570 ++++++++++++++++-- .../userguide/chapters/locking/Locking.adoc | 58 +- .../chapters/pc/PersistenceContext.adoc | 58 +- .../caching/FirstLevelCacheTest.java | 134 ++++ .../caching/NonStrictReadWriteCacheTest.java | 173 ++++++ .../caching/SecondLevelCacheTest.java | 299 +++++++++ .../locking/ExplicitLockingTest.java | 179 ++++++ .../locking-buildLockRequest-example.sql | 9 + .../locking-jpa-query-hints-scope-example.sql | 5 + ...ocking-jpa-query-hints-timeout-example.sql | 5 + 11 files changed, 1444 insertions(+), 47 deletions(-) create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/FirstLevelCacheTest.java create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/NonStrictReadWriteCacheTest.java create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/SecondLevelCacheTest.java create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/ExplicitLockingTest.java create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-buildLockRequest-example.sql create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-jpa-query-hints-scope-example.sql create mode 100644 documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-jpa-query-hints-timeout-example.sql diff --git a/documentation/documentation.gradle b/documentation/documentation.gradle index ba9a4cce0fd..118ffe047e9 100644 --- a/documentation/documentation.gradle +++ b/documentation/documentation.gradle @@ -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') ) diff --git a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc index b0fa45444bb..ada512d2ec4 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc @@ -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` + +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] +---- + +---- +==== -TODO +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] +---- + +---- +==== + +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 two specific providers: `infinispan` and `infinispan-jndi`. + +[[caching-config-provider-infinispan-region-factory]] +===== `InfinispanRegionFactory` + +If Hibernate and Infinispan are running in a standalone environment, the `InfinispanRegionFactory` should be configured as follows: + +[[caching-config-provider-infinispan-region-factory-example]] +.`InfinispanRegionFactory` configuration +==== +[source, XML, indent=0] +---- + +---- ==== -The hibernate-infinispan module defines 2 specific providers: `infinispan` and `infinispan-jndi`. +[[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] +---- + + + +---- +==== -TODO +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-behavior]] -=== Caching behavior +[[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 behavior: +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 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: +`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 <>: + +[[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] +---- + +---- +==== + +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 + +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 relationship between Hibernate and JPA cache modes can be seen in the following table: + +.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: -At runtime Hibernate handles moving data into and out of the second-level cache in response to the operations performed by the `Session`. +[[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] +---- +==== -The `org.hibernate.Cache` interface (or the `javax.persistence.Cache` interface if using JPA) allow to clear data from the second-level cache. +[[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] +---- +==== -TODO \ No newline at end of file +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] +---- +==== \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc b/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc index 719175f46b9..36bb0e3fe01 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/locking/Locking.adoc @@ -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 \ No newline at end of file +[[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[] +---- +==== \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc index 028995f3944..83556ef0a0b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc @@ -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] +---- +==== \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/FirstLevelCacheTest.java b/documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/FirstLevelCacheTest.java new file mode 100644 index 00000000000..272e3c9c547 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/FirstLevelCacheTest.java @@ -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 . + */ +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 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 dtos = new ArrayList<>( ); + //tag::caching-management-native-evict-example[] + Session session = entityManager.unwrap( Session.class ); + for(Person person : (List) 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; + } + } +} diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/NonStrictReadWriteCacheTest.java b/documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/NonStrictReadWriteCacheTest.java new file mode 100644 index 00000000000..098e6eeff19 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/NonStrictReadWriteCacheTest.java @@ -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 . + */ +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 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 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[] +} diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/SecondLevelCacheTest.java b/documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/SecondLevelCacheTest.java new file mode 100644 index 00000000000..4660589392a --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/caching/SecondLevelCacheTest.java @@ -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 . + */ +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 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 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 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 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 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 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 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 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 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[] +} diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/ExplicitLockingTest.java b/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/ExplicitLockingTest.java new file mode 100644 index 00000000000..170877e32b2 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/ExplicitLockingTest.java @@ -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; + +/** + * BuildLockRequestTest - 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 phones = new ArrayList<>(); + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public List 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[] +} diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-buildLockRequest-example.sql b/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-buildLockRequest-example.sql new file mode 100644 index 00000000000..0901e873fb2 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-buildLockRequest-example.sql @@ -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 \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-jpa-query-hints-scope-example.sql b/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-jpa-query-hints-scope-example.sql new file mode 100644 index 00000000000..ce04bf48c8f --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-jpa-query-hints-scope-example.sql @@ -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 \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-jpa-query-hints-timeout-example.sql b/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-jpa-query-hints-timeout-example.sql new file mode 100644 index 00000000000..ce04bf48c8f --- /dev/null +++ b/documentation/src/test/java/org/hibernate/jpa/test/userguide/locking/locking-jpa-query-hints-timeout-example.sql @@ -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 \ No newline at end of file