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