Nous essayons de charger certaines entités dans nos classes au démarrage. Les entités que nous chargeons (entités LocationGroup) ont une relation @ManyToMany avec une autre entité (entités Location), définie par une table de jointure (LOCATION_GROUP_MAP). Cette relation devrait être ardemment recherchée.JPA/EclipseLink Eager Récupérer des données non peuplées (pendant plusieurs requêtes simultanées)
Cela fonctionne correctement avec un seul thread ou lorsque la méthode exécutant la requête JPA est synchronisée. Cependant, lorsque plusieurs threads exécutent tous la requête JPA de manière asynchrone (tous via la même classe DAO de Singleton), les données de la collection d'emplacement, qui doivent être récupérées avec précaution, sont laissées NULL dans certains cas.
Nous utilisons EclipseLink dans Glassfish v3.0.1.
Nos tables de base de données (dans un Oracle DB) se présentent comme suit:
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
...
Et notre code Java ressemble à ceci (je l'ai omis getters/setters et hashCode, égaux, toString des entités - les entités ont été générée à partir du DB via NetBeans, puis légèrement modifié, donc je ne crois pas qu'il y ait de problème avec eux):
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;
}
}
}
Lieu. 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");
}
}
}
}
}
Ainsi, notre gestionnaire démarre et crée ensuite 20 fils de discussion, tout cela appelle en t Il Singleton LocationGroupDao simultanément, en essayant de charger les LocationGroups de type MY_GROUP_TYPE. Les deux LocationGroups sont toujours renvoyés. Toutefois, la collection Location sur LocationGroup (définie par la relation @ManyToMany) est parfois NULL lorsque les entités LocationGroup sont renvoyées. Si nous rendons la méthode LocationGroupDao.getLocationGroupList (LocationGroupType groupType) synchronisée tout va bien (nous ne voyons jamais les lignes de sortie indiquant une exception NullPointerException), et de même si vous changez la boucle for dans Manager.startup() pour avoir seulement une seule itération (donc un seul Thread est créé/exécuté).
Cependant, avec le code tel qu'il est, nous obtenons les lignes de sortie avec NullPointerException, par exemple (filtrage juste les lignes pour un des fils):
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
Cependant, fils que l'exécution complète plus tard au cours de la même course ont pas 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
Certainement semble être un problème de concurrence, mais je ne vois pas pourquoi les entités locationgroup sont retournées à moins que toutes les entités liées Ardemment ont été chargés Tirée par les cheveux.En parallèle, j'ai essayé cela avec Lazy fetching - J'ai un problème similaire, les premiers threads à exécuter montrent que la collection Location est non initialisée, puis à un moment donné elle est initialisée et tous les threads plus tard fonctionne comme prévu.
Cela a fonctionné, merci. –
Avez-vous une chance d'expliquer comment @PersistenceContext évite le problème? Comme un seul EntityManager est injecté, je suppose que cela signifie qu'une seule requête à la fois est exécutée sur l'EntityManager, et cela évite les problèmes vus plus haut (similaire à l'ajout synchronisé ou à la méthode @Lock (WRITE)). Je suppose que la seule façon de faire en sorte que ce processus soit entièrement simultané est d'avoir plusieurs instances d'EntityManager? Merci encore pour l'aide! –