您应该将 @Transactional
放在 DAO
类和/或其方法中,还是最好对使用 DAO 对象调用的服务类进行注释?或者注释两个“层”是否有意义?
我认为事务属于服务层。它了解工作单元和用例。如果您将多个 DAO 注入需要在单个事务中协同工作的服务中,那么这是正确的答案。
一般来说,我同意其他人的观点,即事务通常在服务级别启动(当然取决于您需要的粒度)。
但是,与此同时,我也开始将 @Transactional(propagation = Propagation.MANDATORY)
添加到我的 DAO 层(以及其他不允许启动事务但需要现有事务的层),因为它更容易检测您忘记启动事务的错误调用者(例如服务)。如果您的 DAO 使用强制传播进行注释,您将收到一个异常,指出调用该方法时没有活动事务。
我还有一个集成测试,我检查所有 bean(bean 后处理器)是否有此注释,如果在不属于服务层的 bean 中存在具有传播以外的传播的 @Transactional
注释,则失败。这样我确保我们不会在错误的层上启动事务。
@Transactional
放在服务实现类上,我应该把 @Transactional(propagation = MANDATORY)
放在 DAO(存储库)类实现上?
Transactional Annotations 应该放在所有不可分割的操作周围。
例如,您的呼叫是“更改密码”。这包括两个操作
更改密码。审核更改。通过电子邮件向客户发送密码已更改的电子邮件。
那么在上述情况下,如果审核失败,那么密码更改是否也应该失败?如果是这样,那么事务应该在 1 和 2 左右(在服务层也是如此)。如果电子邮件失败(可能应该对此进行某种故障保护,因此不会失败),那么它是否应该回滚更改密码和审核?
在决定将 @Transactional
放在何处时,您需要提出这些问题。
传统 Spring 架构的正确答案是将事务语义放在服务类上,原因其他人已经描述过。
Spring 的一个新兴趋势是向 domain-driven design (DDD) 发展。 Spring Roo 很好地体现了这一趋势。这个想法是使域对象 POJO 比典型的 Spring 架构(通常是 anemic)要多很多 richer,特别是将事务和持久性语义放在域对象本身上。在只需要简单的 CRUD 操作的情况下,Web 控制器直接在域对象 POJO 上操作(它们在此上下文中作为实体运行),并且没有服务层。如果域对象之间需要某种协调,您可以让服务 bean 处理它,按照传统使用 @Transaction
。您可以将域对象上的事务传播设置为类似 REQUIRED
的内容,以便域对象使用任何现有事务,例如在服务 bean 上启动的事务。
从技术上讲,这种技术利用了 AspectJ 和 <context:spring-configured />
。 Roo 使用 AspectJ 类型间定义将实体语义(事务和持久性)与域对象内容(基本上是字段和业务方法)分开。
正常情况是在服务层级别上进行注释,但这实际上取决于您的要求。
在服务层上进行注释将导致比在 DAO 级别上进行注释更长的事务。取决于可以解决问题的事务隔离级别,因为并发事务不会看到彼此的更改,例如。可重复阅读。
在 DAO 上注释将使事务尽可能短,缺点是您的服务层公开的功能不会在单个(可回滚)事务中完成。
如果将传播模式设置为默认值,则对两个层都进行注释是没有意义的。
我将 @Transactional
放在 @Service
层上并设置 rollbackFor
任何异常和 readOnly
以进一步优化事务。
默认情况下,@Transactional
将仅查找 RuntimeException
(未检查的异常),通过将回滚设置为 Exception.class
(已检查的异常),它将回滚任何异常。
@Transactional(readOnly = false, rollbackFor = Exception.class)
请参阅Checked vs. Unchecked Exceptions。
对于数据库级别的事务
大多数情况下,我在 DAO 中仅在方法级别使用 @Transactional
,因此配置可以专门针对方法/使用默认值(必需)
DAO 获取数据的方法(select ..) - 不需要 @Transactional 这可能会导致一些开销,因为事务拦截器/和 AOP 代理也需要执行。 DAO 的插入/更新方法将获得 @Transactional
transctional 上非常好的博客
对于应用程序级别-我正在使用事务性的业务逻辑我希望能够在出现意外错误的情况下回滚
@Transactional(rollbackFor={MyApplicationException.class})
public void myMethod(){
try {
//service logic here
} catch(Throwable e) {
log.error(e)
throw new MyApplicationException(..);
}
}
Java
中 +1 关于 Transactional
的非常好的文章
或者注释两个“层”是否有意义? - 注释服务层和 dao 层是否有意义 - 如果要确保始终从服务层调用(传播)DAO 方法,并且在 DAO 中传播“强制性”。这将为从 UI 层(或控制器)调用 DAO 方法提供一些限制。此外 - 特别是在对 DAO 层进行单元测试时 - 对 DAO 进行注释也将确保对其进行事务功能测试。
propagation=Propagation.REQUIRES_NEW
,可能会发生嵌套事务。否则对于大多数情况,包括propogation=mandatory,DAO 将只参与服务层启动的现有事务。
此外,Spring 建议仅在具体类而不是接口上使用注解。
http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html
@Transactional
应在所有不可分割的操作周围放置注释。使用 @Transactional
事务传播是自动处理的。在这种情况下,如果当前方法调用另一个方法,则该方法将具有加入正在进行的事务的选项。
所以让我们举个例子:
我们有 2 个模型,即 Country
和 City
。 Country
和 City
模型的关系映射就像一个 Country
可以有多个城市,所以映射就像,
@OneToMany(fetch = FetchType.LAZY, mappedBy="country")
private Set<City> cities;
此处 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
真正含义的解释
通常,应该将事务放在服务层。
但如前所述,操作的原子性告诉我们在哪里需要注释。因此,如果您使用像 Hibernate 这样的框架,其中对对象的单个“保存/更新/删除/...修改”操作有可能修改多个表中的几行(因为通过对象图的级联),当然,还应该对这个特定的 DAO 方法进行事务管理。
服务层是添加 @Transactional
注释的最佳位置,因为这里存在的大多数业务逻辑,它包含详细级别的用例行为。
假设我们将它添加到 DAO 并从服务中调用 2 个 DAO 类,一个失败,另一个成功,在这种情况下,如果 @Transactional
不在服务上,一个 DB 将提交,另一个将回滚。
因此,我的建议是明智地使用此注释并仅在服务层使用。
首先让我们定义我们必须在哪里使用事务?
我认为正确的答案是 - 当我们需要确保一系列动作将作为一个原子操作一起完成,或者即使其中一个动作失败也不会进行任何更改。
将业务逻辑放入服务中是众所周知的做法。因此服务方法可能包含不同的操作,这些操作必须作为单个逻辑工作单元来执行。如果是这样 - 那么这种方法必须标记为事务性的。当然,并非每种方法都需要这种限制,因此您不需要将整个服务标记为事务性。
甚至更多 - 不要忘记考虑到 @Transactional 显然可能会降低方法性能。为了了解全局,您必须了解事务隔离级别。知道这可能会帮助您避免在不一定需要的地方使用@Transactional。
https://i.stack.imgur.com/WlLfh.png
@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
最好将@Transactional 保留在DAO 和服务层之间的单独中间层中。由于回滚非常重要,您可以将所有数据库操作放在中间层,并将业务逻辑写入服务层。中间层将与您的 DAO 层交互。
这将在许多情况下为您提供帮助,例如 ObjectOptimisticLockingFailureException - 此异常仅在您的事务结束后发生。所以,你不能在中间层捕捉它,但你现在可以在你的服务层捕捉它。如果您在服务层中有@Transactional,这将是不可能的。尽管您可以在 Controller 中捕获,但 Controller 应该尽可能干净。
如果您在完成所有保存、删除和更新选项后在单独的线程中发送邮件或短信,您可以在中间层完成事务后在服务中执行此操作。同样,如果您在服务层中提及@Transactional,即使您的交易失败,您的邮件也会继续发送。
因此,拥有一个中间 @Transaction 层将有助于使您的代码更好且易于处理。否则,如果在 DAO 层使用,可能无法回滚所有操作。如果你在服务层使用,在某些情况下你可能不得不使用 AOP(Aspect Oriented Programming)。
理想情况下,服务层(管理器)代表您的业务逻辑,因此应使用 @Transactional
进行注释。服务层可能会调用不同的 DAO 来执行数据库操作。让我们假设一个服务方法中有 N 个 DAO 操作的情况。如果您的第一个 DAO 操作失败,其他操作可能仍然通过,您最终会出现不一致的数据库状态。注释服务层可以使您免于这种情况。
我更喜欢在方法级别的服务层上使用 @Transactional
。
@Transactional
用于服务层,通过控制器层(@Controller
)和服务层调用DAO层(@Repository
)调用,即数据库相关操作。