简体   繁体   中英

How does JPA cache refreshes entities externally modified?

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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM