Spring学习之事务Transaction的使用

零、事务管理器运行过程

通过两个事务方法来说明一下:

service1方法:
@Transactional(transactionManager="transactionManager1",propagation = Propagation.REQUIRED)
  public void m1(){
   
   
    this.jdbcTemplate.update("insert into user1(username) VALUES (?)","aa");
    service2.m2();
    }
service2方法:
@Transactional(transactionManager="transactionManager1",propagation = Propagation.REQUIRED)
  public void m2(){
   
   
    this.jdbcTemplate.update("insert into user1(username) VALUES (?)","ss");}

spring事务中有个resources的ThreadLocal,static修饰的,用来存放共享的资源

private static final ThreadLocal<Map<Object,Object>> resources = new NamedThreadLocal<>("Transactional resources");

下面具体看一下简化版的事务过程:

1.TransactionInterceptor拦截m1方法
2.获取m1方法的事务配置信息:事务管理器名称和事务传播行为
3.从spring容器中找到事务管理器,然后判断当前上下文有没有事务,这时候显然没有
4.创建一个事务
  //获取当前事务管理器的数据源
  DataSource datasource1 = transactionManager1.getDataSource();
  //获取当前数据源的连接
  Connection conn = datasource1.getConnection();
  //设置事务手动提交,开启事务
  conn.setAutoCommit(false)
  //将datasource1和conn存入map中
  map.put(datasource1,conn)
  //将map存入static的ThrealLocal中
  resources.set(map);
5.执行this.jdbcTemplate.update();
6.jdbcTemplate内部需要获取连接,过程如下:
  //先从上面的resources中获取map
  Map map = resources.get();
  //从map中获取连接,看有没有可用的连接
  Connection conn = map.get(jdbaTemplate.getDatasources());
  if(conn == null){
   
   
   //如果没有找到连接,就重新获取一个;
  	conn = jdbcTemplate.datasource.getConnection();
  }
7.执行db操作,进行插入
8.执行service2.m2()
9.m2方法上也有@Transactional, TransactionInterceptor拦截m2方法
10.同样获m2上的事务配置信息:事务管理器和传播行为
11.从spring中获取到事务管理器,transactionManager1和required,
   然后判断当前上下文有没有事务,发现当前是有事务的,m1的事务正在进行,所以m2就加入了
12.执行this.jdbcTemplate.update();
13.jdbcTemplate内部需要获取连接,过程如下:
  //先从上面的resources中获取map	
  Map map = resources.get();
  //从map中获取连接,看有没有可用的连接
  Connection conn = map.get(jdbaTemplate.getDatasources());
  if(conn == null){
   
   
   //如果没有找到连接,就重新获取一个;
  	conn = jdbcTemplate.datasource.getConnection();
  }
14.执行db操作,进行插入
15.最终TransactionInterceptor发现两个方法都执行完毕,没有异常,执行事务提交
//获取当前事务管理器的数据源
  DataSource datasource1 = transactionManager1.getDataSource();
  //先从上面的resources中获取map	
  Map map = resources.get();
   //从map中获取连接,看有没有可用的连接
  Connection conn = map.get(datasource1);
  //提交事务
  conn.commit();
  //管理链接
  conn.close();
16.清理ThreadLocal中的连接,通过resources.remove(datasource1)将连接移除
17.清理事务

0.0、事务管理器如何判断当前有事务

在这里插入图片描述

一、Spring中事务的使用方式

1. 编程式事务

通过硬编码的方式使用spring中提供的事务相关的类来控制事务

方式1:通过PlatformTransactionManager控制事务

步骤1:定义事务管理器PlatformTransactionManager
步骤2:定义事务属性TransactionDefinition
步骤3:开启事务
步骤4:执行业务操作
步骤5:提交 or 回滚
开启事务后,spring内部会执行一些操作

//有一个全局共享的threadLocal对象 resources
static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
//获取一个db的连接
DataSource datasource = platformTransactionManager.getDataSource();
Connection connection = datasource.getConnection();
//设置手动提交事务
connection.setAutoCommit(false);
Map<Object, Object> map = new HashMap<>();
map.put(datasource,connection);
resources.set(map);

将数据源datasource和connection映射起来放在了ThreadLocal中,ThreadLocal大家应该比较熟悉,用于在同一个线程中共享数据;后面我们可以通过resources这个ThreadLocal获取datasource其对应的connection对象。

为什么要扔进ThreadLocal ,原因是数据库实现事务是基于同一个数据库链接的,spring把他扔进ThreadLocal 就是为了保证同一个用事务注解的方法里面的所有sql执行时候拿来的数据库链接是同一个

  1. 准备maven依赖
<!-- JdbcTemplate需要的 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.3.RELEASE</version>
</dependency>
<!-- spring 事务支持 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.3.RELEASE</version>
</dependency>
  1. 编写配置类,配置数据源和JdbcTemplate
@Configuration
@ComponentScans(
        {
   
   @ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {
   
   @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
                @ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {
   
   @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
        }
)
public class MyConfig {
   
   
    @Bean
    public DruidDataSource druidDataSource(){
   
   
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("ronny@123456");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
   
   
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(druidDataSource);
        return jdbcTemplate;
    }
}
  1. 编写测试类
@Repository
public class UserDaoImpl implements UserDao {
   
   
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private DruidDataSource dataSource;

    public void update(User user) {
   
   
        //1.获取事务管理器,指定数据源
        PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
        //2.定义事务属性(传播特性,隔离级别,超时时间,是否制度,回滚)
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        //3.开启事务,返回事务状态
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        try {
   
   
            System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from user"));
            int resilt = jdbcTemplate.update("insert into user(userId, username, password)value(?,?,?)",
                    user.getUserId(), user.getUsername(), user.getPassword());
            System.out.println("新增行数"+resilt);
            //4.提交事务
            transactionManager.commit(transactionStatus);
        } catch (DataAccessException e) {
   
   
            //5.回滚事务
            transactionManager.rollback(transactionStatus);
        }
        System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from user"));
    }
}
  1. 运行输出
 @Test
    public void test(){
   
   
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        User user = new User(6,"ddd","1233121");
        userService.doUpdate(user);
    }

方式2:通过TransactionTemplate来控制事务

一、通过TransactionTemplate提供的方法执行业务操作主要有2个方法:(1).executeWithoutResult(Consumer action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作

transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
   
   
    @Override
    public void accept(TransactionStatus transactionStatus) {
   
   
        //执行业务操作
    }
});

(2). T execute(TransactionCallback action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作

Integer result = transactionTemplate.execute(new TransactionCallback<Integer>() {
   
   
    @Nullable
    @Override
    public Integer doInTransaction(TransactionStatus status) {
   
   
        return jdbcTemplate.update("insert into t_user (name) values (?)", "executeWithoutResult-3");
    }
});

二、调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。那么什么时候事务会回滚,有2种方式:
(1)在execute或者executeWithoutResult内部执行transactionStatus.setRollbackOnly();将事务状态标注为回滚状态,spring会自动让事务回滚
(2)execute方法或者executeWithoutResult方法内部抛出任意异常即可回滚
三、什么时候事务会提交?
方法没有异常 && 未调用过transactionStatus.setRollbackOnly();

案例:

  1. 配置DataSource和JdbcTemplate,TransactionTemplate,DataSourceTransactionManager
@Configuration
@ComponentScans(
        {
   
   @ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {
   
   @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
                @ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {
   
   @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
        }
)
public class MyConfig {
   
   

    @Bean
    public DruidDataSource druidDataSource(){
   
   
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("ronny@123456");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
   
   
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(druidDataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DruidDataSource druidDataSource){
   
   
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(druidDataSource);
        return transactionManager;
    }

    @Bean
    public TransactionTemplate transactionTemplate(DataSourceTransactionManager transactionManager){
   
   
        TransactionTemplate transactionTemplate = new TransactionTemplate();
        transactionTemplate.setTransactionManager(transactionManager);
        return transactionTemplate;
    }
}
  1. 测试
@Service("userService")
public class UserService {
   
   
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void bus1(){
   
   
        this.transactionTemplate.executeWithoutResult(transactionStatus -> {
   
   
            //先删除数据
            this.jdbcTemplate.update("delete from user");
            this.bus2();
        });
    }
    private void bus2() {
   
   
        this.transactionTemplate.executeWithoutResult(transactionStatus -> {
   
   
            this.jdbcTemplate.update("insert into user(userId, username, password) value (?,?,?)",7,"zzz","123");
            this.jdbcTemplate.update("insert into user(userId, username, password) value (?,?,?)",8,"pppp","123");
            this.jdbcTemplate.update("insert into user(userId, username, password) value (?,?,?)",9,"kkkk","123");
        });
    }
}

bus1中会先删除数据,然后调用bus2,此时bus1中的所有操作和bus2中的所有操作会被放在一个事务中执行,这是spring内部默认实现的,bus1中调用executeWithoutResult的时候,会开启一个事务,而内部又会调用bus2,而bus2内部也调用了executeWithoutResult,bus内部会先判断一下上线文环境中有没有事务,如果有就直接参与到已存在的事务中,刚好发现有bus1已开启的事务,所以就直接参与到bus1的事务中了,最终bus1和bus2会在一个事务中运行

2. 声明式事务

2.1 配置xml文件的方式

 <!--datasource-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="ronny@123456"/>
    </bean>
<!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg ref="dataSource"/>
    </bean>
    <!--配置事务传播特性-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add" propagation="REQUIRED"/>
            <tx:method name="delete" propagation="REQUIRED"/>
            <tx:method name="update" propagation="REQUIRED"/>
            <tx:method name="query" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一位不知名民工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值