Извлечение сущностей через локальный EJB без сохранения состояния из полного состояния EJB (длинный разговор с использованием сеанса на разговор) - PullRequest
3 голосов
/ 12 января 2012

Во-первых, у меня есть bean-компонент без состояния, который выполняет простое ретривирование, выглядящее так

@Stateless
@LocalBean
public A {
    @PersistenceContext
    private EntityManager em;

    public MyEntity retrieveMethod(){
        em.createQuery(...).getSingleResult();
    }
}

У меня есть компонент statefull, используемый для управления длинным разговором с удаленным клиентом, он выглядит следующим образом:

@Statefull
@LocalBean
@TransactionAttribute(NOT_SUPPORTED)
public class B implements BRemote {
    @PersistenceContext(type = EXTENDED)
    private EntityManager em;

    @EJB
    A a;

    public void start(){
        OtherEntity oe = new OtherEntity();
        oe.setRelationMyEntitie(this.a.retrieveMethod());

        em.persist(oe);
    }

    @TransactionAttribute(REQUIRED)
    public void end(){
        em.flush();
    }
}

Проблема возникает при запуске em.persist (oe). oe имеет ссылку на экземпляр MyEntity, который был загружен другим EntityManager. Так что они не знают, что жалуются на сохранение отстраненной сущности.

Хотелось бы узнать, как существует способ избежать этой проблемы. Если нет прямого решения, какой шаблон лучше выбрать?


РЕДАКТИРОВАТЬ: я не хочу использовать транзакцию на start (), потому что в реальном приложении, компонент statefull используется для реализации сложной модели сущностей, которая должна сохраняться сразу. Я пытаюсь настроить шаблон, называемый сеанс на разговор, описанный здесь http://docs.jboss.org/hibernate/core/4.0/manual/en-US/html/transactions.html#transactions-basics-apptx. Так что, если я вас правильно понимаю, решение состоит в том, чтобы «использовать транзакцию в методе start () компонента B», но если я это сделаю это, в конце метода, содержимое сбрасывается в базу данных, и это не то, что я хочу.

Другое решение, которое я вижу, состоит в том, чтобы получить MyEntity в EntityManager B, так что сделайте слияние или em.find () или делегируйте retrieveMethod некоторому классу стиля DAO, используя параметр em в параметре и в bean-компоненте A, сделайте простое делегирование в DAO, в бине B, вызовите непосредственно DAO.

Есть идеи, как лучше всего подходить?

Ответы [ 3 ]

2 голосов
/ 13 января 2012

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

  • Обмен ресурсами с другими компонентами в цепочке вызовов
  • Автоматически сохранять операции, которые были поставлены в очередь, когда контекст постоянства не был связан с транзакцией

Это стало еще сложнее из-за того факта, что ресурс, которым вы хотите поделиться, является именно менеджером сущностей. В противном случае вы могли бы использовать третий вариант в EJB, называемый менеджером сущностей "управляемым приложением". Это можно программно связать с транзакцией (используя em.join ()), независимо от того, используется ли метод бизнес-транзакции или нет.

Вы должны либо поделиться без транзакции, либо запретить их автоматическую очистку при сопоставлении транзакции. Оба, насколько мне известно, отсутствуют функции в EJB. Возможно, приложение, управляющее расширенными приложениями, не выполняет автоматическую очистку, но я не задерживаю дыхание.

Но как насчет более ручного подхода? Пока не вызывайте em.persist (), но используйте отдельный список для хранения ссылок на любые объекты, которые необходимо сохранить во время разговора.

Затем в методе close введите этот список и вызовите em.persist ().

приписка

Еще один вариант: что если вы используете em.merge () вместо em.persist ()? Слияние - это многофункциональный метод, который выполняет как обновления, так и вставки, и не заботится о присоединении сущностей или нет. Было бы лучше, если бы сущности никогда не были отделены между А и В, но это могло бы быть практическим решением.

1 голос
/ 13 января 2012

Кажется, проблема в том, что нормальный (не расширенный) контекст персистентности ограничен транзакцией JTA.

Поскольку вы объявили bean-компонент B как транзакции NOT_SUPPORTED, в то время как A имеет REQUIRED, вызов метода start даст вам другой контекст персистентности, аналогичный контексту в `retrieveMethod '. (на самом деле, в первом случае контекст сохранения вообще не будет).

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

Даже без bean-компонента A ваш код не работал бы, поскольку для его сохранения требуется наличие транзакции. Явная очистка также не будет иметь большого значения, поскольку в этом контексте она не нужна (происходит автоматически при фиксации транзакции).

Если вы хотите оставить управляемые объекты подключенными во время диалога, компонент B может использовать расширенный контекст постоянства @PersistenceContext(type = EXTENDED). Если он также использует транзакции, то bean-компонент A будет использовать тот же контекст, даже если он сам не имеет расширенного контекста (важно то, что он будет вызываться из транзакционного контекста B).

0 голосов
/ 31 января 2012

Вот решение, которое я использовал:

import java.lang.reflect.Field;

import javax.annotation.Resource;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class SessionPerConversationInterceptor {
    private final static ThreadLocal<EntityManager> s_thEntityManager = new ThreadLocal<>();

    @AroundInvoke
public Object manageEntityManager(InvocationContext ctx) throws java.lang.Exception {
    EntityManager em = s_thEntityManager.get();
    if (em == null) {
        MasterPersistenceContext traversableEntityManager = ctx.getTarget().getClass().getAnnotation(MasterPersistenceContext.class);
        if (traversableEntityManager != null) {
            for (Field field : ctx.getTarget().getClass().getDeclaredFields()) {
                if (field.getAnnotation(PersistenceContext.class) != null) {
                    field.setAccessible(true);
                    em = (EntityManager) field.get(ctx.getTarget());
                    s_thEntityManager.set(em);

                    try {
                        Object oRet = ctx.proceed();
                        return oRet;
                    } finally {
                        s_thEntityManager.set(null);
                    }
                }
            }
        }
    } else if (ctx.getTarget().getClass().getAnnotation(MasterPersistenceContext.class) == null) {
        for (Field field : ctx.getTarget().getClass().getDeclaredFields()) {
            if (field.getAnnotation(PersistenceContext.class) != null) {
                field.setAccessible(true);

                EntityManager oldEntityManager = (EntityManager) field.get(ctx.getTarget());

                field.set(ctx.getTarget(), em);

                try {
                    Object oRet = ctx.proceed();
                    return oRet;
                } finally {
                    field.set(ctx.getTarget(), oldEntityManager);
                }
            }
        }
    }

    return ctx.proceed();
}
}

Я надеюсь, что это может помочь (и я надеюсь, что это не очень уродливое / глупое решение).

...