With caching enabled:
Furthermore JBoss Cache supports clustering. If running within a cluster, and the cache is updated, changes to the entries in one node will be replicated to the corresponding entries in the other nodes in the cluster.
This defines that caching should use the JBoss Cache (which is distributed and transactional), instead of the default HashtableCacheProvider:
<property name="hibernate.cache.provider_class" value="org.jboss.ejb3.entity.TreeCacheProviderHook"/>
This defines the object name of the JBoss Cache to be used, and references jboss.cache:service=EJB3EntityTreeCache:
<property name="hibernate.treecache.mbean.object_name" value="jboss.cache:service=EJB3EntityTreeCache"/>
You define your entities (Customer.java and Contact.java the normal way.
The default behaviour is to not cache anything, even with the settings shown above. A very simplified rule of thumb is that you will typically want to do caching for objects that rarely change, and which are frequently read.
We also annotate the classes with the @Cache annotation to indicate that the values should be cached
@Entity @Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL) public class Customer implements java.io.Serializable { ... }
@Entity @Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL) public class Customer implements java.io.Serializable { ... }
This defines that Customer and Contact should be cached. Any attempt to look up Customer or Contact by their primary key, will first attempt to read the entry from the cache. If it cannot be found it will load it up from the database.
You can also cache relationship collections. Any attempt to access the contacts collection of Customer will attempt to load the data from the cache before hitting the database:
@Entity @Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL) public class Customer implements java.io.Serializable { ... @Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL) @OneToMany(mappedBy="customer", fetch=FetchType.EAGER, cascade=CascadeType.ALL) public Set<Contact> getContacts() { return contacts; } ... }
Please consult the Hibernate documentation for further information aboout settings for caching.
Unix: $ export JBOSS_HOME=<where your jboss 4.0 distribution is> Windows: $ set JBOSS_HOME=<where your jboss 4.0 distribution is> $ ant $ ant run ... run: [java] Saving customer to node 1 [java] Looking for customer in node 2's cache [java] Found customer 1 in node2 cache [java] Found contacts collection for 1 in node2 cache [java] Found contact 2 collection in node2 cache [java] Found contact 1 collection in node2 cache [java] Lookup of customer from node2 worked (via cache) [java] Customer: id=1; name=JBoss [java] Contact: id=2; name=Bill [java] Contact: id=1; name=Kabir
Now open up the JMX console and enter the jboss.cache:service=EJB3EntityTreeCache MBean. Scroll down the page untill you find the operation printDetails and press its Invoke button. This dumps out all the entries in the cache:
//org/jboss/ejb3/tutorial/clusteredentity/bean/Customer/contacts /org.jboss.ejb3.tutorial.clusteredentity.bean.Customer.contacts#1 item: CollectionCacheEntry[1,2]This represents the cache for the collection/relationship Customer.contacts. The collection cache only contains the ids of the entities represented. In this case it says that The Customer with id=1 points to the Contacts with id=1 and id=2.
//org/jboss/ejb3/tutorial/clusteredentity/bean/Customer /org.jboss.ejb3.tutorial.clusteredentity.bean.Customer#1 item: CacheEntry(org.jboss.ejb3.tutorial.clusteredentity.bean.Customer)[JBoss,1]This shows the entry for the Customer with id=1.
//org/jboss/ejb3/tutorial/clusteredentity/bean/Contact /org.jboss.ejb3.tutorial.clusteredentity.bean.Contact#1 item: CacheEntry(org.jboss.ejb3.tutorial.clusteredentity.bean.Contact)[Kabir,1111,1] /org.jboss.ejb3.tutorial.clusteredentity.bean.Contact#2 item: CacheEntry(org.jboss.ejb3.tutorial.clusteredentity.bean.Contact)[Bill,2222,1]And finally we have the entries for Contact with id=1 and id=2.
Since the cache is clustered any changes to the cache in one node will be replicated to the cache of the other nodes. Let's take a look at doing this in practice.
This example assumes that you have two NICs on your computer, with the IP addresses 192.168.1.1 and 192.168.1.2. If you have only one network card (sorry, I only know how to do this on windows!), you can fudge this up by entering 192.168.1.1 as the IP address in the General tab of 'Internet Protocols (TCP/IP) Properties' dialog for your network settings. Next you can click 'Advanced...' and add 192.168.1.2.
If JBoss is still running take it down. Create a new server configuration by copying $JBOSS_HOME/server/all to $JBOSS_HOME/server/test. Make sure that tutorial.jar exists under both $JBOSS_HOME/server/all/deploy to $JBOSS_HOME/server/test/deploy.
Now, open up two new console windows. Go to $JBOSS_HOME/bin in both. In the first console enter
$ run.sh -c all -b 192.168.1.1
In the first console enter
$ run.sh -c test -b 192.168.1.2
(If you are using Windows, and not using cygwin, you should substitute run.sh with run.bat)
Now you should have two jboss instances running. The first is using the 'all' configuration and is bound to the IP address 192.168.1.1, the other is running the 'test' configuration and is bound to the address 192.168.1.2. If it all worked for you, congratulations, you now have a small cluster running on your machine.
Now edit the run target of the build.xml to be as follows:
<target name="run" depends="ejbjar"> <java classname="org.jboss.ejb3.tutorial.clusteredentity.client.CachedEntityRun" fork="yes" dir="."> <!-- node 1 --> <arg value="192.168.1.1:1099"/> <!-- node 2 --> <arg value="192.168.1.2:1099"/> <classpath refid="classpath"/> </java> </target>
Now run the client again. It will save data to node1's database via node 1. This data does not go into node2's database. Since we are using the embedded hypersonic database here, the two instances of jboss are using separate databases. The last thing the client does is to load the data from node2; since the data exists in the cache it does not load from the database (which is empty for node2). NOTE: This is just for the purposes of demonstrating that the cache is being used. Normally, you would have the nodes in the cluster use the same database, but the purpose of this tutorial is to show the use of a cache in a cluster.
You can examine the date in the cache for node1 and node2. Scroll down to the 'printDetails' operation and press 'Invoke'. Note that the data is the same.
Also, you can look at the databases for node1 and node2. Scroll down to the 'startDatabaseManager' operation and press 'Invoke'. Note that the Customer and Contact tables for node 1 contains data while for node2 they are empty.