GWT Spring and Hibernate enter the world of Data Grids
Maximize Hibernate performance with the power of Infinispan Data Grid. A GWT, Spring, JPA, Hibernate, Infinispan integration tutorial.
In this tutorial we are going to discuss about how you can use Infinispan as a Hibernate second level cache provider. Infinispan is the successor of JBoss cache and the company’s flagship towards the open source data grids domain. To make things more interesting we are going to continue from where we left of at our previous article about Spring GWT Hibernate and JPA integration. We are going to use our GWTSpring project and empower it with data grid functionality!
Hiberante supports Infinispan as a second level cache provider from version 3.5 an on, we are going to use Hibernate version 3.5.2 and Infinispan version 4.0.0. You can download Infinispan binary distribution here
It is highly recommended that Hibernate is configured to use JTA transactions so that both Hibernate and Infinispan cooperate within the same transaction. Otherwise, operations on the database and the second level cache will not be treated as a single unit of work. Risks here include failures to update the second level cache leaving it with stale data while the database committed data correctly.
Due to the fact that we are deploying our Web application to a standalone environment (Apache – Tomcat) and not to a full blown, JTA enabled, application server, to comply with the aforementioned requirements, we are going to realize the JTA environment within Spring framework. To do so we need a JTA compliant transaction manager and our preference is Atomikos. Of course you can use any JTA compliant transaction manager you prefer. We will use Atomikos Transactions Essentials version 3.6.5 that you can download from here
Last but not least we will need MySQL Connector/J to connect to a MySQL database for testing. Version 3.6.5 of Atomikos Transactions Essentials is being tested and works well with MySQL Connector/J version 5.1.5 which you can download from here
In order to properly integrate Infinispan and Hibernate at runtime, we must provide all necessary libraries to the Web application. So copy the files listed below under /war/WEB-INF/lib (copy the relevant files if you are using different versions)
From Infinispan binary distribution
- infinispan-core.jar
- /lib/jboss-common-core-2.2.14.GA.jar
- /lib/jcip-annotations-1.0.jar
- /lib/jgroups-2.9.0.GA.jar
- /lib/marshalling-api-1.2.0.GA.jar
- /lib/rhq-pluginAnnotations-1.4.0.B01.jar
- /lib/river-1.2.0.GA.jar
From Atomikos Transactions Essentials distribution
- /dist/transactions-essentials-all.jar
- /lib/jca.jar
- /lib/jms.jar
- /lib/jmx.jar
The MySQL Connector/J
- mysql-connector-java-5.1.5-bin.jar
Finally, for Atomikos to work properly at runtime, a “jta.properties” file must be located on the classpath of our Web application. Create a text file named “jta.properties”, place it under /resources package of the Eclipse project and populate it with the properties shown below :
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory com.atomikos.icatch.force_shutdown_on_vm_exit = true com.atomikos.icatch.automatic_resource_registration = false com.atomikos.icatch.console_log_level = INFO
We now have to take care of dependences for our Eclipse project. The following jars should be included in the Java build path of the project :
- hibernate3.jar
Next step is to configure Hibernate to use MySQL dialect and second level cache. Locate the persistence.xml file under /resources/META-INF folder, and perform the changes described below :
To use MySQL dialect add the following property :
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
To configure the transaction manager to use add the following properties :
<property name="hibernate.transaction.manager_lookup_class" value="com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup" />
To enable Hibernate second level cache with query result caching enabled you should add the following properties :
<property name="hibernate.cache.use_second_level_cache" value="true"/> <property name="hibernate.cache.use_query_cache" value="true"/>
At this point we have to pinpoint that enabling query result caching may not improve performance especially if your application performs queries that mainly return unique results.
To configure the Infinispan cache region factory add the following property :
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.infinispan.InfinispanRegionFactory"/>
We can also configure an eviction policy (here Least Recently Used – LRU) by adding the following properties :
<property name="hibernate.cache.infinispan.entity.eviction.strategy" value= "LRU"/> <property name="hibernate.cache.infinispan.entity.eviction.wake_up_interval" value= "2000"/> <property name="hibernate.cache.infinispan.entity.eviction.max_entries" value= "5000"/> <property name="hibernate.cache.infinispan.entity.expiration.lifespan" value= "60000"/> <property name="hibernate.cache.infinispan.entity.expiration.max_idle" value= "30000"/>
By using an eviction policy we prevent the second level cache from consuming all available memory heap by unconditionally caching objects. The Least Recently Used eviction policy evicts entries based on how often they are used. This in our case the following rules will be applied :
- The “wake_up_interval” property defines how often a controller process will scan second level cache for candidate eviction entries (here every 2 seconds)
- The “max_entries” property defines the maximum number of cache entries
- The “lifespan” property defines the maximum amount of time an object can be kept in cache. If the lifespan of an object is reached then the object is evicted regardless of how often is accessed (here 1 minute)
- The “max_idle” property defines the maximum amount of time an object can be idle (not accessed) before evicted (here 30 seconds)
Finally we must change the “persistence-unit” “transaction-type” attribute to JTA
The complete persistence.xml file should look like the one provided below :
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0"> <persistence-unit name="MyPersistenceUnit" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <properties> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="false" /> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" /> <property name="hibernate.transaction.manager_lookup_class" value="com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup" /> <property name="hibernate.cache.use_second_level_cache" value="true"/> <property name="hibernate.cache.use_query_cache" value="true"/> <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.infinispan.InfinispanRegionFactory"/> <!-- <property name="hibernate.cache.infinispan.entity.eviction.strategy" value= "LRU"/> <property name="hibernate.cache.infinispan.entity.eviction.wake_up_interval" value= "2000"/> <property name="hibernate.cache.infinispan.entity.eviction.max_entries" value= "5000"/> <property name="hibernate.cache.infinispan.entity.expiration.lifespan" value= "60000"/> <property name="hibernate.cache.infinispan.entity.expiration.max_idle" value= "30000"/> --> </properties> </persistence-unit> </persistence>
The next step is to configure Spring regarding the JTA datasource, Atomikos transaction manager and JPA/Hibernate. Locate your applicationContext.xml file under /war/WEB-INF/ and alter it as follows :
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <context:component-scan base-package="com.javacodegeeks.gwtspring" /> <task:annotation-driven executor="myExecutor" scheduler="myScheduler" /> <task:executor id="myExecutor" pool-size="5" /> <task:scheduler id="myScheduler" pool-size="10" /> <tx:annotation-driven /> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </property> </bean> <bean id="dataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="javacodegeeks" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">jdbc:mysql://localhost:3306/javacodegeeks</prop> <prop key="user">***</prop> <prop key="password">***</prop> </props> </property> <property name="maxPoolSize" value="50" /> <property name="minPoolSize" value="20" /> </bean> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <property name="forceShutdown" value="false" /> </bean> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.J2eeUserTransaction"> <property name="transactionTimeout" value="300" /> </bean> <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" depends-on="atomikosTransactionManager,atomikosUserTransaction"> <property name="transactionManager" ref="atomikosTransactionManager" /> <property name="userTransaction" ref="atomikosUserTransaction" /> <property name="allowCustomIsolationLevels" value="true" /> </bean> </beans>
Things to notice here :
- Spring Entity Manager Factory holds references to the datasource and the JPA provider in order to properly provide ORM functionality to our DAOs
- The datasource is configured as an XA resource. This is mandatory in order for Infinispan and the database to participate in the same transactions, as described above. You must change “xaProperties” URL, user and password property values according to your database configuration.
- We configure Spring to use the JTA compliant Atomikos transaction manager.
We are almost done!
To make an entity cachable, we only have to annotate it as such. Locate the EmployeeDTO object under /shared/dto package and add the @Cache annotation as shown below :
… import statements here … @Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL) @Entity @Table(name = "EMPLOYEE") public class EmployeeDTO implements java.io.Serializable { private static final long serialVersionUID = 7440297955003302414L; … }
Things to notice here :
- We dictate the Cache Concurrency Strategy as “TRANSACTIONAL” because we want to perform not only retrieve but also create/update/delete operations on the cached object.
Thats it! To deploy the web application just copy the /war folder in Apache – Tomact “webapps” folder. You can change the name of the war folder to whatever you like, preferably rename it after the project name e.g. GWTSpringInfinispan
Prior lunching the application do not forget to create the database schema, here “javacodegeeks”.
To lunch the application point your browser to the following address
http://localhost:8080/GWTSpringInfinispan/
If all went well you should see your main web page. Two text boxes should be displayed followed by a button each. In the first text box you can save or update an employee to the database. Provide as input the id, the name, the surname, and a job description separated by a space character. Clicking on the “SaveOrUpdate” button the provided information will be stored to the database. For existing employee entries (same id) an update will be performed. The second text box is used to retrieve existing employee entries. Provide an employee id and click on the “Retrieve” button. If the employee exists you should see the employee id, name, surname and job description.
Atomikos transaction manager is configured to produce log records at INFO level (see “jta.properties” file above). The log file is located inside the bin directory of your Apache – Tomcat installation. Open the file and observe the queries performed to the database. What you should expect is the following :
- Perform a retrieve operation on an employee for the first time. The employee object is not cached and a query is performed against the database. The employee object should now be cached
- Perform a second retrieve operation for the same employee. No query should be performed now. Data is retrieved from Infinispan
- Perform an update operation on the same employee. Update operations are performed to both Infinispan and the database in a single unit of work
- Perform a third query for the same employee. No query should be performed against the database and you should see the updated data from the previous step!
You can download the project from here (required 3rd party libraries as described at the beginning and previous articles are not included)
Have Fun!
Justin
- GWT 2 Spring 3 JPA 2 Hibernate 3.5 Tutorial
- Spring 3 HornetQ 2.1 Integration Tutorial
- Spring 3 RESTful Web Services
- GWT 2 Spring 3 JPA 2 Hibernate 3.5 Tutorial – Eclipse and Maven 2 showcase
- JAX–WS with Spring and Maven Tutorial
Prior lunching the application do not forget to create the database schema, here “javacodegeeks”.
Change to Prior launching the application…
To lunch the application point your browser to the following address
Change to
To launch
(lunch is mid day meal – launch is like a rocket say). Nice tutorial