【问题标题】:@Transactional and JPA object parameters@Transactional 和 JPA 对象参数
【发布时间】:2014-11-11 16:28:46
【问题描述】:

在编写多层应用程序时,最好只将对象 ID 传递给事务服务方法。但我宁愿传递实际的 JPA 对象。与问题Is domain model object passing between layers overhead? 不同,一些同事担心a) 对象可能属于另一个/无事务,如果在服务方法内部修改时会导致问题,并且b) 在从UI 调用此类服务​​方法后修改对象可能会导致问题组件,因为事务已经提交。

用我想要的代码表达

@Named public class MyServiceImpl
{
   ...

   @Transactional
   public BigDecimal calculate(ObjectOne objectOne, ObjectTwo objectTwo)
   {
      ...
   }
}

而不是

@Named public class MyServiceImpl
{
   ...

   @Transactional
   public BigDecimal calculate(long objectOneId, long objectTwoId)
   {
      ObjectOne objectOne = objectOneService.find(objectOneId);
      ObjectTwo objectTwo = objectTwoService.find(objectTwoId);
      ...
   }
}

那么有没有一种技术可以让事务管理器(spring)正确地处理对象?或者您是否建议使用 JPA 合并或其他任何明确的方法来正确处理直接对象引用?还是您也不鼓励传递对象而不是 ID?

显然,带有官方知名来源链接的解释会很有帮助。

【问题讨论】:

    标签: spring jpa transactions


    【解决方案1】:

    由于default behavior of @TransactionalPROPAGATION_REQUIRED,因此将业务对象传递给事务方法应该没问题。如果调用操作已经打开了一个事务,则打开一个新的虚拟事务(对于相同的物理事务)并且对象引用保持不变。如果没有事务处于活动状态,则不会损害持久状态。

    为了确保对象是健全的,你可以这样做

    @Transactional
    public BigDecimal calculate(ObjectOne objectOne, ObjectTwo objectTwo)
    {
       objectOne = objectOneService.find(objectOne.getId());
       objectTwo = objectTwoService.find(objectTwo.getId());
       ...
    }
    

    这很便宜,因为对象是从缓存中获取的。但它不是必需的,因为对象在同一个物理事务中。

    如果对象确实来自不同的事务,您可以使用上面的代码解决它。但是您的调用方法不会看到对对象所做的任何更改,除非它再次从数据库中获取其对象(并且取决于隔离启动一个新事务)。

    【讨论】:

      【解决方案2】:

      根据我的经验,我更倾向于传递对象而不是 id。根据您的示例,这是非常简单的场景。但在实时中,对象可能非常复杂并且有很多子对象。在这种情况下,每次检索对象都会影响性能。

      因场景而异。在复杂对象的情况下,我更喜欢将对象存储在会话中并将其传递并合并到实体管理器中。

      此外,它还取决于其他因素,例如您希望在会话中保留多少数据(在传递完整对象的情况下)或每次检索对象时您可以承受多少性能下降。

      【讨论】:

        【解决方案3】:

        您对分离对象的概念不清楚。请参阅以下链接了解 JPA/hibernate 中的不同对象状态及其发生时间。

        Hibernate - Working with objects 并引用 here 为例

        【讨论】:

        • 是的,这正是我所指的分离对象的概念。你觉得有什么不清楚的地方?
        • 根据您的陈述 - a) 对象可能会在服务方法中分离,并且 b) 在从 UI 组件调用此类服务​​方法后,对象可能会分离/破坏。它使认为它不清楚。一旦您的会话关闭,对象将被分离,让它处于服务或任何其他层。现在,如果您想将该分离的对象传递到 UI 中,那么您应该创建新的客户端模型并将 dao 模型复制到客户端模型中,然后再将其传递给 UI。从 UI 中,您将获得客户端模型复制到 dao 模型,然后将其合并。
        • 啊。我试图改善我的问题中的担忧。但实际的问题是,传递对象是否被认为是不好的做法。或者在哪些情况下可以/不能使用它。
        • 您可以传递对象,最佳实践是创建另一个模型类并将 DAO/DTO 对象状态复制到该模型类实例以将其传递给 UI 或任何其他层。不要将 DAO/DTO 对象传递给 UI。更具体地说,DAO/DTO 对象应该只保留在 DAO 和业务层中,它们不应该暴露给任何其他层。要转移到任何其他层,您为该层创建模型类并从业务层复制它们。
        • 这很有趣。您的意思是一般业务对象不应该跨层共享吗?如果是,参考是如何在层之间传递的?因为在 UI 层中不能使用 id,所以无法访问数据库。或者您的意思是业务对象在所有层中都使用但它们不是持久的,但 DTO 是?
        【解决方案4】:

        根据我的经验,正确的方法是在中间:根据您的用例(即业务逻辑),您会想要一个或另一个。

        使用 ID 的示例: 假设信用审批人想要批准或拒绝信用。为此,我肯定会选择 ID: ApprovalService.approveCreditRequest(Long creditRequestId, ApprovalStatusapprovalStatus); 在 web 层中获取CreditRequest 的问题对我来说似乎是一个无用的调用,假设这不是问题,您必须确保在您的 EJB/服务层中 web 层向您发送正确加载的 @987654323 @instance,即Web 层中没有人更改任何重要字段,例如creditRequest.setApprovalStatus(ApprovalStatus.DENY)creditRequest.setRequester(anotherRequester)。并且不要只考虑 WebLayer,因为可能有一个远程 Ejb 调用您的服务层,您必须信任它。您肯定要使用 ID 的另一个用例是在以下场景中:您有一个批次,通过调用方法 approveCreditRequest(),您批准了许多(数万个)CreditRequests。从数据库中获取所有实体及其所有关系(而不仅仅是它们的 ID)肯定是一个性能瓶颈,并且将每个实例传递给该方法可能会两次批准某些 CreditRequest(例如,在获取实例和调用 approveCreditRequest() 之间的同时,已经被批准了)。

        这种方法的缺点是:不容易UnitTest,你需要一个集成测试或一个模拟框架(例如mockito)。

        使用完全对象的示例: 假设您需要在 HTTP 请求之后更改 90% 的字段,例如在更新实体时。比使用整个对象更容易。

        现在涉及到实例的托管状态:当Entity实例离开服务层时(不管是EJB还是Spring Beans),你必须确保事务是关闭的(在WebLayer中永远不要使用Transactional),所以实体将自动取消管理,因此您可以在 WebLayer 中毫无问题地更改它们。

        【讨论】:

        • 在第一个示例中,对象在 UI 中不可用,首先,对吗?所以你不会从数据库中获取它。这是完全理智的。但是在现有对象的情况下,您将通过它们。那是你的答案吗?如果是,您是否知道有任何来源可以证明这是最佳做法?
        • 是的,在第一个示例中,该对象在 UI 中不可用。当谈到最佳实践时,没有证据,只有争论。但我提到了一些赞成和反对“仅使用 ID”的论点。
        • @Christian 我添加了另一个细节:一个额外的示例何时使用 ID 以及使用 ID 时应该做什么。
        猜你喜欢
        • 2017-10-10
        • 2011-10-15
        • 2013-11-12
        • 1970-01-01
        • 2012-02-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-16
        相关资源
        最近更新 更多