Как управлять жизненным циклом EntityManager в среде CDI (используя Tomcat)

Я разрабатываю одно приложение, и я начал использовать CDI вместе с JSF и JPA. Веб-контейнером является Tomcat.

Я очень озадачен жизненным циклом EntityManager в моих компонентах CDI и мне нужен хороший совет, чтобы очистить некоторые вещи в моей голове. Обычно я читал, что EntityManager должен использоваться в основном в контейнере Java EE, добавляя его с помощью аннотации PersistenceContext. Тогда контейнер заботится о своей жизни. Однако, если вы не используете контейнер Java EE (как Tomcat), тогда мне нужно управлять своей жизнью EntityManager.

Каковы мои лучшие варианты сейчас, используя Tomcat, CDI, JSF and JPA? Что я сейчас делаю, так это следующее:

public class EntityManagerFactoryProducer {

    public static final String TEST = "test";

    @Produces
    @ApplicationScoped
    public EntityManagerFactory create() {
        return Persistence.createEntityManagerFactory(TEST);
    }

    public void destroy(@Disposes
    EntityManagerFactory factory) {
        factory.close();
    }
}

public class EntityManagerProducer {

    @Inject
    private transient Logger logger;

    @Inject
    private EntityManagerFactory emf;

    @Produces
    public EntityManager create() {
        return emf.createEntityManager();
    }

    public void destroy(@Disposes
    EntityManager em) {
        em.close();
        logger.debug(String.format("%s Entity manager was closed", em));
    }
}

@Named
@ViewScoped
@Interceptors(LoggingInterceptor.class)
public class ProductBacking implements Serializable {

    @Inject
    private ProductDAO productDAO;

@ViewScoped
public class ProductDAOImpl implements ProductDAO, Serializable {
    private static final long serialVersionUID = 4806788420578024259L;

    private static final int MAX_RANDOMIZED_ELEMENTS = 3000;

    @Inject
    private transient Logger logger;

    @Inject
    private EntityManager entityManager;

    @Override
    public List<Product> getSuggestedProducts() {
        logger.debug(String.format("%s Entity manager get products", entityManager));

        return entityManager.createQuery("SELECT p FROM Product p ORDER BY random()", Product.class).setMaxResults(
                MAX_RANDOMIZED_ELEMENTS).getResultList();
    }

    @Override
    public void saveProduct(Product product) {
        logger.debug(String.format("%s Entity manager save product", entityManager));

        entityManager.getTransaction().begin();
        entityManager.merge(product);
        entityManager.getTransaction().commit();
    }

    @PreDestroy
    void destroy() {
        entityManager.close();
    }
}

Так что в основном я использую простой CDI для достижения этой цели. Однако я не уверен, является ли это стандартным способом сделать это, и что более важно, я не знаю, как закрыть EntityManager после того, как закончится жизнь bean-компонента. В качестве резюме: у меня много незакрытых соединений (EntityManager), поэтому утечка памяти.

Может ли кто-нибудь помочь мне понять, как мне поступить?

Ответ 1

Это не о CDI. Жизненный цикл EntityManager зависит от его типа, который может быть:

  • транзакция, управляемая контейнером,
  • расширенный контейнер,
  • применение управляемого.

Первые два доступны только на полномасштабном сервере приложений. Поэтому, если вы собираетесь придерживаться контейнера сервлетов, вы сузились до третьего варианта.

Вам нужно будет явно открыть и закрыть EM в вашем приложении. Это просто: создайте экземпляр EntityManagerFactory для всего приложения, введите его во все ваши beans. Когда вам нужна EM, просто создайте его, используйте, а затем немедленно закройте, не дожидаясь окончания контекста bean. Поскольку в этой конфигурации открытый EntityManager сохранит соединение, и с долговечным beans у вас закончится соединение. Вы можете найти простое и всестороннее объяснение в разделе Получение подключения к базе данных JPA в руководстве ObjectDB.

Ответ 2

Вы можете поддерживать состояние CDI bean с помощью CDI Scopes. Фактически EntityManagerProducer#create() отсутствует область действия. Как бы то ни было RI CDI, который вы настраиваете/устанавливаете в tomact, либо его Weld, либо OpenWebBean, вы можете определить свое состояние cdi bean как belwo.

@Produces @RequestScoped
public EntityManager create() {
    return emf.createEntityManager();
}

Ваша проблема

1. CDI, JSF and JPA2.  
2. Managing EntityManager lifecycle when using JPA in a non enterprise environment (e.g. Tomcat)

1. CDI, JSF и JPA2.

Контейнер Tomcat не поддерживает CDI из коробки, даже не JSF, вы знаете, разработчики должны были снабжать JSF баночками для себя. JSF 2.2 имеет новые совместимые с CDI отсеки @ViewScoped здесь, только CDI @FlowScoped, который не имеют эквивалент для @ManagedBean.

(1) Действительно Если вас больше всего интересует использование CDI или CDI + JSF + JPA, то обновите tomcat до TomEE или зайдите в TomEE.    Tomcat + Java EE = TomEE. Java Enterprise Edition от Tomcat, с TomEE вы получаете Tomcat с JPA.

(2) Если вы не контролируете обновление сервера tomcat, в этом случае вам нужно было сделать   я. Поставляйте CDI и некоторые другие баннеры и файлы конфигурации вместе с самим собой.   II. Установка CDI в tomcat (Weld или OpenWebBeans - это основные реализации CDI)

(3) Tomcat 8. Tomcat 8 выровнен с Java EE 7.

2) Управление жизненным циклом EntityManager

Управление жизненным циклом EntityManager при использовании JPA в не-корпоративной среде (например, Tomcat) или Java SE - это настраиваемая задача. В этой ситуации вы должны рассмотреть правильную область EntityManager для использования и при работе с ресурсами всегда важно, чтобы они были закрыты, когда они больше не нужны.

There are three main types of EntityManagers defined in JPA.

    Container Managed and Transaction Scoped Entity Managers
    Container Managed and Extended Scope Entity Managers
    Application Managed Entity Managers

Работа с JPA - это два вида ресурсов, которые мы можем позаботиться: EntityManager и транзакции. В этом случае вы должны рассмотреть правильную область использования EntityManager.

1. An EntityManager is not a heavyload object.
   There is no need to use the same EntityManger longer than needed,
   You can't use an EntityManager instance for the whole application lifecycle (application scope) for the EntityManager is not Thread-safe)
2. It not safe to traverse lazy-loaded relationships once the EntityManager is closed (This situation will change as of JPA 2.0).
i.)Method scope (i.e. instantiate/destroy one EntityManager in each business method).
   The method scope is not enough for every situation. There could be some scenarios where you'll need a wide scope, such as the following situations:
   i.  When transactions spread multiple business methods.
   ii. Need to traverse lazy-loaded relationships outside a method (e.g. in a JSF page).
   In method scope be careful to ensure the EntityManger is always closed
  ii.)Request scope (on-demand creation of the EntityManager instance to use within the request service)
   EntityManager per HTTP request strategy with the following features:
    i.  Creation on demand of the EntityManager.
    ii. Lazy closing of the EntityManager. 

The main benefit of this scope is derived from the delayed closing of the EntityManager (it will last as long as a HTTP request is in process).
Every queried entity will be managed till the end of the request and therefore during the presentation phase (the render phase in JSF for instance).

В вашем случае вы используете манипулятор приложения и управляемую приложением транзакцию, это означает его код, который должен обрабатывать транзакцию. В двух словах это означает:

Вы вызываете:

entityManager.getTransaction().begin(); //to start a transaction

то если вы добьетесь успеха, вы сможете позвонить

entityManager.getTranasaction().commit(); //to commit changes to database

или в случае отказа вы обязательно позвоните:

entityManager.getTransaction().rollBack();

Теперь представьте, что у вас есть контейнер, который знает, когда вызывать begin(), commit() or rollback(), то есть транзакцию, управляемую контейнером.

Ответ 3

Основная проблема заключается в том, что ваш менеджер-менеджер вашего объекта не имеет области. В результате это зависит от того, что никогда не очищается. Вы должны предоставить область для своего менеджера сущностей.

Другое дело, что Apache DeltaSpike уже разрешил это. Почему бы не использовать DeltaSpike? https://deltaspike.apache.org/documentation/jpa.html

Ответ 4

Вы можете настроить три типа EM

container-managed transactional,
container-managed extended,
application-managed.

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

Для управления приложениями обычно мы определяем один EM внутри одного метода.

public List<BookingMainDO> retrieve(String key) {
...
        EntityManager em = null;
        try {
            em = emf.createEntityManager();
            Query query = em.createQuery(queryString);      
            //get the resultList of BookingMain 
            result = query.getResultList();
        } catch (Exception e) {
            DAOExceptionHandler.handler(dataSource,BookingMainDAO.class, e, queryString);
        }finally{
            em.close();
        }
...
}

для управляемой EM контейнера, по умолчанию используется транзакция. вам нужно настроить в spring с помощью ниже аннотации

<context:annotation-config/>

то в вашем классе DAO добавьте ниже аннотацию

@PersistenceContext
private EntityManager em;

то в каждом методе вы можете использовать автоматическую инъекцию EM. с областью действия транзакций EM, persistenceContext может распространяться между различными методами в рамках одной и той же транзакции.