JPA / EclipseLink Eager Fetch, оставляя данные незаполненными (во время нескольких одновременных запросов) - PullRequest
1 голос
/ 02 декабря 2010

Мы пытаемся загрузить некоторые объекты в наши классы при запуске.Загружаемые нами объекты (объекты LocationGroup) имеют отношение @ManyToMany с другим объектом (объектами Location), определенным таблицей соединений (LOCATION_GROUP_MAP).Это отношение должно быть извлечено с нетерпением.

Это прекрасно работает с одним потоком или когда метод, выполняющий запрос JPA, синхронизирован.Однако, когда несколько потоков все выполняют запрос JPA асинхронно (все через один и тот же класс DAO Singleton), мы получаем данные из Коллекции расположений, которые следует извлекать с нетерпением, в некоторых случаях оставляя NULL.

Мы находимсяиспользование EclipseLink в Glassfish v3.0.1.

Таблицы нашей базы данных (в базе данных Oracle) выглядят следующим образом:

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 выглядит следующим образом (я пропустил getters / settersи hashCode, equals, toString from Entities - сущности были сгенерированы из БД через NetBeans, а затем слегка изменены, поэтому я не думаю, что с ними есть какие-либо проблемы):

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

Location.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 потоков, каждый из которых одновременно обращается к Singleton LocationGroupDao, пытаясь загрузить LocationGroups типа MY_GROUP_TYPE.Обе LocationGroups всегда возвращаются.Однако коллекция Location в LocationGroup (определяемая отношением @ManyToMany) иногда равна NULL, когда возвращаются сущности LocationGroup.

Если мы сделаем метод LocationGroupDao.getLocationGroupList (LocationGroupType groupType) синхронизированным, все хорошо (мыникогда не видеть выходные строки, указывающие на возникновение исключения NullPointerException), и аналогично, если вы измените цикл for в Manager.startup (), чтобы он имел только одну итерацию (поэтому создается / выполняется только один поток).

Однако,с кодом, как есть, мы получаем выходные строки с NullPointerException, например (отфильтровывая только строки для одного из потоков):

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

Однако потоки, которые завершают выполнение позже во время того же запуска, имеютno 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, если не были загружены все связанные с ними объекты Eagerly Fetched.

Asпримечание, у меня есть тЭто также вызвано отложенной выборкой - у меня возникает похожая проблема: первые несколько потоков, которые нужно выполнить, показывают, что коллекция Location неинициализирована, а затем в какой-то момент она инициализируется и все более поздние потоки работают как положено.

1 Ответ

0 голосов
/ 02 декабря 2010

Я не думаю, что это допустимо для доступа к одному управляемому приложением EntityManager из нескольких потоков.

Либо сделайте область действия управляемой контейнером областью действия:

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

или создайте отдельный EntityManager для каждого потока (внутри getLocationGroupList()).

РЕДАКТИРОВАТЬ: По умолчанию EntityManager не является потокобезопасным. Единственное исключение - управляемая контейнером область действия EntityManager, то есть EntityManager, введенная через @PersistenceContext без scope = EXTENDED. В этом случае EntityManager действует как прокси для фактического локального потока EntityManager s, поэтому его можно использовать из нескольких потоков.

Для получения дополнительной информации см. §3.3 Спецификация JPA .

...