blob: 96674467b580d7c1862b50940fd4b13b7bcb7afa [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.examples.datagrid.hibernate;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteException;
import org.apache.ignite.Ignition;
import org.apache.ignite.cache.CacheAtomicityMode;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.examples.ExampleNodeStartup;
import org.apache.ignite.examples.ExamplesUtils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.stat.SecondLevelCacheStatistics;
import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC;
import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
/**
* This example demonstrates the use of Ignite In-Memory Data Ignite cluster as a Hibernate
* Second-Level cache provider.
* <p>
* The Hibernate Second-Level cache (or "L2 cache" shortly) lets you significantly
* reduce the number of requests to the underlying SQL database. Because database
* access is known to be an expansive operation, using L2 cache may improve
* performance dramatically.
* <p>
* This example defines 2 entity classes: {@link User} and {@link Post}, with
* 1 <-> N relation, and marks them with appropriate annotations for Hibernate
* object-relational mapping to SQL tables of an underlying H2 in-memory database.
* The example launches node in the same JVM and registers it in
* Hibernate configuration as an L2 cache implementation. It then stores and
* queries instances of the entity classes to and from the database, having
* Hibernate SQL output, L2 cache statistics output, and Ignite cache metrics
* output enabled.
* <p>
* When running example, it's easy to notice that when an object is first
* put into a database, the L2 cache is not used and it's contents is empty.
* However, when an object is first read from the database, it is immediately
* stored in L2 cache (which is Ignite In-Memory Data Ignite cluster in fact), which can
* be seen in stats output. Further requests of the same object only read the data
* from L2 cache and do not hit the database.
* <p>
* In this example, the Hibernate query cache is also enabled. Query cache lets you
* avoid hitting the database in case of repetitive queries with the same parameter
* values. You may notice that when the example runs the same query repeatedly in
* loop, only the first query hits the database and the successive requests take the
* data from L2 cache.
* <p>
* Note: this example uses {@link AccessType#READ_ONLY} L2 cache access type, but you
* can experiment with other access types by modifying the Hibernate configuration file
* {@code IGNITE_HOME/examples/config/hibernate/example-hibernate-L2-cache.xml}, used by the example.
* <p>
* Remote nodes should always be started with special configuration file which
* enables P2P class loading: {@code 'ignite.{sh|bat} examples/config/example-ignite.xml'}.
* <p>
* Alternatively you can run {@link ExampleNodeStartup} in another JVM which will
* start node with {@code examples/config/example-ignite.xml} configuration.
*/
public class HibernateL2CacheExample {
/** JDBC URL for backing database (an H2 in-memory database is used). */
private static final String JDBC_URL = "jdbc:h2:mem:example;DB_CLOSE_DELAY=-1";
/** Path to hibernate configuration file (will be resolved from application {@code CLASSPATH}). */
private static final String HIBERNATE_CFG = "hibernate/example-hibernate-L2-cache.xml";
/** Entity names for stats output. */
private static final List<String> ENTITY_NAMES =
Arrays.asList(User.class.getName(), Post.class.getName(), User.class.getName() + ".posts");
/** Caches' names. */
private static final String USER_CACHE_NAME = "org.apache.ignite.examples.datagrid.hibernate.User";
/** */
private static final String USER_POSTS_CACHE_NAME = "org.apache.ignite.examples.datagrid.hibernate.User.posts";
/** */
private static final String POST_CACHE_NAME = "org.apache.ignite.examples.datagrid.hibernate.Post";
/** */
static final String MODULE_PATH = System.getProperty("user.dir");
/**
* Executes example.
*
* @param args Command line arguments, none required.
* @throws IgniteException If example execution failed.
*/
public static void main(String[] args) throws IgniteException {
// Start the node, run the example, and stop the node when finished.
try (Ignite ignite = Ignition.start(MODULE_PATH + "/config/example-ignite.xml")) {
// We use a single session factory, but create a dedicated session
// for each transaction or query. This way we ensure that L1 cache
// is not used (L1 cache has per-session scope only).
System.out.println();
System.out.println(">>> Hibernate L2 cache example started.");
// Auto-close cache at the end of the example.
try (
// Create all required caches.
IgniteCache c1 = createCache(timestampsCacheName(), ATOMIC);
IgniteCache c2 = createCache(queryResultsCacheName(), ATOMIC);
IgniteCache c3 = createCache(USER_CACHE_NAME, TRANSACTIONAL);
IgniteCache c4 = createCache(USER_POSTS_CACHE_NAME, TRANSACTIONAL);
IgniteCache c5 = createCache(POST_CACHE_NAME, TRANSACTIONAL)
) {
URL hibernateCfg = ExamplesUtils.url(HIBERNATE_CFG);
try (SessionFactory sesFactory = createHibernateSessionFactory(hibernateCfg)) {
System.out.println();
System.out.println(">>> Creating objects.");
final long userId;
Session ses = sesFactory.openSession();
try {
Transaction tx = ses.beginTransaction();
User user = new User("jedi", "Luke", "Skywalker");
user.getPosts().add(new Post(user, "Let the Force be with you."));
ses.save(user);
tx.commit();
// Create a user object, store it in DB, and save the database-generated
// object ID. You may try adding more objects in a similar way.
userId = user.getId();
}
finally {
ses.close();
}
// Output L2 cache and Ignite cache stats. You may notice that
// at this point the object is not yet stored in L2 cache, because
// the read was not yet performed.
printStats(sesFactory);
System.out.println();
System.out.println(">>> Querying object by ID.");
// Query user by ID several times. First time we get an L2 cache
// miss, and the data is queried from DB, but it is then stored
// in cache and successive queries hit the cache and return
// immediately, no SQL query is made.
for (int i = 0; i < 3; i++) {
ses = sesFactory.openSession();
try {
Transaction tx = ses.beginTransaction();
User user = (User)ses.get(User.class, userId);
System.out.println("User: " + user);
for (Post post : user.getPosts())
System.out.println("\tPost: " + post);
tx.commit();
}
finally {
ses.close();
}
}
// Output the stats. We should see 1 miss and 2 hits for
// User and Collection object (stored separately in L2 cache).
// The Post is loaded with the collection, so it won't imply
// a miss.
printStats(sesFactory);
}
}
finally {
// Distributed cache could be removed from cluster only by #destroyCache() call.
ignite.destroyCache(timestampsCacheName());
ignite.destroyCache(queryResultsCacheName());
ignite.destroyCache(USER_CACHE_NAME);
ignite.destroyCache(USER_POSTS_CACHE_NAME);
ignite.destroyCache(POST_CACHE_NAME);
}
}
}
/**
* Creates cache.
*
* @param name Cache name.
* @param atomicityMode Atomicity mode.
* @return Cache configuration.
*/
private static IgniteCache createCache(String name, CacheAtomicityMode atomicityMode) {
CacheConfiguration ccfg = new CacheConfiguration(name);
ccfg.setAtomicityMode(atomicityMode);
ccfg.setWriteSynchronizationMode(FULL_SYNC);
return Ignition.ignite().getOrCreateCache(ccfg);
}
/**
* Creates a new Hibernate {@link SessionFactory} using a programmatic
* configuration.
*
* @param hibernateCfg Hibernate configuration file.
* @return New Hibernate {@link SessionFactory}.
*/
private static SessionFactory createHibernateSessionFactory(URL hibernateCfg) {
StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder();
builder.applySetting("hibernate.connection.url", JDBC_URL);
builder.applySetting("hibernate.show_sql", true);
builder.configure(hibernateCfg);
return new MetadataSources(builder.build()).buildMetadata().buildSessionFactory();
}
/**
* Prints Hibernate L2 cache statistics to standard output.
*
* @param sesFactory Hibernate {@link SessionFactory}, for which to print
* statistics.
*/
private static void printStats(SessionFactory sesFactory) {
System.out.println("=== Hibernate L2 cache statistics ===");
for (String entityName : ENTITY_NAMES) {
System.out.println("\tEntity: " + entityName);
SecondLevelCacheStatistics stats =
sesFactory.getStatistics().getSecondLevelCacheStatistics(entityName);
System.out.println("\t\tPuts: " + stats.getPutCount());
System.out.println("\t\tHits: " + stats.getHitCount());
System.out.println("\t\tMisses: " + stats.getMissCount());
}
System.out.println("=====================================");
}
/**
* Returns the name of the timestamps cache to a specific version of apache-hibernate.
*
* @return Name of the update timestamps cache.
*/
private static String timestampsCacheName() {
return isIgniteHibernate51orBelowEnabled() ?
// Represents the name of timestamps region specific to hibernate 5.1 {@see HibernateTimestampsRegion}.
"org.hibernate.cache.spi.UpdateTimestampsCache" :
// Represents the name of timestamps region specific to hibernate 5.3 {@see IgniteTimestampsRegion}.
"default-update-timestamps-region";
}
/**
* Returns the name of the query results cache to a specific version of apache-hibernate.
*
* @return Name of the update timestamps cache.
*/
private static String queryResultsCacheName() {
return isIgniteHibernate51orBelowEnabled() ?
// Represents the name of query results region specific to hibernate 5.1 {@see HibernateQueryResultsRegion}.
"org.hibernate.cache.internal.StandardQueryCache" :
// Represents the name of query results region specific to hibernate 5.3 {@see IgniteQueryResultsRegion}.
"default-query-results-region";
}
/**
* Returns {@code true} if ignite-hibernate 5.1 is enabled.
*
* @return {@code true} if ignite-hibernate 5.1 is enabled.
*/
private static boolean isIgniteHibernate51orBelowEnabled() {
try {
Class.forName("org.apache.ignite.cache.hibernate.HibernateTimestampsRegion");
return true;
}
catch (ClassNotFoundException ignore) {
return false;
}
}
}