Skip to main content

The innermost secrets of -javaagent:toplink-essentials-agent.jar revealed

Posted by cayhorstmann on June 9, 2006 at 6:40 PM PDT

???When you
start a href="http://java.sun.com/products/persistence/docs.html">JPA client
application, tutorials such as href="https://glassfish.dev.java.net/javaee5/persistence/persistence-example.html">this
one tell you to use this mysterious incantation on the command
line:

java -javaagent:toplink-essentials-agent.jar client.Client

What is the reason behind that? Go to the href="http://forums.java.net/jive/forum.jspa?forumID=56&start=0">GlassFish
forum and search for javaagent. You'll get lots of confused queries.
(Ok, most of them are mine.) The gist of the friendly advice that I got
seemed to be that the toplink-essentials-agent.jar file was required for
lazy loading.

Lazy loading means that you can fetch an object from a database without
also fetching all of its dependent objects. Here is the canonical
example:

In a multiple-choice testing application, a Question has a
number of choices:

@Entity
public class Question implements Serializable {
    private int id;
    private Collection<Choice> choices;
    . . .
    @OneToMany
    public Collection<Choice> getChoices() {
        return choices;
    }
}

Now let's say we load a Question object from the database.
Should all choices be loaded as well? And what about the objects that are
related to Choice objects? That can be dangerous--suppose a
Choice remembers all the questions that contain it? Clearly,
something has to give, or we pull out a potentially huge transitive
closure with every query.

That's where lazy loading comes in. You can mark relationships so that
they are fetched lazily. By default, collections are always fetched
lazily. For example, consider this code:

Question q = em.find(Question.class, id);
System.out.println(q.getChoices());
System.out.println(q.getChoices().iterator().next());       

It prints

{IndirectList: not instantiated}
elvis.entity.Choice[id=1,text=run anywhere]

Clearly, our collection isn't a humble array list. Indeed, calling
q.getChoices().getClass() reveals that it is a
oracle.toplink.essentials.indirection.IndirectList.

Ok, so I tried omitting -javaagent. What would happen to my lazy
collection? Would it be eagerly fetched? Would my keyboard melt into a
puddle of plastic if I accessed it? To my surprise, nothing special
happened. The output was exactly the same. What, if anything, did the
agent do?

???I
finally figured it out, thanks to reading href="http://www.oracle.com/technology/products/ias/toplink/jpa/howto/java-se-usage.html">this
article very carefully. You need -javaagent for one specific
purpose, to lazily load one-to-one and many-to-one relationships.
(If you knew this and you read this far, I am sorry to have wasted your
time. This blog entry is for href="http://www.nikhilk.net/Personas.aspx">Elvis, not Einstein.)

We had a one-to-many relationship, so the agent didn't care. Let's add
a lazily fetched one-to-one relationship:

@Entity
public class Question implements Serializable {
    . . .
    private Choice answer;

    @OneToOne(fetch=FetchType.LAZY)
    public Choice getAnswer() {
        return answer;
    }
    . . .
    public String toString() {
        return getClass().getName() + "[id=" + id
                + ",answer=" + answer"]";
    }
}

Here we need to specify the fetch type since one-to-one relationship
are eagerly fetched by default.

Now we'll be able to tell the difference between running with
-javaagent and without.

Question q = em.find(Question.class, id);
System.out.println(q.toString());
System.out.println(q.getAnswer());       
With -javaagent Without -javaagent
elvis.entity.Question[id=3,answer=null]
elvis.entity.Choice[id=1,text=run anywhere]
elvis.entity.Question[id=3,answer=elvis.entity.Choice[id=1,text=run anywhere]
elvis.entity.Choice[id=1,text=run anywhere]

Both outputs are interesting.

???With
-javaagent, the answer instance field is null, but the
getAnswer method doesn't simply return the instance field (even
though our code says it does). The agent intercepted the loading of the
Question class and edited the bytecodes of the getAnswer
method so that it fetches the result. (This process of on-the-fly bytecode
modification is called "weaving" by the cognoscenti.)

Without -javaagent, lazy fetching has been disabled.

Why was the agent necessary? With the Collection
field, the persistence provider can simply set an object of a class that
implements the Coll ection interface and that does the lazy
loading. There is no need to modify the bytecodes of the class. But one
can't do that with a single object. The Choice field must be set
to a Choice object (or to null), and the getter must be
modified. Mystery solved.

Unfortunately, I haven't yet found a way to actually see the modified
bytecodes, but if you set a breakpoint in the debugger and inspect the
Question object in the debugger, you can see a field that was
added to the class:

???

???Here
is the executive summary in terms that href="http://www.horstmann.com/elvis/elvis-meets-glassfish.html">Elvis
can understand:

  • -javaagent:toplink-essentials-agent.jar is only useful when
    you have one-to-one or many-to-one lazily fetched relationships.
  • If you omit it, your program will still work, but one-to-one and
    many-to-one relationships will be fetched eagerly.