Chapter 3. JBoss Cache - Pojo Cache

3.1.

What is PojoCache?

PojoCache (currently implemented PojoCache as a sub-class of TreeCache) is a fine-grained field-level replicated and transactional POJO (plain old Java object) cache. By POJO, we mean that the cache: 1) automatically manages object mapping and relationship for a client under both local and replicated cache mode, 2) provides support for inheritance relationship between "aspectized" POJOs. By leveraging the dynamic AOP in JBossAop, it is able to map a complex object into the cache store, preserve and manage the object relationship behind the scene. During replication mode, it performs fine-granularity (i.e., on a per-field basis) update, and thus has the potential to boost cache performance and minimize network traffic.

From a user perspective, once your POJO is managed by the cache, all cache operations are transparent. Therefore, all the usual in-VM POJO method semantics are still preserved, providing ease of use. For example, if a POJO has been put in PojoCache (by calling putObject, for example), then any get/set method will be intercepted by PojoCache to provide the data from the cache.

3.2.

What's the relationship between PojoCache and TreeCacheAop classes?

Since release 1.4, we have created a new class called PojoCache (to better reflect the cache nature). The old implementation TreeCacheAop has been deprecated.

3.3.

Does PojoCache have all the functional capabilities of TreeCache?

Yes. PojoCache extends TreeCache so it has all the same features TreeCache such as cache mode, transaction isolation level, and eviction policy.

3.4.

What is the difference between TreeCache and PojoCache?

Think of PojoCache as a TreeCache on steroids. :-) Seriously, both are cache stores-- one is a generic cache and the other other one POJO Cache. However, while TreeCache only provides pure object reference storage (e.g., put(FQN fqn, Object key, Object value) ), PojoCache goes beyond that and performs fine-grained field level replication object mapping and relationship management for a user behind the scenes. As a result, if you have complex object systems that you would like to cache, you can have PojoCache manage it for you. You simply treat your object systems as they are residing in-memory, e.g., use your regular POJO methods without worrying about cache management. Furthermore, this is true in replication mode as well.

3.5.

What are the steps to use the PojoCache feature?

Starting from release 1.3, depends on the JDK you use, it has slightly different steps. But in general, in order to use PojoCache, you will need to:

  • prepare POJO. You can do either via xml declaration or annotation. For annotation, you can use either the JDK1.4 style or JDK50 one (of which is part of JVM spec). If you use JDK14, you will also need a annotation pre-compiler (annoc) to pre-process it.

  • instrumentation. You will need to instrument your POJO either at compile-time or load-time. If you do it during compile-time, you use so-called aop pre-compiler (aopc) to do bytecode manipulation. If you do it via load-time, however, you need either a special system class loader or, in JDK50, you can use the javaagent option.

So if you use JDK50, for example, with annotation and load-time instrumentation, then you won't need any pre-processing step to use PojoCache. For a full example, please refer to the distro examples directory. There are numerous PojoCache examples that uses different options.

3.6.

Can I run PojoCache in JBoss AS 3.2.x application server?

Yes and no. Yes, since JBossAop can also be back-ported to 3.2.x (see JBossAop wiki for details). However, it will take some effort. Therefore, the recommended JBoss version is 4.x to run PojoCache.

3.7.

Can PojoCache run as a MBean as well?

Yes. It is almost the same as TreeCache MBean. The only difference is the object name and the class name. E.g., instead of

<mbean code="org.jboss.cache.TreeCache"
                     name="jboss.cache:service=TreeCache">

you will have:

<mbean code="org.jboss.cache.aop.PojoCache"
                     name="jboss.cache:service=PojoCache">

in the xml configuration file.

3.8.

Can I pre-compile the aop classes such that I don't need to use the system classloader and jboss-aop configuration xml?

Yes. The latest versions of JBossCache have a pre-compiler option called aopc . You can use this option to pre-compile your "aspectized" POJO. Once the classes have been byte code generated, they can be treated as regular class files, i.e., you will not need to include any jboss-aop.xml that specifies the advisable POJO and to specify the JBossAop system class loader.

For an example of how to use aopc , please see 1) tools directory for PojoCacheTasks14.xml and PojoCacheTasks50.xml. Both contain Ant tasks that you can import to your regular project for annoc and aopc . In addition, please also check out the examples directory for concrete examples.

3.9.

How do I use aopc on multiple module directories?

In aopc, you specify the src path for a specific directory. To pre-compile multiple ones, you will need to invoke aopc multiple times.

3.10.

What's in the jboss-aop.xml configuration?

jboss-aop.xml is needed for POJO instrumentation. In jboss-aop.xml , you can declare your POJO (e.g., Person ) to be "prepared", a JBossAop term to denote that the object will be "aspectized" by the system. After this declaration, JBossAop will invoke any interceptor that associates with this POJO. PojoCache will dynamically add an org.jboss.cache.aop.CacheInterceptor to this POJO to perform object mapping and relationship management.

Note that to add your POJO, you should declare all the fields to be "prepared" as in the example.

3.11.

Can I use annotation instead of the xml declaration?

Yes, starting with JBossCache 1.3, you can use annotation to instrument your POJO for both JDK1.4 and 1.5. Check the documentation for details.

3.12.

What are the pro and con of xml vs. annotation?

It really depends on your organization environment, I'd say, since this can be turned into a hot debate. Having said that, I feel strongly that POJO annotation is well suited for PojoCache. This is because once you specify the annotation, you'd probably change it rarely since there is no parameters to tune, for example.

3.13.

What are the @org.jboss.cache.aop.annotation.Transient and @org.jboss.cache.aop.annotation.Serializable field level annotations?

Starting in 1.4, we also offer two additional field-level annotations. The first one, @Transient , when applied has the same effect as declaring a field transient . PojoCache won't put this field under management.

The second one, @Serializable when applied, will cause PojoCache to treat the field as a Serializable object even when it is @PojoCacheable .

3.14.

What about compile-time vs. load-time instrumentation then?

Again it depends. But my preference is to do compile-time instrumentation via aopc. I prefer this approach because it is easier to debug (at least at the development stage). In addition, once I generate the new class, there is no more steps needed.

3.15.

Is it possible to store the same object multiple times but with different Fqn paths? Like /foo/byName and /foo/byId ?

Yes, you can use PojoCache to do that. It supports the notion of object reference. PojoCache manages the unique object through association of the dynamic cache interceptor.

3.16.

Do I need to declare all my objects "prepared" in jboss-aop.xml ?

Not necessarily. If there is an object that you don't need the cache to manage for you, you can leave it out of the declaration. The cache will treat this object as a "primitive" type. However, the object will need to implement Serializable interface for replication.

3.17.

Can the cache aop intercept update via reflection?

No. The update via reflection will not be intercepted in JBossAop and therefore PojoCache will not be able to perform the necessary synchronization.

3.18.

When I declare my POJO to be "aspectized", what happens to the fields with transient, static, and final modifiers?

PojoCache currently will ignore the fields with these modifiers. That is, it won't put these fields into the cache (and thus no replication either).

3.19.

What are those keys such as JBoss:internal:class and AOPInstance ?

They are for internal use only. Users should ignore these keys and values in the node hashmap.

3.20.

What about Collection classes? Do I need to declare them "prepared"?

No. Since the Collection classes such as ArrayList are java util classes, aop by default won't instrument these classes. Instead, PojoCache will generate a dynamic class proxy for the Collection classes (upon the putObject call is invoked). The proxy will delegate the operations to a cache interceptor that implements the actual Collection classes APIs. That is, the system classes won't be invoked when used in PojoCache.

Internally, the cache interceptor implements the APIs by direct interaction with respect to the underlying cache store. Note that this can have implications in performance for certain APIs. For example, both ArrayList and LinkedList will have the same implementation. Plan is currently underway to optimize these APIs.

3.21.

How do I use List , Set , and Map dynamic proxy?

PojoCache supports classes extending from List , Set , and Map without users to declare them "aspectized". It is done via a dynamic proxy. Here is a code snippet to use an ArrayList proxy class.

ArrayList list = new ArrayList();
                  list.add("first");

                  cache.putObject("/list/test", list); // Put the list under the aop cache
                  list.add("second"); // Won't work since AOP intercepts the dynamic proxy not the original POJO.

                  ArrayList myList = (List)cache.getObject("/list/test"); // we are getting a dynamic proxy instead
                  myList.add("second"); // it works now
                  myList.add("third");
                  myList.remove("third");
               
3.22.

What is the proper way of assigning two different keys with Collection class object?

Let's say you want to assign a List object under two different names, you will need to use the class proxy to insert the second time to ensure both are managed by the cache. Here is the code snippet.

ArrayList list = new ArrayList();
                  list.add("first");

                  cache.putObject("/list", list); // Put the list under the aop cache

                  ArrayList myList = (List)cache.getObject("/list"); // we are getting a dynamic proxy instead
                  myList.add("second"); // it works now

                  cache.putObject("/list_alias", myList); // Note you will need to use the proxy here!!
                  myList.remove("second");
               
3.23.

OK, so I know I am supposed to use proxy when manipulating the Collection classes once they are managed by the cache. But what happens to Pojos that share the Collection objects, e.g., a List instance that is shared by 2 Pojos?

Pojos that share Collection instance references will be handled by the cache automatically. That is, when you ask the Cache to manage it, the Cache will dynamically swap out the regular Collection references with the dynamic proxy ones. As a result, it is transparent to the users.

3.24.

What happens when my "aspectized" POJO has field members that are of Collection class ?

When a user puts a POJO into the cache through the call putObject , it will recursively map the field members into the cache store as well. When the field member is of a Collection class (e.g., List, Set, or Map), PojoCache will first map the collection into cache. Then, it will swap out dynamically the field reference with an corresponding proxy reference.

This is necessary so that an internal update on the field member will be intercepted by the cache.

3.25.

What are the limitation of Collection classes in PojoCache?

Use of Collection class in PojoCache helps you to track fine-grained changes in your collection fields automatically. However, current implementation has the follow limitation that we plan to address soon.

Currently, we only support a limited implementation of Collection classes. That is, we support APIs in List, Set, and Map. However, since the APIs do not stipulate of constraints like NULL key or value, it makes mapping of user instance to our proxy tricky. For example, ArrayList would allow NULL value and some other implementation would not. The Set interface maps to java.util.HashSet implementation. The List interface maps to java.util.ArrayList implementation. The Map interface maps to java.util.HashMap implementation.

Another related issue is the expected performance. For example, the current implementation is ordered, so that makes insert/delete from the Collection slow. Performance between Set, Map and List collections also vary. Adding items to a Set is slower than a List or Map, since Set does not allow duplicate entries.

3.26.

What are the pros and cons of PojoCache?

As mentioned in the reference doc, PojoCache has the following advantages:

  • Fine-grained replication and/or persistency. If you use a distributed PojoCache and once your POJO is put in the cache store, there is no need to use another API to trigger your changes. Furthermore, the replication are fine-grained field level. Note this also applies to persistency.

  • Fine-grained replication can have potential performance gain if your POJO is big and the changes are fine-grained, e.g., only to some selected fields.

  • POJO can posses object relationship, e.g., multiple referenced. Distributed PojoCache will handle this transparently for you.

And here are some cases that you may not want to use PojoCache:

  • You use only cache. That is you don't need replication or persistency. Then since everything is operated on the in-memory POJO reference, there is no need for PojoCache.

  • You have simple and small POJOs. Your POJO is small in size and also there is no object relationship, then PojoCache possess not clear advantage to plain cache.

  • Your application is bounded by memory usage. Because PojoCache need almost twice as much of memory (the original POJO in-memory space and also the additional cache store for the primitive fields), you may not want to use PojoCache.

  • Your POJO lifetime is short. That is, you need to create and destroy your POJO often. Then you need to do "pubObject" and "removeObject" often, it will be slow in performance.