通过有状态EJB中的无状态本地EJB检索实体(使用会话每次会话进行长对话)

时间:2012-01-12 19:01:56

标签: java transactions ejb entitymanager

首先,我有一个无状态的bean,它做了一个简单的retreive,看起来像这样。

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

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

我有一个用于管理远程客户端长对话的状态bean,它看起来像这样:

@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引用了另一个EntityManager加载的MyEntity实例。所以他们不知道抱怨持久的实体。

我想知道有什么方法可以避免这个问题。如果没有直接解决方案,采用的最佳模式是什么?


编辑:我不想在start()上使用事务,因为在实际应用程序中,statefull bean用于实现需要立即保留的复杂实体模型。我尝试在此处描述的http://docs.jboss.org/hibernate/core/4.0/manual/en-US/html/transactions.html#transactions-basics-apptx中设置称为会话会话的模式。因此,如果我理解正确,解决方案是“在bean B的start()方法中使用事务”,但是如果我这样做,在方法结束时,内容被刷新到数据库,这不是我想要的

我可以看到的其他解决方案是在B的EntityManager中获取MyEntity,所以做一个合并,或者em.find()或者使用em in参数和一个bean A,将retrieveMethod委托给某个DAO样式类,对bean进行简单的委托,在bean B中直接调用DAO。

关于什么是最佳方法的任何想法?

3 个答案:

答案 0 :(得分:2)

这是一个相当有趣的问题。设置似乎绝对合理,但困难在于交易的两个“自动”行为:

  • 将资源共享给调用链中的其他bean
  • 当持久性上下文未与事务关联时自动保留排队的操作

由于您要共享的资源恰好是实体管理器,因此更加困难。否则,您可以在EJB中使用称为“应用程序管理”实体管理器的第三个变体。这个可以通过编程方式与事务相关联(使用em.join()),与事务中的业务方法无关。

您可能需要在没有事务的情况下共享,或者阻止em在事务关联时自动刷新。两者都是我所知道的缺少EJB的功能。 也许应用程序管理的扩展em不会执行自动刷新,但我不会屏住呼吸。

但是更手动的方法呢?不要再调用em.persist(),而是使用单独的列表来存储对话期间需要持久保存的任何实体的引用。

然后在close方法中删除该列表并调用em.persist()。

P.S。

另一个选择:如果使用em.merge()而不是em.persist()会怎么样?合并是一种多功能方法,既可以进行更新也可以进行插入,而不关心是否附加实体。如果实体从未在A和B之间分离,那将会更好,但这可能是一个实际的解决方案。

答案 1 :(得分:1)

问题似乎是正常(非扩展)持久化上下文的范围限定为JTA事务。

由于您已将bean B声明为事务NOT_SUPPORTED,而A已具有REQUIRED,因此调用方法start将为您提供与`retrieveMethod'中的持久化上下文不同的持久性上下文。 (实际上,在第一种情况下根本就没有持久化上下文)。

通常在EJB资源中,包括实体管理器会在单个事务中自动共享,因此即使在不同的bean中看起来像不同的注入,您仍然会获得相同的资源。

即使没有bean A,您的代码也不会有效,因为持久化需要事务存在。显式刷新也没有多大用处,因为在这种情况下不需要这样做(在事务提交时自动发生)。

如果要在对话期间保持托管实体的连接,Bean B可以使用扩展持久性上下文@PersistenceContext(type = EXTENDED)。如果它也使用事务,那么bean A将共享相同的上下文,即使它本身没有扩展上下文(重要的是它将从B的事务上下文中调用)。

答案 2 :(得分:0)

以下是我使用的解决方案:

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

我希望它可以提供帮助(我希望这不是一个不那么丑陋/愚蠢的解决方案)。