spring事物问题,在一个方法里需要更新某张表的数据并使用最新的这个数据,使用事物注解应该如何写?

spring事物问题,在一个方法里需要更新某张表的数据并使用最新的这个数据,使用事物注解应该如何写?

阅读 3.7k
2 个回答

这应该是数据库事物隔离级别的问题,数据库默认的隔离级别是可重复读, 也就是说事物内的操作在本事物中是可见的,但是在提交之前,该操作不对其他事物产生影响,其他事物看不见这个操作。
数据库的四种隔离级别,下面用mysql数据库演示一下:

create table test (id int not null,name vachar(20),primary key(id));
insert into test (1,'AA');
insert into test (2,'BB');
insert into test (3,'CC');
insert into test (4,'DD');
insert into test (5,'EE');
  • READ UNCOMMITTED未提交读,事务中的修改,即使没有提交,对其他事务也都是可见的,事物1的更新操作在未提交时,事物2是可见的。这种隔离级别下可能会有脏读、不可重复读、幻读等情况。
//事物1的操作
//查看当前会话隔离级别
select @@tx_isolation;
//修改当前会话的隔离级别
set session transaction isolation level READ UNCOMMITTED;
//关闭自动提交
set autocommit = off;
//开始事物
begin;
// 查看当前事物中test表的视图
select * from test;
// 更新操作
update test set name = 'AAAAA' where id = 1;
//再次查看当前事物中test表的视图,发现更新对事物本身可见,
//在其他事物中(注意其他事物不能是可重复读级别)对这个为提交的更新依然是可见的。
select * from test;
//回滚事物,回滚后其他事物就看不到更新的操作了
rollback;
  • READ COMMITTED(已提交读/不可重复读):事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。

还是上面的例子,事物1的更新操作在提交前,事物2是看不到的,提交之后才能看到。这种隔离级别下可能会有不可重复读、幻读等情况。

  • REPEATABLE READ(可重复读):同一事务中多次读取同样的记录结果是一致的,MySQL默认的隔离级别。这种隔离级别下可能会有幻读情况。
  • SERIALIZABLE(序列化):事务串行执行,读写加锁。

以上四种隔离级别体现了事物之间的相互影响程度。但是在同一个事物中,它的更新操作都是对本身可见的。不存在在同一个事物中,上面更新了,下面看不到的情况。除非你在使用spring事物的传播特性时,在事物中又开启了另外一个事物,导致在两个事物中,一个事物的更新另外一个事物看不到。

补充:
我用SpringBoot写了一个例子,在事物传播属性和隔离级别的影响下,有可能出现前面的更新,后面看不到的情况,下面是代码,注意看注释内容。
SpringBoot pom.xml文件加入以下依赖:

         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!-- Use MySQL Connector-J -->

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

主类:

@EnableAutoConfiguration
@ComponentScan
public class SpringBootApplication {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(SpringBootApplication.class);
        Map<String, Object> defaultMap = new HashMap<String, Object>();
        //设置数据库连接
        defaultMap.put("spring.datasource.driverClassName", "com.mysql.jdbc.Driver");
        defaultMap.put("spring.datasource.url", "jdbc:mysql://192.168.1.105:3306/sakila?useUnicode=true&characterEncoding=utf-8");
        defaultMap.put("spring.datasource.username", "root");
        defaultMap.put("spring.datasource.password", "root");
        application.setDefaultProperties(defaultMap);
        ApplicationContext context = application.run(args);

        //调用事物1
        SpringTransaction_1 transaction = context.getBean("springTransaction_1", SpringTransaction_1.class);
        transaction.transaction_1();

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("JVM正在关闭.....");
        }));
    }
}

事物1:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

@Service
public class SpringTransaction_1 {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    SpringTransaction_2 springTransaction_2;

    /**
     * 默认传播属性,当前没有事物就新建一个事物,如果有事物则加入当前事物
     *
     * 注意:transaction_1和transaction_2不要放在一个类里面,否则transaction_2
     * 就不会被AOP代理,不会创建新事物
     */
    @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
    public void transaction_1(){
        List<Map<String,Object>> list = jdbcTemplate.queryForList("select * from test");
        System.out.println("initial view in transaction_1");
        list.forEach(System.out::println);
        springTransaction_2.transaction_2();
        list = jdbcTemplate.queryForList("select * from test");
        System.out.println("view in transaction_1 after transaction_2");
        list.forEach(System.out::println);
    }

}

事物2:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

@Service
public class SpringTransaction_2 {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 如果用@Transactional 默认REQUIRED传播机制,和Transaction1在同一个事物中,
     * 由于REPEATABLE_READ隔离级别,它的更新会影响到transaction_1方法
     *
     * 这里使用REQUIRES_NEW无论何时都新建一个事物,由于REPEATABLE_READ隔离级别,
     * 此方法的更新,在transaction_1中不被察觉
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.REPEATABLE_READ)
    public void transaction_2(){
        List<Map<String,Object>> list = jdbcTemplate.queryForList("select * from test");
        System.out.println("initial view in transaction_2");
        list.forEach(System.out::println);
        System.out.println("update record in transaction_2");
        System.out.println("update test set name = 'AB' where id = 1");
        jdbcTemplate.update("update test set name = 'AB' where id = 1");
        System.out.println("view in transaction_2 after update");
        list = jdbcTemplate.queryForList("select * from test");
        list.forEach(System.out::println);
    }
}

输出:

transaction_1 begin
initial view in transaction_1
{id=1, name=AB}
transaction_2 begin
initial view in transaction_2
{id=1, name=AB}
update record in transaction_2
update test set name = 'AAAS' where id = 1
view in transaction_2 after update
{id=1, name=AAAS}
transaction_2 end
view in transaction_1 after transaction_2
//注意,transaction_2的更新,在transaction_1 中不可见,或者说transaction_1必须保持在本事物中的一致性即可
{id=1, name=AB}
transaction_1 end

完整代码放在github

//A方法的propagation为REQUIRED
public void A(){

 //调用B方法更新,B方法的propagation为REQUIRES_NEW
 B();
 //调用C方法使用最新数据,C方法的propagation为REQUIRES_NEW
 C();
}
试一下行不行
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题