有人可以通过现实世界的例子解释一下@Transactional
注解中隔离级别和传播行为参数是什么吗?
基本上,我应该在何时以及为什么要选择更改它们的默认值。
有人可以通过现实世界的例子解释一下@Transactional
注解中隔离级别和传播行为参数是什么吗?
基本上,我应该在何时以及为什么要选择更改它们的默认值。
好问题,虽然不容易回答。
定义事务之间的关系。常见选项:
REQUIRED
:代码总是在一个事务中运行。如果可用,创建一个新的事务或重用一个事务。REQUIRES_NEW
:代码总是在一个新的事务中运行。如果存在一个事务,则挂起当前事务。@Transactional
的默认值是 REQUIRED
,这通常是你想要的。
定义事务之间的数据契约。
ISOLATION_READ_UNCOMMITTED
: 允许脏读。ISOLATION_READ_COMMITTED
: 不允许脏读。ISOLATION_REPEATABLE_READ
: 如果在同一事务中读取两次某行,结果将始终相同。ISOLATION_SERIALIZABLE
: 按顺序执行所有事务。在多线程应用程序中,不同级别具有不同的性能特征。如果您了解“脏读”概念,您将能够选择一个好的选项。
默认值可能因数据库而异。例如,对于MariaDB,默认值为REPEATABLE READ
。
thread 1 thread 2
| |
write(x) |
| |
| read(x)
| |
rollback |
v v
value (x) is now dirty (incorrect)
ISOLATION_READ_COMMITTED
,它只允许您读取已被其他正在运行的事务提交的值,并与传播级别REQUIRED
相结合。如果您的应用有其他需求,那么您可以从这里开始工作。
provideService
例程时,总是会创建一个新的事务,并在离开时完成。public class FooService {
private Repository repo1;
private Repository repo2;
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void provideService() {
repo1.retrieveFoo();
repo2.retrieveFoo();
}
}
REQUIRED
,当进入例程时,如果事务已经打开,那么该事务将保持打开状态。
还要注意的是,rollback
的结果可能会有所不同,因为多个执行可能参与同一个事务。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:/fooService.xml")
public class FooServiceTests {
private @Autowired TransactionManager transactionManager;
private @Autowired FooService fooService;
@Test
public void testProvideService() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
fooService.provideService();
transactionManager.rollback(status);
// assert repository values are unchanged ...
}
传播级别为
REQUIRES_NEW
: 我们期望 fooService.provideService()
不会被回滚,因为它创建了自己的子事务。
REQUIRED
: 我们期望所有内容都被回滚,且后端存储不会发生变化。
PROPAGATION_REQUIRED = 0; 如果对于方法M1已经启动了DataSourceTransactionObject T1。如果另一个方法M2需要事务对象,则不会创建新的事务对象。同一对象T1用于M2。
PROPAGATION_MANDATORY = 2; 方法必须在事务内运行。如果没有正在进行的现有事务,则会抛出异常。
PROPAGATION_REQUIRES_NEW = 3; 如果已经为方法M1启动了DataSourceTransactionObject T1,并且它正在进行中(执行方法M1)。如果另一个方法M2开始执行,则T1将暂停以持续方法M2,并为M2创建新的DataSourceTransactionObject T2。M2在其自己的事务上下文中运行。
PROPAGATION_NOT_SUPPORTED = 4; 如果对于方法M1已经启动了DataSourceTransactionObject T1。如果同时运行另一个方法M2。则M2不应在事务上下文中运行。T1被暂停直到M2完成。
PROPAGATION_NEVER = 5; 没有任何方法在事务上下文中运行。
隔离级别: 它关乎在并发事务活动的影响下,一个事务可能会受到多个其他并发事务的影响。它支持跨多个表的数据保持一致性。它涉及在数据库中锁定行和/或表。
多个事务的问题
情景1。如果T1事务从由另一个并发事务T2写入的A1表中读取数据。如果在T2上进行回滚,则T1获取的数据是无效的。例如,a=2是原始数据。如果T1读取了由T2写入的a=1。如果T2回滚,那么a=1将在数据库中回滚到a=2。但是现在,T1具有a=1,但在数据库表中已更改为a=2。
情景2。如果T1事务从A1表中读取数据。如果另一个并发事务(T2)更新A1表上的数据。那么T1读取的数据与A1表中的数据不同。因为T2已经更新了A1表上的数据。例如,如果T1读取a=1并且T2更新a=2。然后a!= b。
情景3。如果T1事务从A1表中读取带有特定行数的数据。如果另一个并发事务(T2)在A1表上插入更多行。则T1读取的行数与A1表中的行数不同。
情景1称为脏读。
情景2称为不可重复读。
情景3称为幻影读。
因此,隔离级别是防止情景1、情景2、情景3发生的程度。 您可以通过实施锁定来获得完全的隔离级别。这样可以防止并发读取和写入相同数据。但它会影响性能。隔离级别取决于应用程序需要多少隔离。
ISOLATION_READ_UNCOMMITTED:允许读取尚未提交的更改。它受到情景1、情景2、情景3的影响。
ISOLATION_READ_COMMITTED:允许从已提交的并发事务中读取。它可能会受
public class TransactionBehaviour {
// set is either using xml Or annotation
DataSourceTransactionManager manager=new DataSourceTransactionManager();
SimpleTransactionStatus status=new SimpleTransactionStatus();
;
public void beginTransaction()
{
DefaultTransactionDefinition Def = new DefaultTransactionDefinition();
// overwrite default PROPAGATION_REQUIRED and ISOLATION_DEFAULT
// set is either using xml Or annotation
manager.setPropagationBehavior(XX);
manager.setIsolationLevelName(XX);
status = manager.getTransaction(Def);
}
public void commitTransaction()
{
if(status.isCompleted()){
manager.commit(status);
}
}
public void rollbackTransaction()
{
if(!status.isCompleted()){
manager.rollback(status);
}
}
Main method{
beginTransaction()
M1();
If error(){
rollbackTransaction()
}
commitTransaction();
}
}
您可以使用不同的隔离级别和传播行为进行调试并查看结果。
其他答案已经详细解释了每个参数;然而,你要求一个现实世界的例子,这里有一个可以阐明不同传播选项目的的例子:
假设你负责实现一个注册服务,在该服务中向用户发送确认电子邮件。你想出了两个服务对象,一个用于注册用户,另一个用于发送电子邮件,后者在前者内部调用。例如像这样:
/* Sign Up service */
@Service
@Transactional(Propagation=REQUIRED)
class SignUpService{
...
void SignUp(User user){
...
emailService.sendMail(User);
}
}
/* E-Mail Service */
@Service
@Transactional(Propagation=REQUIRES_NEW)
class EmailService{
...
void sendMail(User user){
try{
... // Trying to send the e-mail
}catch( Exception)
}
}
您可能已经注意到第二项服务属于传播类型 REQUIRES_NEW ,而且很有可能会抛出异常(SMTP服务器关闭,无效的电子邮件或其他原因)。 您可能不希望整个过程回滚,例如从数据库中删除用户信息或其他事情; 因此,您将在单独的事务中调用第二项服务。
回到我们的例子,这次您关心数据库安全性,因此按以下方式定义DAO类:/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
// some CRUD methods
}
这意味着每当创建一个DAO对象,也就是潜在访问数据库时,我们需要确保该调用是从我们的一个服务内部发出的,这意味着必须存在一个活动事务;否则,将会抛出异常。因此传播类型为MANDATORY。
隔离级别定义了一个事务对数据仓库所做的更改如何影响其他同时并发的事务,以及更改后的数据何时以及如何对其他事务可用。当我们使用Spring框架定义一个事务时,我们也能够配置该事务在哪个隔离级别下执行。
@Transactional(isolation=Isolation.READ_COMMITTED)
public void someTransactionalMethod(Object obj) {
}
READ_UNCOMMITTED隔离级别表示一个事务可以读取其他事务未提交的数据。
READ_COMMITTED隔离级别表示一个事务不能读取其他事务未提交的数据。
REPEATABLE_READ隔离级别表示如果一个事务多次从数据库中读取同一条记录,那么每次读取的结果必须相同。
SERIALIZABLE隔离级别是所有隔离级别中最严格的。事务在所有级别(读取级别、范围级别和写入级别)上都被执行锁定,因此它们似乎是按照序列化方式执行的。
传播性是指决定业务方法应如何封装在逻辑或物理事务中的能力。
Spring REQUIRED行为意味着如果当前bean方法执行上下文中已经存在一个打开的事务,则将使用相同的事务。
REQUIRES_NEW行为意味着容器始终会创建一个新的物理事务。
NESTED行为使嵌套的Spring事务使用相同的物理事务,但在嵌套调用之间设置保存点,这样内部事务也可以独立于外部事务回滚。
MANDATORY行为表示必须已经存在一个打开的事务。如果不存在,则容器将抛出异常。
NEVER行为表示必须不存在已经打开的事务。如果存在一个事务,容器将抛出异常。
NOT_SUPPORTED行为将在任何事务范围之外执行。如果已经存在打开的事务,则会暂停它。
SUPPORTS行为将在已经存在打开的事务范围内执行。如果没有已经打开的事务,该方法仍将以非事务方式执行。
TransactionDefinition
接口定义了符合Spring规范的事务属性。@Transactional
注解描述了方法或类上的事务属性。@Autowired
private TestDAO testDAO;
@Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
public void someTransactionalMethod(User user) {
// Interact with testDAO
}
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| value | Propagation | Description |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| -1 | TIMEOUT_DEFAULT | Use the default timeout of the underlying transaction system, or none if timeouts are not supported. |
| 0 | PROPAGATION_REQUIRED | Support a current transaction; create a new one if none exists. |
| 1 | PROPAGATION_SUPPORTS | Support a current transaction; execute non-transactionally if none exists. |
| 2 | PROPAGATION_MANDATORY | Support a current transaction; throw an exception if no current transaction exists. |
| 3 | PROPAGATION_REQUIRES_NEW | Create a new transaction, suspending the current transaction if one exists. |
| 4 | PROPAGATION_NOT_SUPPORTED | Do not support a current transaction; rather always execute non-transactionally. |
| 5 | PROPAGATION_NEVER | Do not support a current transaction; throw an exception if a current transaction exists. |
| 6 | PROPAGATION_NESTED | Execute within a nested transaction if a current transaction exists. |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
+---------------------------+-------------------+-------------+-------------+------------------------+
| Isolation Level Mode | Read | Insert | Update | Lock Scope |
+---------------------------+-------------------+-------------+-------------+------------------------+
| READ_UNCOMMITTED (Low) | uncommitted data | Allowed | Allowed | No Lock |
| READ_COMMITTED (Default) | committed data | Allowed | Allowed | Lock on Committed data |
| REPEATABLE_READ | committed data | Allowed | Not Allowed | Lock on block of table |
| SERIALIZABLE (High) | committed data | Not Allowed | Not Allowed | Lock on full table |
+---------------------------+-------------------+-------------+-------------+------------------------+
+---------------------------+--------------+----------------------+----------------+
| Isolation Level Mode | Dirty reads | Non-repeatable reads | Phantoms reads |
+---------------------------+--------------+----------------------+----------------+
| READ_UNCOMMITTED | X | X | X |
| READ_COMMITTED (Default) | solves | X | X |
| REPEATABLE_READ | solves | solves | X |
| SERIALIZABLE | solves | solves | solves |
+---------------------------+--------------+----------------------+----------------+
在大多数情况下,你不应该使用 Read Uncommited
,因为它并不真正符合 ACID
标准。作为默认的起点,Read Commmited
是一个不错的选择。只有在报告、数据滚动或聚合等场景下,才需要使用 Repeatable Read
。请注意,许多数据库(包括 postgres)实际上并不支持 Repeatable Read,你必须使用 Serializable
。与 Java 中的 synchronized
类似,Serializable
对于那些你知道完全独立于其他任何事情都必须发生的事情非常有用。Serializable 与 REQUIRES_NEW
传播方式相辅相成。
我对所有运行 UPDATE 或 DELETE 查询以及 "service" 层函数使用 REQUIRES
。对于只运行 SELECT 的 DAO 层函数,我使用 SUPPORTS
,如果已经启动了事务(即从服务函数调用),则会参与到该事务中。
事务隔离和事务传播虽然相关,但它们是两个截然不同的概念。在这两种情况下,默认值都在客户端边界组件上通过使用 声明式事务管理 或者 编程式事务管理 进行自定义。每个隔离级别和传播属性的详细信息可以在下面的参考链接中找到。
对于运行中的两个或多个事务/连接到数据库的情况,一个事务中查询所做的更改何时会影响到另一个事务中的查询。它也涉及对用于将此事务中的更改与其他事务隔离开来的数据库记录锁定的类型。通常由参与事务的数据库/资源实现。
.
在企业应用程序中,针对任何给定的请求/处理,都会涉及许多组件来完成工作。其中一些组件标记了事务的边界(开始/结束)将在各自的组件及其子组件中使用。对于这些组件的事务边界,事务传播指定了相应组件是否参与事务以及如果调用组件已经创建/启动了事务,则会发生什么,或者没有事务。这与Java EE事务属性相同。通常由客户端事务/连接管理器实现。
参考:
我已经运行了outerMethod
,method_1
和method_2
并使用不同的传播模式。
以下是不同传播模式的输出。
@Transactional
@Override
public void outerMethod() {
customerProfileDAO.method_1();
iWorkflowDetailDao.method_2();
}
@Transactional(propagation=Propagation.MANDATORY)
public void method_1() {
Session session = null;
try {
session = getSession();
Temp entity = new Temp(0l, "XXX");
session.save(entity);
System.out.println("Method - 1 Id "+entity.getId());
} finally {
if (session != null && session.isOpen()) {
}
}
}
@Transactional()
@Override
public void method_2() {
Session session = null;
try {
session = getSession();
Temp entity = new Temp(0l, "CCC");
session.save(entity);
int i = 1/0;
System.out.println("Method - 2 Id "+entity.getId());
} finally {
if (session != null && session.isOpen()) {
}
}
}
@Transactional(readOnly = true)
public class Banking_CustomerService implements CustomerService {
public Customer getDetail(String customername) {
// do something
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateCustomer(Customer customer) {
// do something
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public EventMessage<ModificaOperativitaRapporto> activate(EventMessage<ModificaOperativitaRapporto> eventMessage) {
//here some transaction related code
}
你也可以使用这个东西:
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
sessionFactory.getCurrentTransaction()
被添加后,就不再需要运行HibernateTemplate
来管理事务了。我已经将其删除 :) - Johan Sjöberg