2010-12-02 60 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