【问题标题】:DAO pattern - where do transactions fit in?DAO 模式 - 交易适合哪里?
【发布时间】:2023-03-03 00:37:01
【问题描述】:

所以我已经完成了这个通用的 DAO 事情,从表面上看,它似乎没问题。它基本上是仿照 Hibernate 家伙的 CaveatEmptor 示例应用程序。

除此之外,我还有一个业务层……应用程序的核心。它完全不知道任何特定的 DAO 实现。

到目前为止,一切似乎都很好,直到我开始考虑交易。如果将事务留给客户端来实现,那么我究竟如何保持我在各层之间进行的良好分离?也就是说,我目前正在使用 Hibernate,我不太想将特定于 Hibernate 的事务添加到我的业务层代码中。

我可以使用开始、提交和回滚方法创建一个简单的事务接口,并将实现传递给我的业务层...但是...我不确定...

所以挑战来了:你能推荐一种不使用 Spring(或 EJB,或任何其他额外框架)这个词的方法吗?

【问题讨论】:

    标签: transactions dao


    【解决方案1】:

    我记得Martin Fowler 建议在业务层保持对事务的控制,因为事务是一个业务问题。 (如果您设计 BankAccount 类,则事务是领域语言的一部分)。

    您可以尝试在 .NET 中实现 TransactionScope,它的工作原理类似于

    using (TransactionScope ts = new TransactionScope())
    {
      ...
    }
    

    与(不完全是,但如果您是 Java 人,这对您来说更明确)是一样的

    TransactionScope scope = new TransactionScope();
    try
    {
     ...
     scope.Commit();
    }
    catch(Exception ex)
    {
      scope.Rollback();
      throw;
    }
    

    要将您的业务层与任何 DAO 技术分离,您可以在您的领域语言中添加一个 TransactionFactory,它返回一个您使用 Commit 和 Rollback 方法定义的 ITransactionScope(一个接口)。这样你的域层就不会绑定到你的 DAO 层,只有 TransactionFactory 的具体实现。

    ITransactionScope scope = transactionFactory.CreateTransaction();
    try
    {
     ...
     scope.Commit();
    }
    catch(Exception ex)
    {
      scope.Rollback();
      throw;
    }
    

    【讨论】:

    • 嗯,这行得通,但是对于任何实际的商业应用程序,您很快就会得到数千行重复的代码。
    • @Rogerio,并非业务对象中的每个方法都必须是事务。如果最终出现重复,那么可能是您的域没有很好地表达,或者您可以找到更好的方式来表达它。
    • 我刚刚重新阅读了 PoEAA 书籍 (71-77) 中有关交易的相关页面,作者建议不要将交易视为业务问题(它们不是 i> 业务语言的一部分,但也是并发控制的工具)。此外,在实践中,业务操作中涉及的几乎所有方法必须在系统事务的上下文中运行,即使您通常没有针对每个方法的单独事务。重点仍然是,一个设计良好的应用程序不应该到处都有明确的事务分界代码,而只能在一两个中心位置。
    • 除了并发控制事务还用作确保一致性的工具 - 这有点使它们成为业务问题,不是吗?在 BUS 层类 Foo 中,在方法“transferMoney(fromAccount,toAccount,ammount)”中,我会说这是业务逻辑的一部分,即“此方法要么完成所有工作,要么不工作”。并不是说我想用大量的 DB 特定代码来污染这种方法,而是像在 Java Spring 中那样用事务性注释它,对我来说似乎没问题。
    【解决方案2】:

    在 Web 应用程序中,我划分事务的方法是利用 HTTP 请求/响应周期,其中每个原子业务操作都在这些周期之一的范围内、在单个专用线程中执行。

    无论使用什么 Web 框架(Struts、JSF、GWT 等),通常都存在一个可以执行事务划分的“接缝”。在 Struts 中,它可以是一个基本的 Action 类。在 GWT 中,它可以是一个 RemoteServiceImpl 基类。

    因此,使用该中心访问点打开事务(在允许执行特定于应用程序的代码之前),并在没有异常冒泡或回滚的情况下通过提交终止它(在特定于应用程序的代码之后)被执行)。

    我在一个大型复杂的商业网络应用中广泛应用了这个策略,结果证明效果很好。

    【讨论】:

      【解决方案3】:

      也许现在回答有点晚了,但是如何为特定事务创建另一个类,它位于业务层和 dao 层之间?例如。如果来自 DAO 的方法 a() 和 b() 要在某个特定 foo() 业务方法的事务中运行,则创建类似 fooInTransaction() 的东西,它启动一个事务并在其中调用 a() 和 b() .业务方法 foo() 委托给它。

      这将保持业务代码干净,并且可以通过重构避免重复。

      【讨论】:

        【解决方案4】:

        过去,我将事务逻辑放在根 DAO 中,用于与模型中表示系统中单个实体实体的对象层次结构匹配的 DAO 层次结构。

        也就是说,如果你有和 X 有很多 Y,并且你想同时存储和检索 X 和它们的 Y 作为单个复合对象,那么你的 X 的 DAO 也应该调用 Y 的 DAO。然后您可以在 X 的 DAO 中的 add() 和 update() 方法中围绕所有内容进行事务处理 - 甚至可以将 Y DAO 包设为私有以将其隐藏在您的主要业务逻辑中。 即,而不是业务逻辑:

        XDAO xDAO = new XDAO(conn);
        xDAO.startTransaction();
        boolean success = xDAO.add(x);
        if (success)
            for (Y y : x.getYs()) {
                success = YDAO.add(y);
                if (!success) break;
            }
        if (success)
            xDAO.commit();
        else
            xDAO.rollback();
        

        你会得到:

        XDAO xDAO = new XDAO(conn);
        xDAO.add(x);
        

        (具有该 DAO 内部的成功/提交/回滚逻辑)

        但这并不能涵盖所有情况,您的情况可能会有所不同(例如我的工作与 JDBC 一起工作,我不知道 Hibernate 是如何工作的,或者是否有可能)。

        【讨论】:

          【解决方案5】:

          您有权认为应用程序是协调事务的好地方,因为这允许组合由各种服务/管理器/或您想调用的任何东西实现的更复杂的操作。

          一个简单的解决方案是定义一个 ITransaction 接口,并使用某种类型的工厂或 DI 向您的应用程序隐藏实际的 ITransaction 实现者。我使用 nHibernate 在 .net 中像这样滚动我自己的,基本上我有一个基类,我的所有经理(在这种情况下,经理包含一组逻辑实体的业务逻辑,例如可能使用一个或多个存储库的成员资格、订单)。我的基类有一个 ITransaction BeginTransaction(),它根据配置文件动态创建一个类型。

          然后这个类使用 nHibernate 的 Session 来开始和提交事务。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-04-22
            • 1970-01-01
            • 2019-01-10
            • 2018-06-18
            • 2021-05-04
            • 2023-04-05
            • 2017-01-17
            • 2016-01-11
            相关资源
            最近更新 更多