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 there is a trap that has bitten more than one developer. If you ever lie because your fibbing won't affect the database, your lies can still come back to haunt you. This blog gives two examples.

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 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 Diploma (may be null), and every Diploma has a Student.

???

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 “Head First” books, let me BE the entity manager.


???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.

???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.