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.
Das hat funktioniert, danke. –
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! –