【问题标题】:How to do transactional without lose encapsulation?如何在不丢失封装的情况下进行事务处理?
【发布时间】:2011-05-04 06:53:36
【问题描述】:

我有一个代码可以保存一个 bean,并通过 Hibernate 更新数据库中的另一个 bean。它必须在同一个事务中进行,因为如果发生错误(f.ex 启动异常),必须为这两个操作执行回滚。

public class BeanDao extends ManagedSession {

public Integer save(Bean bean) {
    Session session = null;
    try {
        session = createNewSessionAndTransaction();

        Integer idValoracio = (Integer) session.save(bean);  // SAVE
        doOtherAction(bean);                                 // UPDATE

        commitTransaction(session);

        return idBean;
    } catch (RuntimeException re) {
        log.error("get failed", re);
        if (session != null) {
            rollbackTransaction(session);
        }
        throw re;
    }
}

private void doOtherAction(Bean bean) {
    Integer idOtherBean = bean.getIdOtherBean();
    OtherBeanDao otherBeanDao = new OtherBeanDao();
    OtherBean otherBean = otherBeanDao.findById(idOtherBean);
    .
    . (doing operations)
    .
    otherBeanDao.attachDirty(otherBean)
}
}

问题是:

万一

session.save(bean)

启动错误,然后我得到 AssertionFailure,因为函数 doOtherAction(用于项目的其他部分)在抛出异常后使用 session。

我首先想到的是提取函数 doOtherAction 的代码,但后来我有相同的代码重复,这似乎不是最好的做法。

什么是重构它的最佳方法?

【问题讨论】:

    标签: java hibernate transactions


    【解决方案1】:

    在 DAO 之上的一个级别、服务或其他业务逻辑类中管理事务是一种常见的做法。这样,您可以根据业务/服务逻辑,在一种情况下在一个事务中执行两个 DAO 操作,在另一种情况下,在单独的事务中执行它们。

    【讨论】:

      【解决方案2】:

      我是声明式事务管理的忠实粉丝。如果您可以抽出时间让它工作(使用 Application Server 小菜一碟,例如 GlassFish 或 JBoss,使用 Spring 很容易)。如果您使用@TransactionAttribute(REQUIRED) 注释您的业务方法(甚至可以将其设置为默认完成)并且它调用两个DAO 方法,您将得到您想要的:所有内容都立即提交或回滚异常。 这个解决方案几乎是松散耦合的。

      【讨论】:

        【解决方案3】:

        其他人是正确的,因为他们考虑了当前的常见做法。

        但这并不能真正帮助您进行当前的练习。

        您应该做的是创建两个新的 DAO 方法。如 CreateGlobalSession 和 CommitGlobalSession。

        它们的作用与您当前的创建和提交例程相同。

        不同之处在于他们设置了一个“全局”会话变量(很可能最好使用 ThreadLocal 完成)。然后更改当前例程,以便它们检查此全局会话是否已存在。如果您的创建检测到全局会话,则只需将其返回。如果您的提交检测到全局会话,则它什么也不做。

        现在当你想使用它时,你可以这样做:

        try {
            dao.createGlobalSession();
            beanA.save();
            beanb.save();
            Dao.commitGlobalSession();
        } finally {
            dao.rollbackGlobalSession();
        }
        

        确保将进程包装在 try 块中,以便在出现错误时重置全局会话。

        虽然其他技术被认为是最佳实践,并且理想情况下您有朝一日可以演变成类似的技术,但这将让您克服困难,只需 3 种新方法并更改两种现有方法。之后,您的其余代码保持不变。

        【讨论】:

        • 作为一个不得不维护大量使用共享会话/事务(反)模式的代码的人,我可以说在开发阶段使用它是不明智的以避免一些配置工作和重构。这种解决方案往往会增加维护成本。在某一时刻,有人会忘记提交事务,会话会泄漏,您会遇到计划作业或集群应用程序等问题。从完全务实的角度来看,我不是在谈论“理想”的方法,只是“从长远来看被证明很便宜”。
        • 只是为了修正我所说的:共享会话/事务是一种快速复制和粘贴到任何地方的解决方法,迟早会成为项目中事务管理的某种标准。请不要创造怪物。
        • @Anthony,当然,但这就是问题所在。这种情况只需要实际交易,到目前为止(显然)这种情况很少见。所以,DAO 的正常机制仍然存在(当然,它仍然可以泄漏)。然后,如果有人想稍后添加另一个多 bean 事务,他们可能会剪切并粘贴这个原始代码,保留模式。当然,作为副作用。但是,他也没有提到他的代码是在服务器上,在客户端上还是在什么地方。要执行您的建议,他需要 EJB 或 Spring 或其他一些框架。这对他来说可能不切实际。
        • 我明白了,不过,在纯 Java SE 上下文中,我仍然更愿意重构代码,在 DAO 构造函数中接收 Session,并在业务层拥有事务分界代码(见鬼,如果需要,甚至创建一些动态代理)。至少更容易看出事务必须从哪里开始以及必须在哪里结束)。所以我要说的是,即使你的代码可以在一小时内解决他的问题,而不是几个小时/天,我仍然会推荐 Olaf 方法。题外话:1+ 用于休眠“try-with-resources”支持:D.
        猜你喜欢
        • 1970-01-01
        • 2012-04-30
        • 2020-02-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-10
        相关资源
        最近更新 更多