2010-12-02 3 views
1

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.

Répondre

0

Je ne pense pas qu'il soit valide d'accéder à un seul EntityManager géré par l'application à partir de plusieurs threads.

Soit le rendre conteneur gérés transaction de portée:

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

ou créer un EntityManager distinct pour chaque fil (getLocationGroupList() à l'intérieur).

EDIT: Par défaut, EntityManager n'est pas adapté aux threads. La seule exception est EntityManagerEntityManager, EntityManager injectée via @PersistenceContext sans scope = EXTENDED. Dans ce cas, EntityManager agit comme un proxy pour le thread réel local EntityManager s, par conséquent, il peut être utilisé à partir de plusieurs threads.

Pour plus d'informations, voir le §3.3 de JPA Specification.

+0

Cela a fonctionné, merci. –

+0

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! –

Questions connexes