2010-12-02 46 views
1

我們試圖在啓動時將一些實體加載到我們的類中。我們正在加載的實體(LocationGroup實體)與另一個實體(位置實體)具有@ManyToMany關係,由聯合表(LOCATION_GROUP_MAP)定義。這種關係應該被熱切地提取。JPA/EclipseLink Eager Fetch使數據未填充(在多個併發查詢期間)

這對單線程或執行JPA查詢的方法同步時正常工作。但是,當有多個線程全部異步執行JPA查詢(全部通過相同的Singleton DAO類)時,我們得到的應該是熱切抓取的位置集合數據,在某些情況下保留NULL。

我們在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代碼如下(我省略了getter/setter方法和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。兩個LocationGroups總是返回。但是,LocationGroup上的Location集合(由@ManyToMany關係定義)有時在返回LocationGroup實體時爲NULL。

如果我們使LocationGroupDao.getLocationGroupList(LocationGroupType groupType)方法同步一切都很好(我們永遠不會看到指示發生NullPointerException的輸出行),並且類似地,如果您將Manager.startup()中的for循環更改爲只有一次迭代(因此只有一個線程被創建/執行)。在完成執行後

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異常,例如輸出線(過濾掉只爲一個線程行)相同的運行沒有NullPointerException異常:

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 fetching - 我得到一個類似的問題,執行的前幾個線程顯示Location集合未初始化,然後在某些時候它被初始化並且所有後來的線程按預期工作。

回答

0

我不認爲從多個線程訪問單個應用程序管理的EntityManager是有效的。

要麼使其容器管理事務範圍:

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

或創建爲每個線程一個單獨EntityManager(內側getLocationGroupList())。

編輯:默認EntityManager是不是線程安全的。唯一的例外是集裝箱管理的交易範圍EntityManager,即通過@PersistenceContext注入EntityManager而沒有scope = EXTENDED。在這種情況下,EntityManager就像實際線程本地代理EntityManager一樣,因此它可以在多個線程中使用。

欲瞭解更多信息,請參閱JPA Specification§3.3。

+0

工作,謝謝。 – 2010-12-02 14:11:44