1

I faced an issue earlier with JPA. I have two apps : the main one, using Java/JPA (EclipseLink), and a second one, using PHP. The two apps have access to the same database. Now, I'm accessing an "Expedition" object through Java, then calling the PHP app through a web-service (which is supposed to modify an attribute of this object in the shared database table "Expedition"), then accessing this attribute through the Java app.

Problem is, the object seems not to be modified in the Java app, even if it is modified in the database. I'm thinking about a cache problem.

The original code (simplified) :

System.out.println(expedition.getInfosexpedition()); // null

// Calling the web-service (modification of the "expedition" object in the database)
this.ec.eXtractor(expedition);

System.out.println(expedition.getInfosexpedition()); // Still null, should not be

Definitions of the "Expedition" and "Infosexpedition" classes :

Expedition :

@Entity
@Table(name = "expedition")
@XmlRootElement
public class Expedition implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "idExpedition")
    private Integer idExpedition;
    @OneToOne(cascade = CascadeType.ALL, mappedBy = "idExpedition")
    @XmlTransient
    private Infosexpedition infosexpedition;

Infosexpedition :

@Entity
@Table(name = "infosexpedition")
@XmlRootElement
public class Infosexpedition implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "idInfoExpedition")
    private Integer idInfoExpedition;
    @JoinColumn(name = "idExpedition", referencedColumnName = "idExpedition")
    @OneToOne(optional = false)
    @XmlTransient
    private Expedition idExpedition;

I've been able to make the original code work by doing this :

System.out.println(expedition.getInfosexpedition()); // null

// Calling the web-service (modification of the "expedition" object in the database)
this.ec.eXtractor(expedition);

try
{
    // Getting explicitly the "infosExpedition" item through a simple named request
    Infosexpedition infos = this.ec.getFacade().getEm().createNamedQuery("Infosexpedition.findByIdExpedition", Infosexpedition.class)
           .setParameter("idExpedition", expedition)
           .setHint("eclipselink.refresh", "true")
           .setHint("eclipselink.cache-usage", "DoNotCheckCache")
           .setHint("eclipselink.read-only", "true") // This line did the trick
           .getSingleResult();
     expedition.setInfosexpedition(infos);
}
catch (NoResultException nre) {}

System.out.println(expedition.getInfosexpedition()); // Not null anymore, OK

I'm trying to understand what happens here, and why did I had to specify a "read-only" hint to make this work... Before that, I tried almost everything, from evictAll() calls to detach()/merge() calls, and nothing worked.

Can someone help me to understand how the different levels of cache worked here ? And why is my newly created line "read-only" ?

Thanks a lot.

Adrien Dos Reis
  • 472
  • 2
  • 14
  • You haven't shown your code for how you are managing your EntityManagers, but there are 2 levels of caches in JPA. The second level is at the EMF level, while the first is in the EntityManager itself, keeping a reference to every managed object so it can track changes and keep identity. You are probably keeping a single EM around instead of releasing it when done - they are meant to be obtained as needed much like you would a transaction. – Chris Oct 08 '14 at 12:00
  • I'm using JTA for transaction handling, and every creation/edit/deletion is made by calling `MyClass.getEntityManager().persist()/merge()/remove()` If I understand this right, given the fact that "expedition" is a parameter of my method (and I don't know how this parameter is obtained), I can't write something like `this.getEntityManager().refresh(expedition)` if `this.getEntityManager()` isn't the manager that allowed me to retrieve my "expedition" object ? – Adrien Dos Reis Oct 08 '14 at 13:05
  • 1
    My point was not around transaction handling, but around the EntityManager lifecycle. But I assume if you are using JTA that you are likely using container managed EMs as well. When you persist/merge/find an entity, it loads it into the current EntityManager cache. That reference is not released until it is closed or cleared as described in an answer below. It can also be refreshed. em.refresh(expedition) only works if the expedition is a managed entity within that EntityManager. If it is not, then a expedition=em.find(expedition.getId());em.refresh(expedition) might work – Chris Oct 08 '14 at 13:58
  • But you need to look at where your expedition is coming from. How did you read it in in the first place? – Chris Oct 08 '14 at 13:59
  • JTA : UserManaged or ContainerManaged ? How do you manage your transactions ? – Gab Oct 08 '14 at 14:02
  • @Gab JTA ContainerManaged. I have some big complicated SQL request, which gives me about hundreds (or thousands) of "Expedition" objects, then, for each one, I have to call the PHP app to do something, then handle the result from my Java code. – Adrien Dos Reis Oct 08 '14 at 14:56

2 Answers2

2

The settings you are using are attempting to bypass the cache. ("eclipselink.read-only", "true") causes it to bypass the first level cache, while the ("eclipselink.cache-usage", "DoNotCheckCache") makes the query go to the database instead of pulling data from the second level cache. Finally ("eclipselink.refresh", "true") refreshes the data in the shared cache rather then return the prebuilt object. Your facade must be using the same EntityManager for both requests even though you have made changes to the objects between the requests. As mentioned in the comments, an EntityManager is meant to be used as a transaction would, so that you are isolated from changes made during your transactions. If this doesn't work for you, you should clear or release the entityManager after the first call, so that the calls after the web-service modifications can be picked up.

If applications outside this one are going to be making data changes frequently, you might want to look at disabling the shared cache as described here: https://wiki.eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F

And also implement optimistic locking to prevent either application from overwriting the other with stale data as described here: https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Mapping/Locking/Optimistic_Locking

Chris
  • 20,138
  • 2
  • 29
  • 43
  • +1 for l2 cache disabling, didn't know it was enabled by default on eclipse link – Gab Oct 09 '14 at 11:31
  • Thanks for the explanation of these settings. I understand better why my EntityManager couldn't see any changes made to the database. – Adrien Dos Reis Oct 10 '14 at 12:37
1

What you call cache is the 1st level cache, id est the in memory projection of the database state at a time t.

This "cache" has the same lifecycle that the entity manager itself and generally won't be refreshed until you explicitely clear it (using myEntityManager.clear()) (you shouldn't) or force it to refreh a specific entity instance (using myEntityManager.refresh(myEntityInstance), this is the way you should go)

See Struggling to understand EntityManager proper use and Jpa entity lifecycle for a more detailed explanation

Community
  • 1
  • 1
Gab
  • 7,869
  • 4
  • 37
  • 68
  • I tried using `myEntityManager.refresh(expedition)` and `myEntityManager.detach(expedition)...myEntityManager.merge(expedition)`, both didn't work :( – Adrien Dos Reis Oct 08 '14 at 13:39
  • refresh does exactly what you want, if the update is visible from the underlying database connection then it will work. Maybe a transaction isolation issue. How do you get the entityManager instance ? – Gab Oct 08 '14 at 14:00
  • The process is a Cron task which is executed every hour. This Cron task is defined in a class, annotated with `@Singleton`, in which I inject an `ExpeditionController` which has an attribute `entityManager` – Adrien Dos Reis Oct 08 '14 at 14:59