2010-12-02 6 views
1

起動時にいくつかのエンティティをクラスにロードしようとしています。私たちがロードしているエンティティ(LocationGroupエンティティ)は、Join Table(LOCATION_GROUP_MAP)によって定義された別のエンティティ(Locationエンティティ)と@ManyToMany関係を持っています。この関係は熱心に取り出されるべきです。JPA/EclipseLink Eagerフェッチ・データを残しておく(複数の同時問合せ中)

これは、単一のスレッドで、またはJPAクエリを実行するメソッドが同期している場合に問題ありません。しかし、すべてがJPAクエリを非同期的に(同じSingleton DAOクラスを介して)実行している複数のスレッドがある場合、私たちはEagerlyフェッチされるLocation Collectionデータを得ることがあります。

GlassFish v3.0.1でEclipseLinkを使用しています。 (OracleのDBで)

当社のデータベーステーブルは、次のようになります。

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 
... 

そして、我々のJavaコードは次のようになります(私は、ゲッター/セッターとhashCodeを省略等しく、エンティティからのtoStringいる - エンティティでしたその後、)わずかに変更されたので、私は彼らと何らかの問題があるとは思わない、NetBeansのを経由してDBから生成さ:

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; 
     } 
    } 
} 

場所。 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"); 
       } 
      } 
     } 
    } 
} 

は、だから、私たちのマネージャーが起動し、その後、20個のスレッドを作成しますこれらのすべてがtに呼び出されます彼はSingleton LocationGroupDaoを同時に実行し、タイプMY_GROUP_TYPEのLocationGroupsをロードしようとしました。両方のLocationGroupが常に返されます。ただし、LocationGroupエンティティが返されると、@ManyToMany関係で定義されたLocationGroupのLocationコレクションがNULLになることがあります。

LocationGroupDao.getLocationGroupList(LocationGroupType groupType)メソッドを同期させると、すべてが正常です(NullPointerExceptionが発生したことを示す出力行は表示されません)。同様に、Manager.startup()のforループを変更するだけで、単一の反復(したがって、1つのスレッドのみが作成/実行されます)。

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 

しかし、完全な実行後に中のスレッド:例えば、コードのように、我々は、NullPointerExceptionで出力ラインを得るか、(スレッドのいずれかの行だけをフィルタリング)とただし

、同じ実行一切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 

は確かに同時実行の問題のように見えるが、すべての彼らの即時にフェッチに関連するエンティティがロードされていない限りLocationGroupエンティティが返されますなぜ私は表示されません。

私はこれをLazyフェッチでも試してみました。同様の問題が発生しました。実行する最初のいくつかのスレッドは、Locationコレクションが初期化されていないことを示しています。期待どおりに動作します。

答えて

0

複数のスレッドからEntityManagerという1つのアプリケーションにアクセスすることは有効ではないと思います。

どちらのコンテナ管理のトランザクションスコープそれを作る:

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

または(getLocationGroupList()内側)スレッドごとに別々のEntityManagerを作成します。

編集:デフォルトではEntityManagerはスレッドセーフではありません。唯一の例外はコンテナ管理のトランザクションスコープEntityManagerです。EntityManager@PersistenceContextであり、scope = EXTENDEDなしで注入されます。この場合、EntityManagerは、実際のスレッドローカルEntityManagerのプロキシのように動作します。したがって、複数のスレッドから使​​用できます。

詳細については、JPA Specificationの§3.3を参照してください。

+0

ありがとうございました。 –

+0

@PersistenceContextが問題をどのように防止するか説明できますか? 1つのEntityManagerしかインジェクトされていないので、EntityManagerで一度に1つのクエリしか実行されないことを前提としています(同期の追加や@Lock(WRITE)のメソッドの作成と同様)。 このプロセスを完全に並行させる唯一の方法は、複数のEntityManagerインスタンスを持つルートを辿るということですか? もう一度お世話になります! –

関連する問題