【问题标题】:Where does the @Transactional annotation belong?@Transactional 注解属于哪里?
【发布时间】:2010-11-07 22:11:29
【问题描述】:

您应该将@Transactional 放在DAO 类和/或其方法中,还是最好注释使用DAO 对象调用的服务类?或者注释两个“层​​”有意义吗?

【问题讨论】:

    标签: java spring annotations transactions dao


    【解决方案1】:

    我认为事务属于服务层。它了解工作单元和用例。如果您将多个 DAO 注入到需要在单个事务中协同工作的服务中,这是正确的答案。

    【讨论】:

    • 我同意这一点。有时这并不重要,但有时您可以从中受益,例如休眠会话是为 while 事务跨越的,因此所有加载的对象都在一级缓存中,您不需要再次将对象重新附加到会话,加上延迟加载的属性功能,无需模糊。
    • 一个全局事务可以包含多个@Transactional(propagation = Propagation.REQUIRED) 吗?或者@Transactional 始终是事务的边界?我不确定我是从文档中得到的,但似乎你可以创建由 @Transactional 方法和在其中运行的所有内容组成的事务
    • 是的,我认为如果 Propagation.REQUIRED 开启,最外层的@Transactional 就是事务的边界。
    • 当@Transactional 是单个工作单元时,我们不需要明确提及它。
    【解决方案2】:

    总的来说,我同意其他人的观点,即事务通常在服务级别启动(当然取决于您需要的粒度)。

    然而,与此同时,我也开始将@Transactional(propagation = Propagation.MANDATORY) 添加到我的 DAO 层(以及其他不允许启动事务但需要现有事务的层),因为在您忘记启动的地方更容易检测到错误调用者(例如服务)中的事务。如果您的 DAO 使用强制传播进行注释,您将收到一个异常,指出调用该方法时没有活动事务。

    我还有一个集成测试,我检查所有 bean(bean 后处理器)是否有这个注解,如果在不属于服务层的 bean 中有一个 @Transactional 具有传播以外的传播的注解,则失败。这样我可以确保我们不会在错误的层上启动事务。

    【讨论】:

    • 这应该在dao(接口)层,在dao impl中完成吗?层还是两者都有?
    • 我不知道它是否适合这个讨论,但另一个提示可以在非写入操作的 dao impl 中添加 @Transactional(readOnly = true)。
    • @Johan Spring 建议将 Transaction 注释放在实现类而不是接口上。
    • 我真的很喜欢这个想法,也尝试在我的项目中使用它
    • 让我看看我是否明白......你是说我应该把@Transactional放在Service实现类上,我应该把@Transactional(propagation = MANDATORY)放在DAO(存储库)类实现上?
    【解决方案3】:

    事务性注解应该放在所有不可分割的操作周围。

    例如,您的呼叫是“更改密码”。这包括两个操作

    1. 更改密码。
    2. 审核更改。
    3. 向客户发送电子邮件告知密码已更改。

    那么在上面,如果审核失败,那么密码更改是否也应该失败?如果是这样,那么事务应该在 1 和 2 左右(在服务层也是如此)。如果电子邮件失败(可能应该对此进行某种故障保护,以免失败),那么它是否应该回滚更改密码和审核?

    这些是您在决定将@Transactional 放在哪里时需要提出的问题。

    【讨论】:

      【解决方案4】:

      传统 Spring 架构的正确答案是将事务语义放在服务类上,原因其他人已经描述过。

      春季的一个新兴趋势是domain-driven design (DDD)。 Spring Roo 很好地体现了这一趋势。这个想法是使域对象 POJO 比典型的 Spring 架构(通常是 anemic)更多 richer,特别是在域对象本身上放置事务和持久性语义。在只需要简单的 CRUD 操作的情况下,Web 控制器直接在域对象 POJO 上操作(它们在此上下文中作为实体运行),并且没有服务层。在域对象之间需要某种协调的情况下,您可以使用服务 bean 处理它,按照传统使用@Transaction。您可以将域对象上的事务传播设置为 REQUIRED 之类的内容,以便域对象使用任何现有事务,例如在服务 bean 上启动的事务。

      从技术上讲,这种技术利用了 AspectJ 和 <context:spring-configured />。 Roo 使用 AspectJ 类型间定义将实体语义(事务和持久性)与域对象内容(基本上是字段和业务方法)分开。

      【讨论】:

        【解决方案5】:

        通常情况下是在服务层级别进行注释,但这实际上取决于您的要求。

        在服务层进行注释将导致比在 DAO 级别进行注释更长的事务。取决于可以解决问题的事务隔离级别,因为并发事务不会看到彼此的更改,例如。可重复阅读。

        在 DAO 上注释将使事务尽可能短,缺点是您的服务层公开的功能不会在单个(可回滚)事务中完成。

        如果传播模式设置为默认,则对两个层都进行注释是没有意义的。

        【讨论】:

          【解决方案6】:

          我将@Transactional 放在@Service 层上并设置rollbackFor 任何异常和readOnly 以进一步优化事务。

          默认情况下,@Transactional 只会查找 RuntimeException(未检查异常),通过将回滚设置为 Exception.class(已检查异常),它将回滚任何异常。

          @Transactional(readOnly = false, rollbackFor = Exception.class)
          

          Checked vs. Unchecked Exceptions

          【讨论】:

            【解决方案7】:

            对于数据库级别的事务

            我主要在 DAO 中使用 @Transactional 只是在方法级别,因此配置可以专门针对方法/使用默认值(必需)

            1. DAO 获取数据的方法(选择 ..) - 不需要 @Transactional 这可能会导致一些开销,因为 需要执行的事务拦截器/和 AOP 代理 好吧。

            2. DAO 的插入/更新方法将得到@Transactional

            transctional 上非常好的博客

            对于应用程序级别 -
            我正在使用事务性的业务逻辑我希望能够在出现意外错误的情况下回滚

            @Transactional(rollbackFor={MyApplicationException.class})
            public void myMethod(){
            
                try {    
                    //service logic here     
                } catch(Throwable e) {
            
                    log.error(e)
                    throw new MyApplicationException(..);
                }
            }
            

            【讨论】:

            • +1 篇关于Transactional 的非常好的文章Java
            【解决方案8】:

            或者注释两个“层​​”是否有意义? - 注释服务层和 dao 层是否有意义 - 如果要确保 DAO 方法是总是从服务层调用(传播),在 DAO 中传播“强制”。这将为从 UI 层(或控制器)调用 DAO 方法提供一些限制。此外 - 特别是在对 DAO 层进行单元测试时 - 对 DAO 进行注释也将确保对其进行事务功能测试。

            【讨论】:

            • 这样做不会导致嵌套事务吗?以及随之而来的所有微妙问题?
            • 不,JPA 中没有嵌套事务。将它们放在两者中会非常好 - 如果您在点击 DAO 时已经在交易中,那么该交易将继续进行。
            • 如果使用propagation=Propagation.REQUIRES_NEW,可能会发生嵌套事务。否则对于大多数情况,包括 propogation=mandatory,DAO 将只参与服务层启动的现有事务。
            【解决方案9】:

            另外,Spring 建议只在具体类上使用注解,而不是在接口上使用。

            http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

            【讨论】:

              【解决方案10】:

              通常应该在服务层放置一个事务。

              但如前所述,操作的原子性告诉我们在哪里需要注释。因此,如果您使用像 Hibernate 这样的框架,其中对对象的单个“保存/更新/删除/...修改”操作有可能修改多个表中的几行(因为通过对象图的级联),当然这个特定的 DAO 方法也应该有事务管理。

              【讨论】:

                【解决方案11】:

                @Transactional注解应该放在所有不可分割的操作周围。 使用@Transactional 事务传播是自动处理的。在这种情况下,如果当前方法调用了另一个方法,那么该方法将可以选择加入正在进行的事务。

                让我们举个例子:

                我们有 2 个模型,即 CountryCityCountryCity 模型的关系映射就像一个 Country 可以有多个城市,所以映射就像,

                @OneToMany(fetch = FetchType.LAZY, mappedBy="country")
                private Set<City> cities;
                

                Here Country 映射到多个城市并获取它们Lazily。因此,当我们从数据库中检索 Country 对象时,@Transactinal 的角色出现了,然后我们将获取 Country 对象的所有数据,但不会获取城市集,因为我们正在获取城市 LAZILY

                //Without @Transactional
                public Country getCountry(){
                   Country country = countryRepository.getCountry();
                   //After getting Country Object connection between countryRepository and database is Closed 
                }
                

                当我们想从国家对象访问城市集合时,我们将在该集合中获得空值,因为集合的对象仅创建了这个集合并没有用那里的数据初始化以获取我们使用的集合的值@Transactional 即,

                //with @Transactional
                @Transactional
                public Country getCountry(){
                   Country country = countryRepository.getCountry();
                   //below when we initialize cities using object country so that directly communicate with database and retrieve all cities from database this happens just because of @Transactinal
                   Object object = country.getCities().size();   
                }
                

                所以基本上@Transactional 是服务可以在单个事务中进行多次调用而无需关闭与端点的连接。

                【讨论】:

                • 非常有用,谢谢!正是我要找的,对 @Transactional 的真正含义的解释
                【解决方案12】:

                最好放在服务层!这在我昨天看到的一篇文章中有清楚的解释!这是the link,您可以查看!

                【讨论】:

                  【解决方案13】:

                  首先让我们定义我们必须在哪里使用事务

                  我认为正确的答案是 - 当我们需要确保一系列动作将作为一个原子操作一起完成,或者即使其中一个动作失败也不会进行任何更改。

                  将业务逻辑放入服务中是众所周知的做法。所以服务方法可能包含不同的动作,这些动作必须作为一个单一的逻辑工作单元来执行。如果是这样 - 那么这种方法必须标记为 Transactional。当然,并不是每个方法都需要这样的限制,所以你不需要将整个服务标记为事务性

                  还有更多 - 不要忘记考虑到 @Transactional 显然可能会降低方法性能。 为了了解全局,您必须了解事务隔离级别。了解这一点可能会帮助您避免在不必要的地方使用 @Transactional

                  【讨论】:

                    【解决方案14】:

                    @Transactional 应该用于服务层,因为它包含业务逻辑。 DAO 层通常只有数据库 CRUD 操作。

                    // the service class that we want to make transactional
                    @Transactional
                    public class DefaultFooService implements FooService {
                    
                        Foo getFoo(String fooName);
                    
                        Foo getFoo(String fooName, String barName);
                    
                        void insertFoo(Foo foo);
                    
                        void updateFoo(Foo foo);
                    }
                    

                    春季文档:https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html

                    【讨论】:

                      【解决方案15】:

                      服务层是添加 @Transactional 注释的最佳位置,因为这里存在大多数业务逻辑,它包含详细级别的用例行为。

                      假设我们将它添加到 DAO 并从服务中调用 2 个 DAO 类,一个失败,另一个成功,在这种情况下,如果 @Transactional 不在服务上,一个 DB 将提交,另一个将回滚。

                      因此我的建议是明智地使用此注释并仅在服务层使用。

                      Github project- java-algos

                      【讨论】:

                      • ObjectOptimisticLockingFailureException 等异常仅在事务完成后发生。如果您有单独的线程用于邮件服务等其他操作,则此设计完全失败。我们现在正在受苦。唯一剩下的解决方案是 AOP。
                      【解决方案16】:

                      最好将 @Transactional 保留在 DAO 和服务层之间的单独中间层中。 由于回滚非常重要,您可以将所有数据库操作放在中间层,并将业务逻辑写入服务层。中间层将与您的 DAO 层交互。

                      这将在许多情况下为您提供帮助,例如 ObjectOptimisticLockingFailureException - 此异常仅在您的事务结束后发生。所以,你不能在中间层捕捉它,但你现在可以在你的服务层捕捉它。如果您在服务层中有@Transactional,这将是不可能的。虽然你可以在 Controller 中捕获,但 Controller 应该尽可能干净。

                      如果您在完成所有保存、删除和更新选项后在单独的线程中发送邮件或短信,您可以在中间层中的事务完成后在服务中执行此操作。同样,如果您在服务层中提到@Transactional,即使您的交易失败,您的邮件也会继续发送。

                      因此,拥有一个中间 @Transaction 层将有助于使您的代码更好且更易于处理。否则, 如果在 DAO 层使用,可能无法回滚所有操作。 如果你在服务层使用,在某些情况下你可能不得不使用AOP(Aspect Oriented Programming)。

                      【讨论】:

                        【解决方案17】:

                        理想情况下,服务层(管理器)代表您的业务逻辑,因此应该使用@Transactional 进行注释。服务层可能会调用不同的 DAO 来执行数据库操作。让我们假设一个服务方法中有 N 个 DAO 操作的情况。如果您的第一个 DAO 操作失败,其他操作可能仍然通过,您最终会出现不一致的数据库状态。注释服务层可以帮助您避免这种情况。

                        【讨论】:

                          【解决方案18】:

                          我更喜欢在方法级别的服务层上使用@Transactional

                          【讨论】:

                            【解决方案19】:

                            @Transactional用于服务层,通过控制器层(@Controller)和服务层调用DAO层(@Repository)调用,即数据库相关操作。

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 2015-01-07
                              • 1970-01-01
                              • 2020-08-23
                              • 1970-01-01
                              • 2012-01-01
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多