JPA In Datenbank gelöschter Datensatz wird weiterhin vom EntityManger gefunden

Fant

Bekanntes Mitglied
Hallo zusammen.

Folgendes funktioniert nicht, wie von mir erwartet:


Die Methode, um einen Datensatz "Scope" zu editieren, habe ich mir von NetBeans automatisch generieren lassen. Im laufenden Betrieb meiner WebApp klappt auch alles einwandfrei. Wenn ich jedoch manuell in der Datenbank einen Datensatz lösche, dann funktioniert das Speichern innerhalb der WebApp jedoch komischerweise trotzdem noch! Folgender Code wirft keine Exception, obwohl es den Datensatz "scope" in der Datenbank definitiv nicht gibt.

Java:
public void edit(Scope scope) throws IllegalOrphanException, NonexistentEntityException, RollbackFailureException, Exception {
        EntityManager em = null;
        try {
            utx.begin();
            em = getEntityManager();
            Scope persistentScope = em.find(Scope.class, scope.getRefRmRequestId());
            RmRequest rmRequestOld = persistentScope.getRmRequest();
            RmRequest rmRequestNew = scope.getRmRequest();
            List<String> illegalOrphanMessages = null;
            if (rmRequestNew != null && !rmRequestNew.equals(rmRequestOld)) {
                Scope oldScopeOfRmRequest = rmRequestNew.getScope();
                if (oldScopeOfRmRequest != null) {
                    if (illegalOrphanMessages == null) {
                        illegalOrphanMessages = new ArrayList<String>();
                    }
                    illegalOrphanMessages.add("The RmRequest " + rmRequestNew + " already has an item of type Scope whose rmRequest column cannot be null. Please make another selection for the rmRequest field.");
                }
            }
            if (illegalOrphanMessages != null) {
                throw new IllegalOrphanException(illegalOrphanMessages);
            }
            if (rmRequestNew != null) {
                rmRequestNew = em.getReference(rmRequestNew.getClass(), rmRequestNew.getRmRequestId());
                scope.setRmRequest(rmRequestNew);
            }
            scope = em.merge(scope);
            if (rmRequestOld != null && !rmRequestOld.equals(rmRequestNew)) {
                rmRequestOld.setScope(null);
                rmRequestOld = em.merge(rmRequestOld);
            }
            if (rmRequestNew != null && !rmRequestNew.equals(rmRequestOld)) {
                rmRequestNew.setScope(scope);
                rmRequestNew = em.merge(rmRequestNew);
            }
            utx.commit();
        } catch (Exception ex) {
            try {
                utx.rollback();
            } catch (Exception re) {
                throw new RollbackFailureException("An error occurred attempting to roll back the transaction.", re);
            }
            String msg = ex.getLocalizedMessage();
            if (msg == null || msg.length() == 0) {
                Long id = scope.getRefRmRequestId();
                if (findScope(id) == null) {
                    throw new NonexistentEntityException("The scope with id " + id + " no longer exists.");
                }
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }

Die find()-Operation in Zeile 6 findet den Datensatz, obwohl er nicht in der Datenbank existiert.
Vor und nach der merge()-Operation ist der "richtige" Datensatz in der Variablen scope gespeichert. In der Datenbank wird aber nichts aktualisiert (den Datensatz gibt es ja nicht) und es wird auch kein neuer Datensatz angelegt.

Wieso findet der Entitymanager aber überhaupt etwas? Und wieso wird keine Exception geworfen, wenn sich in der Datenbank nichts tut?


Aufgerufen wird die Methode innerhalb meiner BackingBean wie folgt:

Java:
public void editScope() {
        System.out.println("editScope() aufgerufen!");
        try {
            scopeJpaController.edit(rmRequest.getScope());
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("#Speichern erfolgreich"));
        } catch (Exception e) {
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("#Fehler beim Speichern!"));
        }

    }

Gruß Fant
 
M

maki

Gast
Wieso findet der Entitymanager aber überhaupt etwas?
Weil der EM u.U. gar nicht in de DB nachsieht, sondern alles aus dem Cache kommt.

Vereinfacht:
EM = 1st Level Cache
EMF = 2nd Level Cache

Danach wird erst die DB gefragt.

ORM gehen davon aus, dass sie exklusiven Zuriff auf die DB haben und keiner am EM/EMF vorbei direkt Daten in der DB verändert.

Warum willst du denn daten direkt in der DB ändern?
Es git ja noch EntitManager#refresh(entity), mit dem Ergebnis, dass die Entity mit dem DB Zustand aktualisiert wird, aber vorherige Änderungen an de Entity gehen dann verloren.
 

Fant

Bekanntes Mitglied
Eigentlich will ich das gar nicht. Mir ist das Verhalten nur aufgefallen, da ich jetzt in der Entwicklungsphase öfters mal manuell Testdaten in Datenbank schreibe und wieder lösche.

Es war ein administrativer Zugang auf den Datenbestand von einer komplett eigenständigen Applikation aus gedacht, aber bei der existierenden Struktur muss da wohl doch mehr beachtet werden, als gedacht.

Du hast mir jedenfalls erst mal sehr geholfen. Wenn man weiß, woran das Verhalten liegt, dann bekommt man es ja mit dem refresh schnell in den Griff.

Gruß Fant
 
M

maki

Gast
Mit dem refresh alleine bekommst du gar nix schnell in den Griff, denn dann musst du dich noch für eine locking strategy entscheiden.
 

Fant

Bekanntes Mitglied
Berechtigter Einwand, aber das muss ich nicht.. Ich meinte mit meiner letzten Aussage nur mein aktuelles Problem während den Tests. Da reicht mir das mit dem refresh schon aus..

Danke nochmal
Fant
 

caleb

Mitglied
Alternativ kannst Du auch den Cache programmatisch leeren, hier ohne den Code des dazugehörigen Servlets.

Java:
@Stateless
@LocalBean
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public class CacheEvictServiceBean{

  @PersistenceContext
  private EntityManager em;

  private static final Logger LOGGER = Logger.getLogger(CacheEvictServiceBean.class);

  public void evictDatabaseCache() {
    // clear persistence cache
    if (em != null) {
      LOGGER.info("Evicting database cache!");
      em.getEntityManagerFactory().getCache().evictAll();
    }
    else {
      LOGGER.error("No EntityManager! Cannot evict cache!");
    }
  }

}
Praktisch für DB Migrationen wie das ändern vom Datenstamm.
 

Ähnliche Java Themen


Oben