Skip to main content

Don't Lie to the Entity Manager

Posted by cayhorstmann on July 2, 2006 at 8:10 PM PDT

JPA is the new object-relational mapping standard that you can use in
EJB3 or in standalone applications. For the most part, it is phenomenally
easy to use. But ever so often, you get a query from a developer such as
href="http://forums.java.net/jive/thread.jspa?forumID=56&threadID=16541">this
one. The programmer set up a bidirectional relationship and didn't
populate both sides.

            acc.setAddress(addr);
            // addr.setAccount(acc) missing

I tried to set up a simple example that shows very clearly what goes
wrong and why. Here goes:

Every Student has a href="http://weblogs.java.net/blog/cayhorstmann/archive/2006-07-03/Diploma.java">Diploma (may be null), and
every Diploma has a Student.

student-diploma.png

Now we set up the relationship, "forgetting" to set up the inverse:

            EntityManager em1 = emf.createEntityManager();
            int id;
            try {
                em1.getTransaction().begin();
               
                Student s = new Student("John Q. Public");
                Diploma d1 = new Diploma();
                d1.setDegree("BS");
                s.setDiploma(d1);
                // d1.setStudent(s); // "forgot" to do this
               
                em1.persist(d1);
                em1.persist(s);              
                em1.getTransaction().commit();
                id = d1.getId();
            } finally {
                em1.close();
            }

Of course, the student reference in the d1 object is
null. But so what...in the database, this is not a problem
because the inverse side doesn't actually have a foreign key.

???

When one retrieves the diploma from the database, the Student
entity is obtained by a query SELECT Student x WHERE x.diploma =
diplomaId
.

Let's try it out by making a new entity manager and querying for the
diploma.

            EntityManager em2 = emf.createEntityManager();
            try {
                Diploma d2 = em2.find(Diploma.class, id);               
                System.out.println("d2=" + d2);               
            } finally {
                em2.close();
            }      

Unfortunately, the result is a diploma object whose student
reference is null. Why?

In the spirit of the “ href="http://www.oreilly.com/catalog/hfhtmlcss/">Head First”
books, let me BE the entity manager.


??? style="float:left; padding-right: 1em; width: 15%" />I am an entity
manager. I am asked to find a Diploma entity. I am a brand new entity
manager. I don't know any entities. I construct a new Diploma object. I
make a query to the database and retrieve the name. The DIPLOMA table has
no STUDENT column, so I make another query to set the student ID.

??? style="float: left; margin-right: 1em; width: 15%; margin-bottom: 1em;" />But
wait, I don't do that. I get out my ouija board and communicate with the
spirits of the past. I find that the client has been bad to the deceased
em1 and lied about the bidirectional relationship. Therefore, I
set the student property to null . . .


Clearly, that can't be right. Reading the JPA spec
does not help. It talks about persistence contexts, but em1 and
em2 have their own independent persistence contexts. Instead, the
culprit is the object cache (which is given short shrift in the spec). I
did my experiment with GlassFish, which uses Toplink as its JPA provider.
Apparently, the TopLink cache has located a Diploma instance (namely the
one that I persisted in em1), and it now returns it, without ever
accessing the database. That's what a cache is for. My lie about the
null student came right back to me.

Here is another example. I tried out the @OrderBy
construct:

            @Entity
            public class Course {
               . . .
               @ManyToMany(mappedBy="courses")
               @OrderBy("name ASC")
               private List<Student> students = new ArrayList<Student>();
               . . .
            }

I added a bunch of students, but I didn't bother to sort them by name.
I figured that the @OrderBy annotation will produce an ORDER
BY
in the query, so they'll come out in order anyway.

That's true, except in the program run in which I persisted the
Course object. That time, the cache found the original object in
which the students had the wrong order, and it threw it right back into my
face.

Stephen Connolly pointed me to this footnote in section 2.1.1 of the
spec. (The empasis is his.)

[4]
Portable applications should not expect the order of lists to be
maintained across persistence contexts unless the OrderBy construct is
used and the modifications to the list observe the specified
ordering
. The order is not otherwise persistent.

In other words, don't lie to the entity manager.

Related Topics >>