Подтвердить что ты не робот

Получение ссылки на EntityManager в приложениях Java EE с использованием CDI

Я использую Java EE 7. Я хотел бы знать, как правильно вводить JPA EntityManager в приложение CDI bean. Вы не можете просто вводить его с помощью аннотации @PersistanceContext, потому что экземпляры EntityManager не являются потокобезопасными. Предположим, что мы хотим, чтобы наш EntityManager был создан в начале каждой обработки HTTP-запроса и закрыт после обработки HTTP-запроса. Мне приходят в голову два варианта:

1. Создайте CDI bean с расширенным запросом, который имеет ссылку на EntityManager, а затем введите bean в CDI bean с областью приложения.

import javax.enterprise.context.RequestScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@RequestScoped
public class RequestScopedBean {

    @PersistenceContext
    private EntityManager entityManager;

    public EntityManager getEntityManager() {
        return entityManager;
    }
}

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

@ApplicationScoped
public class ApplicationScopedBean {

    @Inject
    private RequestScopedBean requestScopedBean;

    public void persistEntity(Object entity) {
        requestScopedBean.getEntityManager().persist(entity);
    }
}

В этом примере будет создан EntityManager при создании RequestScopedBean и будет закрыт при уничтожении RequestScopedBean. Теперь я могу переместить инъекцию в какой-то абстрактный класс, чтобы удалить его из ApplicationScopedBean.

2. Создайте производителя, который создает экземпляры EntityManager, а затем вставьте экземпляр EntityManager в CDI bean с охватом приложения.

import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class EntityManagerProducer {

    @PersistenceContext
    @Produces
    @RequestScoped
    private EntityManager entityManager;
}

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;

@ApplicationScoped
public class ApplicationScopedBean {

    @Inject
    private EntityManager entityManager;

    public void persistEntity(Object entity) {
        entityManager.persist(entity);
    }
}

В этом примере мы также будем иметь EntityManager, который создается каждый HTTP-запрос, но как насчет закрытия EntityManager? Будет ли он также закрыт после обработки HTTP-запроса? Я знаю, что аннотация @PersistanceContext вводит управляемый контейнером EntityManager. Это означает, что EntityManager будет закрыт, когда клиент bean будет уничтожен. Что такое клиент bean в этой ситуации? Это ApplicationScopedBean, который никогда не будет уничтожен до тех пор, пока приложение не остановится, или это будет EntityManagerProducer? Любые советы?

Я знаю, что я мог бы использовать независимый EJB без приложения scoped bean, а затем просто вставлять EntityManager by @PersistanceContext аннотацию, но это не точка:)

4b9b3361

Ответ 1

Ты почти прав с продюсером CDI. Единственное, что вы должны использовать метод производителя вместо поля производителя.

Если вы используете Weld в качестве контейнера CDI (GlassFish 4.1 и WildFly 8.2.0), то ваш пример объединения @Produces, @PersistenceContext и @RequestScoped в поле производителя должен выбросить это исключение во время развертывания:

org.jboss.weld.exceptions.DefinitionException: WELD-001502: Поле источника ресурсов [Поле создателя ресурсов [EntityManager] с квалификаторы [@Any @Default], объявленные как [[BackedAnnotatedField] @Produces @RequestScoped @PersistenceContext com.somepackage.EntityManagerProducer.entityManager]] должен быть @Dependent scoped

Оказывается, что контейнер не требуется для поддержки какой-либо другой области, кроме @Dependent, при использовании поля производителя для поиска ресурсов Java EE.

CDI 1.2, раздел 3.7. Ресурсы:

Контейнер не требуется для поддержки ресурсов с областью действия другой чем @Dependent. Переносимые приложения не должны определять ресурсы с любой областью действия, кроме @Dependent.

Эта цитата была посвящена полям производителей. Использование метода-производителя для поиска ресурса полностью законно:

public class EntityManagerProducer {

    @PersistenceContext    
    private EntityManager em;

    @Produces
    @RequestScoped
    public EntityManager getEntityManager() {
        return em;
    }
}

Во-первых, контейнер будет создавать экземпляр производителя, а ссылка с менеджером сущности, управляемой контейнером, будет введена в поле em. Затем контейнер вызовет ваш метод производителя и перенесет то, что он возвращает, в прокси-сервер CDI с запросом. Этот прокси-сервер CDI получает код вашего клиента при использовании @Inject. Поскольку класс-производитель @Dependent (по умолчанию), базовая ссылка диспетчера сущностей, управляемая контейнером, не будет использоваться другими прокси-серверами CDI. Каждый раз, когда другой запрос запрашивает диспетчер сущностей, будет создан экземпляр нового экземпляра класса продюсера, а в производитель будет введена новая ссылка менеджера объектов, которая, в свою очередь, будет завершена в новый прокси-сервер CDI.

Чтобы быть технически корректным, базовый и неназванный контейнер, который выполняет инъекцию ресурсов в поле em, может повторно использовать старые администраторы сущностей (см. сноску в спецификации JPA 2.1, раздел "7.9.1 Обязанности контейнеров", стр. 357). Но до сих пор мы чтим модель программирования, требуемую JPA.

В предыдущем примере не имело бы значения, если вы отметите EntityManagerProducer @Dependent или @RequestScoped. Использование @Dependent семантически более корректно. Но если вы ставите более широкий охват, чем область запроса в классе производителя, вы рискуете подвергнуть ссылку на базовый менеджер сущности многим потокам, которые мы оба знаем, не очень хорошо. Реализация менеджера основного объекта, вероятно, является поточно-локальным объектом, но переносимые приложения не могут полагаться на детали реализации.

CDI не знает, как закрыть все, что угодно, что вы помещаете в связанный с запросом контекст. Более того, управляющий сущностью, управляемый контейнером, не должен быть закрыт кодом приложения.

JPA 2.1, раздел "7.9.1 Обязанности контейнеров":

Контейнер должен выдать исключение IllegalStateException, если приложение вызывает EntityManager.close в менеджере сущностей, управляемых контейнером.

К сожалению, многие люди используют метод @Disposes для закрытия диспетчера сущности контейнера. Кто может обвинить их, когда официальный учебник Java EE 7, предоставленный Oracle, а также спецификация CDI сам использует средство удаления, чтобы закрыть диспетчер сущности, управляемый контейнером. Это просто неправильно, и вызов EntityManager.close() будет вызывать IllegalStateException независимо от того, где вы помещаете этот вызов, в метод удаления или где-то еще. Пример Oracle является самым большим грешником из двух, объявив класс производителя @javax.inject.Singleton. Как мы узнали, этот риск раскрывает ссылку на основной менеджер объектов на множество разных потоков.

Было доказано здесь, что с использованием производителей CDI и средств устранения неправомерно: 1) не зависящий от потоков объект менеджер объекта может быть просочился во многие потоки и 2) утилизатор не действует; оставляя менеджера объекта открытым. То, что произошло, было исключение IllegalStateException, которое контейнер проглотил, не оставляя следов (загадочная запись в журнале, в которой говорится, что была "ошибка, разрушающая экземпляр" ).

Как правило, использование CDI для поиска менеджеров сущностей, управляемых контейнерами, не является хорошей идеей. Приложение, скорее всего, лучше использовать только @PersistenceContext и быть довольным им. Но всегда есть исключения из правила, как в вашем примере, и CDI также может быть полезен для абстрагирования EntityManagerFactory при обработке жизненного цикла управляемых сущностями приложений.

Чтобы получить полное представление о том, как получить управляемый сущностью контейнер, управляемый контейнером и как использовать CDI для поиска сущностей, вы можете прочитать this и this.

Ответ 2

Вы должны использовать аннотацию @Dispose, чтобы закрыть EntityManager, как в примере ниже:

@ApplicationScoped
public class Resources {

    @PersistenceUnit
    private EntityManagerFactory entityManagerFactory;

    @Produces
    @Default
    @RequestScoped
    public EntityManager create() {
        return this.entityManagerFactory.createEntityManager();
    }

    public void dispose(@Disposes @Default EntityManager entityManager) {
        if (entityManager.isOpen()) {
            entityManager.close();
        }
    }

}

Ответ 3

Вы можете добавить sahesty EntityManagerFactory, это сохранить поток

@PersistenceUnit(unitName = "myUnit")
private EntityManagerFactory entityManagerFactory;

то вы можете получить EntityManager из entityManagerFactory.