spring基础知识 (24):认识事务并配置一个JDBC事务管理

认识事务

  • 事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.
  • 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用
  • 事务的四个关键属性(ACID)
    1. 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
    2. 一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
    3. 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
    4. 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.

比如说一次转账流程:

  • A给B转账100元,正常的流程是A账户减少100元,B账户增加100元。
    但是如果当A账户减少100元后,程序突然出错了,导致B账户增加100元这个操作没有执行,这时候就会导致这100元不翼而飞…
  • 这时候如果使用事务,将整个流程放在一个事务中进行,当整个流程正常结束时事务再一次行提交。如果中间出现了什么错误导致流程中断,这时候事务可以回滚,取消这次流程的所有操作。包括已经完成的。
  • 也可以把事务当作一个容器或者平台,流程现在这个平武平台上进行,如果没有问题,则将所有结果同步到持久化存储器(数据库)中,如果有问题,就不同步。

下面结合代码看不使用事务会有什么问题:
账户表有两个账户:
这里写图片描述
下面完成转账的流程:

  • 持久化层:
package com.spring.tx.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class TransferDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    //通过名字获取账户余额
    public int getBalanceByName(String userName){
        String sql = "select balance from account where user_name = '"+userName+"'";
        int bal = jdbcTemplate.queryForObject(sql, Integer.class);
        return bal;
    }

    /*
     * 转出
     * 需要判断,如果账户余额不足,抛出运行时异常
     */
    public void outcome(String userName,int money){
        int bal = this.getBalanceByName(userName);
        //如果余额大于转账金额,才扣钱
        if(bal > money){
            String sql = "update account set balance = "+(bal - money)+" where user_name = '"+userName+"'";
            int updateNum = jdbcTemplate.update(sql);
            System.out.println("成功更新["+updateNum+"]条数据。");
        }else{
            //账户余额不足,抛出运行时异常
            throw new RuntimeException("余额不足");
        }
    }

    //转入
    public void income(String userName,int money){
        int bal = this.getBalanceByName(userName);
        //转入直接加
        String sql = "update account set balance = "+(bal + money)+" where user_name = '"+userName+"'";
        int updateNum = jdbcTemplate.update(sql);
        System.out.println("成功更新["+updateNum+"]条数据。");
    }

}
  • 这里使用jdbcTemplate根据进行持久化操作
  • 定义了两个核心方法:
    outcome :转出,需要判断,如果账户余额不足,抛出运行时异常
    income : 转入,不用判断直接入账
  • 业务层:
package com.spring.tx.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.spring.tx.dao.TransferDao;

@Service
public class TransferService {

    @Autowired
    private TransferDao transferDao;

    public void transfer(){
        //为了测试需要,先入账
        transferDao.income("小明", 100);
        //再出账
        transferDao.outcome("小花", 100);
    }
}

定义了transfer转账方法

  • 测试:
@Test
public void test2(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-account.xml");
    TransferService transferService = (TransferService) ctx.getBean("transferService");
    //执行转账
    transferService.transfer();
}
  • 为了测试需要,这里先进行入账操作,然后再出账。
  • 转账前,小明有100元,小花有50元。现在要小花给小明转账100元。

这里写图片描述

在小花出账时发现余额不足,抛出异常,出账失败。但是小明先进行了入账操作,这时候,整个流程完成了一半,发现莫名其妙多了100元。
这里写图片描述

这样显然是不行的,这时候就需要用到事务。

声明式事务

  • 配置文件:
<!-- 配置事务管理器 -->
<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

 <!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
  • 由于这里使用的是jdbcTemplate,所有配置了一个jdbc的事务管理器,对于其他的一些持久化框架比如hibernate,jpa等spring都提供了专门的事务管理器
  • 这个事务管理器transactionManager相当于一个平台,所有持久化操作先在这个平台中进行,当使用commit后才会真正持久化到数据库。
  • 使用<tx:annotation-driven/>开启了事务注解,可以手动在执行流程的方法上使用,使之在事务管理器这个”平台”上执行。

就上面两段,声明式事务配置完成了,是不是很简单。下面开始使用.
在之前的那个业务层的transfer方法上添加事务,让这个转账的整个流程在事务管理”平台”上执行。

@Transactional
public void transfer(){
    //为了测试需要,先入账
    transferDao.income("小明", 100);
    //再出账
    transferDao.outcome("小花", 100);
}

运行之前的测试方法
这里写图片描述
执行结果都是抛出异常,流程完成一半。但是查看数据库结果就和之前不同了:
这里写图片描述
并没有多出那100元,说明事务起作用了。

回头看上面那个添加了事务的执行结果,抛出异常的截图中可以看到比之前多出很多东西,里面可以看到很多Cglib、invoke、proxy等字样,这些都是在动态代理中涉及到的东西。
没错,事务就是用了动态代理。
这里写图片描述

spring在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,就是将执行的目标包装在上面那一大段代码中。这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。

spring声明式事务管理默认对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作。
继承自RuntimeException或error的是非检查型异常,而继承自Exception的则是检查型异常

猜你喜欢

转载自blog.csdn.net/abc997995674/article/details/80312658