2010-12-02 4 views
1

Wir versuchen beim Start einige Entitäten in unsere Klassen zu laden. Die Entitäten, die wir laden (LocationGroup-Entitäten) haben eine @ManyToMany-Beziehung mit einer anderen Entität (Standort-Entitäten), die durch eine Join-Tabelle (LOCATION_GROUP_MAP) definiert wird. Diese Beziehung sollte eifrig abgerufen werden.JPA/EclipseLink Eager Abrufdaten nicht gefüllt (bei mehreren gleichzeitigen Abfragen)

Dies funktioniert gut mit einem einzelnen Thread, oder wenn die Methode, die die JPA-Abfrage durchführt, synchronisiert ist. Wenn es jedoch mehrere Threads gibt, die alle die JPA-Abfrage asynchron ausführen (alle über die gleiche DAO-Klasse von Singleton), erhalten wir die Location Collection-Daten, die in einigen Fällen mit NULL abgerufen werden sollten.

Wir verwenden EclipseLink in Glassfish v3.0.1.

Unsere Datenbank-Tabellen (in einer Oracle-DB) wie folgt aussehen:

LOCATION_GROUP 
location_group_id | location_group_type 
------------------+-------------------- 
GROUP_A   | MY_GROUP_TYPE 
GROUP_B   | MY_GROUP_TYPE 

LOCATION_GROUP_MAP 
location_group_id | location_id 
------------------+------------ 
GROUP_A   | LOCATION_01 
GROUP_A   | LOCATION_02 
GROUP_A   | ... 
GROUP_B   | LOCATION_10 
GROUP_B   | LOCATION_11 
GROUP_B   | ... 

LOCATION 
location_id 
----------- 
LOCATION_01 
LOCATION_02 
... 

Und unsere Java-Code sieht wie folgt aus (ich habe weggelassen Getter/Setter und hashCode, gleich, toString von Unternehmen - die Entitäten waren erzeugt von der DB über NetBeans, dann leicht modifiziert, so glaube ich nicht, dass es mit ihnen jede Ausgabe):

LocationGroup.java:

@Entity 
@Table(name = "LOCATION_GROUP") 
@NamedQueries({ 
    @NamedQuery(name = "LocationGroup.findAll", query = "SELECT a FROM LocationGroup a"), 
    @NamedQuery(name = "LocationGroup.findByLocationGroupId", query = "SELECT a FROM LocationGroup a WHERE a.locationGroupId = :locationGroupId"), 
    @NamedQuery(name = "LocationGroup.findByLocationGroupType", query = "SELECT a FROM LocationGroup a WHERE a.locationGroupType = :locationGroupType")}) 
public class LocationGroup implements Serializable { 
    private static final long serialVersionUID = 1L; 

    @Id 
    @Basic(optional = false) 
    @Column(name = "LOCATION_GROUP_ID") 
    private String locationGroupId; 

    @Basic(optional = false) 
    @Column(name = "LOCATION_GROUP_TYPE") 
    private String locationGroupType; 

    @JoinTable(name = "LOCATION_GROUP_MAP", 
     joinColumns = { @JoinColumn(name = "LOCATION_GROUP_ID", referencedColumnName = "LOCATION_GROUP_ID")}, 
     inverseJoinColumns = { @JoinColumn(name = "LOCATION_ID", referencedColumnName = "LOCATION_ID")}) 
    @ManyToMany(fetch = FetchType.EAGER) 
    private Collection<Location> locationCollection; 

    public LocationGroup() { 
    } 

    public LocationGroup(String locationGroupId) { 
     this.locationGroupId = locationGroupId; 
    } 

    public LocationGroup(String locationGroupId, String locationGroupType) { 
     this.locationGroupId = locationGroupId; 
     this.locationGroupType = locationGroupType; 
    } 

    public enum LocationGroupType { 
     MY_GROUP_TYPE("MY_GROUP_TYPE"); 

     private String locationGroupTypeString; 

     LocationGroupType(String value) { 
      this.locationGroupTypeString = value; 
     } 

     public String getLocationGroupTypeString() { 
      return this.locationGroupTypeString; 
     } 
    } 
} 

Lage.

java
@Entity 
@Table(name = "LOCATION") 
public class Location implements Serializable { 
    private static final long serialVersionUID = 1L; 

    @Id 
    @Basic(optional = false) 
    @Column(name = "LOCATION_ID") 
    private String locationId; 

    public Location() { 
    } 

    public Location(String locationId) { 
     this.locationId = locationId; 
    } 

} 

LocationGroupDaoLocal.java

@Local 
public interface LocationGroupDaoLocal { 
    public List<LocationGroup> getLocationGroupList(); 
    public List<LocationGroup> getLocationGroupList(LocationGroupType groupType); 
} 

LocationGroupDao.java

@Singleton 
@LocalBean 
@Startup 
@Lock(READ) 
public class LocationGroupDao implements LocationGroupDaoLocal { 
    @PersistenceUnit(unitName = "DataAccess-ejb") 
    protected EntityManagerFactory factory; 

    protected EntityManager entityManager; 

    @PostConstruct 
    public void setUp() { 
     entityManager = factory.createEntityManager(); 
     entityManager.setFlushMode(FlushModeType.COMMIT); 
    } 

    @PreDestroy 
    public void shutdown() { 
     entityManager.close(); 
     factory.close(); 
    } 

    @Override 
    public List<LocationGroup> getLocationGroupList() { 
     TypedQuery query = entityManager.createNamedQuery("LocationGroup.findAll", LocationGroup.class); 
     return query.getResultList(); 
    } 

    @Override 
    public List<LocationGroup> getLocationGroupList(LocationGroupType groupType) { 
     System.out.println("LOGGING-" + Thread.currentThread().getName() + ": Creating Query for groupType [" + groupType + "]"); 
     TypedQuery query = entityManager.createNamedQuery("LocationGroup.findByLocationGroupType", LocationGroup.class); 
     query.setParameter("locationGroupType", groupType.getLocationGroupTypeString()); 
     System.out.println("LOGGING-" + Thread.currentThread().getName() + ": About to Execute Query for groupType [" + groupType + "]"); 
     List<LocationGroup> results = query.getResultList(); 
     System.out.println("LOGGING-" + Thread.currentThread().getName() + ": Executed Query for groupType [" + groupType + "] and got [" + results.size() + "] results"); 
     return results; 
    } 
} 

Manager.java

@Singleton 
@Startup 
@LocalBean 
public class Manager { 
    @EJB private LocationGroupDaoLocal locationGroupDao; 

    @PostConstruct 
    public void startup() { 
     System.out.println("LOGGING: Starting!"); 

     // Create all our threads 
     Collection<GroupThread> threads = new ArrayList<GroupThread>(); 
     for (int i=0; i<20; i++) { 
      threads.add(new GroupThread()); 
     } 

     // Start each thread 
     for (GroupThread thread : threads) { 
      thread.start(); 
     } 
    } 

    private class GroupThread extends Thread { 
     @Override 
     public void run() { 
      System.out.println("LOGGING-" + this.getName() + ": Getting LocationGroups!"); 
      List<LocationGroup> locationGroups = locationGroupDao.getLocationGroupList(LocationGroup.LocationGroupType.MY_GROUP_TYPE); 
      for (LocationGroup locationGroup : locationGroups) { 
       System.out.println("LOGGING-" + this.getName() + ": Group [" + locationGroup.getLocationGroupId() + 
         "], Found Locations: [" + locationGroup.getLocationCollection() + "]"); 

       try { 
        for (Location loc : locationGroup.getLocationCollection()) { 
         System.out.println("LOGGING-" + this.getName() + ": Group [" + locationGroup.getLocationGroupId() 
           + "], Found location [" + loc.getLocationId() + "]"); 
        } 
       } catch (NullPointerException npe) { 
        System.out.println("LOGGING-" + this.getName() + ": Group [" + locationGroup.getLocationGroupId() 
          + "], NullPointerException while looping over locations"); 
       } 

       try { 
        System.out.println("LOGGING-" + this.getName() + ": Group [" + locationGroup.getLocationGroupId() 
         + "], Found [" + locationGroup.getLocationCollection().size() + "] Locations"); 
       } catch (NullPointerException npe) { 
        System.out.println("LOGGING-" + this.getName() + ": Group [" + locationGroup.getLocationGroupId() 
          + "], NullPointerException while printing Size of location collection"); 
       } 
      } 
     } 
    } 
} 

So unser Manager startet, und erstellt dann 20 Fäden, die alle zu t aufrufen Der Singleton LocationGroupDao versucht gleichzeitig, die LocationGroups vom Typ MY_GROUP_TYPE zu laden. Beide LocationGroups werden immer zurückgegeben. Die Location-Auflistung in der LocationGroup (definiert durch die @ ManyToMany-Beziehung) ist jedoch manchmal NULL, wenn die LocationGroup-Entitäten zurückgegeben werden.

Wenn wir die LocationGroupDao.getLocationGroupList (LocationGroupType group) -Methode machen synchronisiert alles gut ist (wir nie die Ausgangsleitungen angibt, eine Nullpointer aufgetreten sehen), und in ähnlicher Weise, wenn Sie die in Manager.startup for-Schleife ändern(), um nur haben eine einzelne Iteration (also nur ein Thread erstellt/ausgeführt wird).

jedoch mit dem Code wie es ist, wir die Ausgangsleitungen mit Nullpointer tun bekommen, zB (Filterung nur die Linien für einen der Fäden aus):

LOGGING-Thread-172: Getting LocationGroups! 
LOGGING-Thread-172: Creating Query for groupType [MY_GROUP_TYPE] 
LOGGING-Thread-172: About to Execute Query for groupType [MY_GROUP_TYPE] 
LOGGING-Thread-172: Executed Query for groupType [MY_GROUP_TYPE] and got [2] results 
LOGGING-Thread-172: Group [GROUP_A], Found Locations: [null] 
LOGGING-Thread-172: Group [GROUP_A], NullPointerException while looping over locations 
LOGGING-Thread-172: Group [GROUP_A], NullPointerException while printing Size of location collection 
LOGGING-Thread-172: Group [GROUP_B], Found Locations: [null] 
LOGGING-Thread-172: Group [GROUP_B], NullPointerException while looping over locations 
LOGGING-Thread-172: Group [GROUP_B], NullPointerException while printing Size of location collection 

jedoch Threads, die vollständige Ausführung später während die gleichen Lauf haben keine Nullpointerexceptions:

LOGGING-Thread-168: Getting LocationGroups! 
LOGGING-Thread-168: Creating Query for groupType [MY_GROUP_TYPE] 
LOGGING-Thread-168: About to Execute Query for groupType [MY_GROUP_TYPE] 
LOGGING-Thread-168: Executed Query for groupType [MY_GROUP_TYPE] and got [2] results 
LOGGING-Thread-168: Group [GROUP_A], Found Locations: [...] 
LOGGING-Thread-168: Group [GROUP_A], Found location [LOCATION_01] 
LOGGING-Thread-168: Group [GROUP_A], Found location [LOCATION_02] 
LOGGING-Thread-168: Group [GROUP_A], Found location [LOCATION_03] 
... 
LOGGING-Thread-168: Group [GROUP_A], Found [8] Locations 
LOGGING-Thread-168: Group [GROUP_B], Found Locations: [...] 
LOGGING-Thread-168: Group [GROUP_B], Found location [LOCATION_10] 
LOGGING-Thread-168: Group [GROUP_B], Found location [LOCATION_11] 
LOGGING-Thread-168: Group [GROUP_B], Found location [LOCATION_12] 
... 
LOGGING-Thread-168: Group [GROUP_B], Found [11] Locations 

auf jeden Fall erscheint ein Concurrency Problem zu sein, aber ich sehe nicht, warum die LOCATION Entitäten, wenn alle ihre Eifrig Fetched verbundenen Unternehmen geladen wurden zurückgegeben werden.Als Nebenbemerkung habe ich dies mit Lazy Fetching auch versucht - ich bekomme ein ähnliches Problem, zeigen die ersten paar Threads ausführen, dass die Location-Auflistung nicht initialisiert ist, und dann irgendwann wird es initialisiert und alle späteren Threads funktionieren wie erwartet.

Antwort

0

Ich glaube nicht, dass es gültig ist, auf eine einzige Anwendung zuzugreifen verwaltet EntityManager aus mehreren Threads.

Entweder es Container verwalteten transaktions scoped:

@PersistenceContext(unitName = "DataAccess-ejb") 
protected EntityManager entityManager; 

oder eine separate EntityManager für jeden Thread erzeugen (innen getLocationGroupList()).

EDIT: Standardmäßig ist EntityManager nicht Thread-sicher. Die einzige Ausnahme ist eine Container-verwaltete transaktionsbasierte Transaktion EntityManager, die EntityManager über @PersistenceContext ohne scope = EXTENDED injiziert wird. In diesem Fall verhält sich EntityManager wie ein Proxy für das tatsächliche Thread-lokale EntityManager s, daher kann es aus mehreren Threads verwendet werden.

Für weitere Informationen siehe §3.3 von JPA Specification.

+0

Das hat funktioniert, danke. –

+0

Können Sie erklären, wie @PersistenceContext das Problem verhindert? Da nur ein EntityManager eingefügt wird, gehe ich davon aus, dass im EntityManager jeweils nur eine Abfrage ausgeführt wird, was die oben genannten Probleme verhindert (ähnlich wie das Hinzufügen von synchronisierten Methoden oder die Methode @Lock (WRITE)). Ich gehe davon aus, die einzige Möglichkeit, diesen Prozess vollständig gleichzeitig zu machen, ist die Route mit mehreren EntityManager-Instanzen gehen? Danke nochmal für die Hilfe! –