2

I have a small web application; a reduced runnable version of it is below.

On every http request, I read an object, every time the same, from the database and use it to show the page's content. On the first request after publishing to Glassfish, everything is fine. However, if I change the value in the database, this change is not reflected in the next request.

Code relevant for getting the entity:

// @WebServlet MyServlet
@Inject
private MyEJB ejb;

// MyServlet.doGet()
MyEntity e = ejb.getResource(1);
System.out.printf("%s: %s%n", e, e.getValue());

// @Stateless MyEJB
@PersistenceContext
private EntityManager em;

// MyEJB.getResource()
TypedQuery<MyEntity> q = em.createNamedQuery("MyEntity.selectById", MyEntity.class);
q.setParameter("id", id);
MyEntity e = q.getSingleResult();
return e;

Changing the value to asdf! between requests, the logging in doGet() outputs:

Information: pkg.model.MyEntity@7bc0b80e: asdf
Information: pkg.model.MyEntity@5543a940: asdf

Apparently, the entity is cached between requests, although a different object is returned. flushing the entity manager in getResource before or after the query did not make a difference, which I don't understand. refreshing the entity does work, but is not recursive and does not cover the other needs that surely arise in the original application. Exceptions I got while trying other things confirmed that the EntityManager is JTA-managed, as I expected.

I'm using GlassFish Server Open Source Edition 4.0 with the default EclipseLink ORM.

I think there are three options that would satisfy my needs:

  • a configuration, in the Glassfish console or some WEB-INF/META-INF file or similar
  • some code that I can execute before actually handling each request, making sure that objects already cached are up-to-date with the database
  • a different way of injection that does this, or that limits caching to a per-request basis

How can I achieve one of these, or is it something different I actually need?


Full code below

package pkg.model;

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(schema = "MySchema")
@NamedQueries({
    @NamedQuery(name = "MyEntity.selectById",
            query = "SELECT e FROM MyEntity e WHERE e.id = :id")
})
public class MyEntity {
    private Long   id;
    private String value;

    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }

    @SuppressWarnings("unused")
    private void setId(Long id) {
        this.id = id;
    }

    @Basic
    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

package pkg;

import java.io.IOException;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.servlet.ServletException;
import pkg.model.MyEntity;

@Stateless
public class MyEJB {
    @PersistenceContext
    private EntityManager em;

    public MyEntity getResource(long id) throws ServletException, IOException {
        TypedQuery<MyEntity> q = em.createNamedQuery("MyEntity.selectById", MyEntity.class);
        q.setParameter("id", id);
        MyEntity e = q.getSingleResult();
        return e;
    }
}

package pkg;

import java.io.IOException;
import java.io.PrintWriter;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import pkg.model.MyEntity;

@WebServlet(name = "MyServlet", urlPatterns = "/")
public class MyServlet extends HttpServlet {
    @Inject
    private MyEJB ejb;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        MyEntity e = ejb.getResource(1);
        System.out.printf("%s: %s%n", e, e.getValue());
        resp.setContentType("text/html;charset=UTF-8");
        try (PrintWriter out = resp.getWriter();) {
            out.print(e.getValue());
        }
    }
}

additionally, an empty beans.xml and a vanilla web.xml (just a welcome-file-list)

Silly Freak
  • 3,637
  • 1
  • 27
  • 50

1 Answers1

4

By default EclipseLink enables a shared object cache to cache objects read from the database to avoid repeated database access. If the database is changed directly through JDBC, or by another application or server, the objects in the shared cache will be stale.

There are different options to disable the cache or to force a refresh.

The normal shared cache can be disabled by adding the following to your persistence.xml:

<property name="eclipselink.cache.shared.default" value="false"/>

When using NamedQueries, like you do, you may also have to disable the query cache by adding the following:

<property name="eclipselink.query-results-cache" value="false"/>
<property name="eclipselink.refresh" value="true"/>

An alternative, more detailed way to disable the caching is via a query hint. This can be done in the following way:

TypedQuery<MyEntity> q = em.createNamedQuery("MyEntity.selectById", MyEntity.class);
q.setHint(QueryHints.CACHE_USAGE, CacheUsage.DoNotCheckCache);
// or 
q.setHint("javax.persistence.cache.storeMode", "REFRESH");

There is a lot of additional information about caching in the EclipseLink wiki:

See also:

Community
  • 1
  • 1
unwichtich
  • 13,143
  • 2
  • 46
  • 60
  • I ended up using the first solution for now. As most of the database will most likely only be modified by my web app, I will revisit this later. So far, I did not experience the need to add `query-results-cache` and `refresh` properties. The links you provided are great resources! Thank you! – Silly Freak Jul 14 '14 at 21:43