有人能通过真实的例子解释@Transactional注释中的隔离和传播参数是用于什么吗?

基本上,我应该在什么时候以及为什么选择更改它们的默认值。


当前回答

其他答案对每个参数都给出了足够的解释;但是,您要求的是一个真实世界的示例,下面是一个阐明不同传播选项的目的的示例:

Suppose you're in charge of implementing a

注册服务

in which a confirmation e-mail is sent to the user. You come up with two service objects, one for

招收

the user and one for

发送

e-mails, which the latter is called inside the first one. For example something like this:
/* 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服务器宕机、无效电子邮件或其他原因)。你可能不希望整个过程回滚,比如从数据库中删除用户信息或其他东西;因此,在单独的事务中调用第二个服务。

Back to our example, this time you are concerned about the database security, so you define your DAO classes this way:
/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
 // some CRUD methods
}

这意味着无论何时创建一个DAO对象,以及因此对DB的潜在访问,我们都需要确保调用是从我们的一个服务内部发出的,这意味着应该存在一个活动事务;否则会出现异常。因此,传播类型为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行为将在事务范围内执行。如果没有已经打开的事务,该方法将以非事务的方式执行。

我已经用不同的传播模式运行了outerMethod, method_1和method_2。

下面是不同传播模式的输出。

外部方法

    @Transactional
    @Override
    public void outerMethod() {
        customerProfileDAO.method_1();
        iWorkflowDetailDao.method_2();
    }

Method_1

    @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()) {

            }
        }
    }

Method_2

    @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()) {

            }
        }
    }

OuterMethod -没有事务 传播。mandatory) - Method_2 -仅事务注释 method_1将抛出不存在事务的异常


OuterMethod -没有事务 Method_1 -仅事务注释 方法2 -传播。mandatory) 输出:method_2将抛出不存在事务的异常 输出:method_1将保存数据库中的记录。


OuterMethod -带有事务 Method_1 -仅事务注释 方法2 -传播。mandatory) 输出:method_2将记录保存在数据库中。 输出:method_1将保存数据库中的记录。 方法1和方法2都使用了Main Outer现有事务


OuterMethod -带有事务 传播。mandatory) Method_2 -仅事务注释并抛出异常 输出:没有记录保存在数据库中意味着回滚完成。


OuterMethod -带有事务 方法1 -传播。requires_new) Method_2 - Propagation.REQUIRES_NEW)并抛出1/0异常 输出:method_2将抛出异常,因此method_2记录不被保存。 输出:method_1将保存数据库中的记录。 输出:method_1没有回滚

事务隔离和事务传播虽然相关,但显然是两个完全不同的概念。在这两种情况下,通过使用声明式事务管理或编程式事务管理在客户端边界组件上自定义默认值。每个隔离级别和传播属性的详细信息可以在下面的参考链接中找到。

事务隔离

对于给定的两个或多个正在运行的事务/到数据库的连接,一个事务中的查询所做的更改如何以及何时对另一个事务中的查询产生影响/可见。它还涉及到将使用哪种数据库记录锁定将此事务中的更改与其他事务隔离,反之亦然。这通常是由参与事务的数据库/资源实现的。

.

事务传播

In an enterprise application for any given request/processing there are many components that are involved to get the job done. Some of this components mark the boundaries (start/end) of a transaction that will be used in respective component and it's sub components. For this transactional boundary of components, Transaction Propogation specifies if respective component will or will not participate in transaction and what happens if calling component already has or does not have a transaction already created/started. This is same as Java EE Transaction Attributes. This is typically implemented by the client transaction/connection manager.

参考:

Spring事务管理 Wiki事务隔离(数据库系统) 关于事务隔离级别的Oracle Java EE事务属性(传播) Spring框架事务传播

其他答案对每个参数都给出了足够的解释;但是,您要求的是一个真实世界的示例,下面是一个阐明不同传播选项的目的的示例:

Suppose you're in charge of implementing a

注册服务

in which a confirmation e-mail is sent to the user. You come up with two service objects, one for

招收

the user and one for

发送

e-mails, which the latter is called inside the first one. For example something like this:
/* 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服务器宕机、无效电子邮件或其他原因)。你可能不希望整个过程回滚,比如从数据库中删除用户信息或其他东西;因此,在单独的事务中调用第二个服务。

Back to our example, this time you are concerned about the database security, so you define your DAO classes this way:
/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
 // some CRUD methods
}

这意味着无论何时创建一个DAO对象,以及因此对DB的潜在访问,我们都需要确保调用是从我们的一个服务内部发出的,这意味着应该存在一个活动事务;否则会出现异常。因此,传播类型为MANDATORY。

我们可以为此添加:

@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
    }
}