目录,更新ing,学习Java的点滴记录
目录放在这里太长了,附目录链接大家可以自由选择查看--------Java学习目录
Spring知识
一丶SpringIOC初步认识↓↓↓
第一篇---->初识Spring
二丶SpringIOC深入↓↓↓
第二篇---->深入SpringIoC容器(一)
第三篇---->深入SpringIoC容器(二)
三丶装配SpringBean↓↓↓
第四篇---->依赖注入的方式
第五篇---->基于xml装配Bean
第六篇---->基于注解装配Bean
第七篇---->Spring Bean之间的关系
第八篇---->SpringBean的作用域
第九篇---->Spring 加载属性(properties)文件
第十篇---->Spring表达式(SpEL)
第十一篇---->Spring在xml中配置组件扫描
四丶面向切面编程↓↓↓
第十二篇—>认识SpringAOP及底层原理
第十三篇—>使用@AspectJ注解开发AOP
第十四篇—>使用xml配置开发AOP
五丶Spring中数据库编程↓↓↓
第十五篇—>数据库编程JdbcTemplate
六丶Spring事务管理↓↓↓
第十六篇—>Spring事务管理初识
第十七篇—>编程式事务和声明式事务
第十八篇—>事务ACID特性
第十九篇—>事务传播行为
第二十篇—>事务隔离级别
6.2 传播行为
6.2.1 概念
- 传播行为是指方法之间的调用事务策略的问题.大部分情况下,希望事务同时成功或者失败.但是例外是不可避免的,假设需要实现信用卡的还款功能,有一个批量还款的业务类RepaymentBatchService的batch方法,它可以实现批量信用卡还款,并且记录还款成功的总卡数,而每一张卡的还款是的通过RepaymentService的repay方法完成的,batch就是通过调用repay方法来实现一张张还款的
- 如果有N个信用卡要进行还款,将事务设置在batch方法上,那么所有的还款操作都存在于同一个事务中,但是其中有一个信用卡在调用RepaymentService的repay方法还款时,如果余额不足以还款,则抛出一行,那么此时不仅该操作会回滚,其他正常还款的信用卡操作也会被回滚.那么该如何避免呢?如果batch方法在调用repay方法时,可以为每一个信用卡还款的repay方法单独创建一个
新事务
,当某个信用卡还款出现异常时,只会回滚它自身的事务,并不会影响主事务和其他事务,这样就可以避免上述问题了. - 下图给出上述文字描述中对应的操作,通过batch方法调用repay方法时能产生新事务,去处理一个信用卡的还款.如果这张信用卡还款异常,那么只会回滚该条新事务,而不是回滚主事务.
- 类似这样一个方法调度另外一个方法时,可以对事务的特性进行传播配置,称为传播行为
- 在Spring中传播行为的类型,是通过一个
枚举类型
去定义的,这个枚举类是org.springframework.transaction.annotation.Propagation,它定义了以下7种传播行为
- 其中使用最多的就是REQUIRED,也就是默认的传播行为.它很简单,有事务则沿用,无事务,则启用新事务.此外用的比较多的就是REQUIRES_NEW和NESTED了,对于那些不支持事务的方法使用的不多.
6.2.2 案例应用
- 案例就以上面提到的还款案例来说
- MySQL数据库建表并插入数据,
从数据中可以看出3号卡的余额是不足以还款的
CREATE TABLE `card` (
`id` int(11) NOT NULL,
`money` int(11) DEFAULT NULL,
`refund` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `card` VALUES ('1', '10', '8');
INSERT INTO `card` VALUES ('2', '20', '10');
INSERT INTO `card` VALUES ('3', '30', '40');
INSERT INTO `card` VALUES ('4', '40', '39');
INSERT INTO `card` VALUES ('5', '50', '10');
- 首先创建项目,导入jar包
资料:链接:https://pan.baidu.com/s/18Howl00S0lyOwkSvkCJ1yQ 提取码:2p1u
复制这段内容后打开百度网盘手机App,操作更方便哦 - 创建jd.properties文件,不了解的可以参考---->Spring 加载属性(properties)文件
- 创建Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd">
<!--1. 配置组件扫描-->
<context:component-scan base-package="com.tx.refund"/>
<!--2. 加载属性配置文件-->
<context:property-placeholder location="classpath:jd.properties"/>
<!--3. 配置数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="10"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="10000"/>
</bean>
<!--4. 配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--5. 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--6. 启用事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
- 编写信用卡card类,保存卡号,余额,待还金额
- 编写RepaymentBatchService批量还款类
@Service
public class RepaymentBatchService {
@Autowired
private RepaymentService repaymentService;
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 执行批量还款操作
* @param ids 传入要还款的信用卡id
* @return 返回成功还款的卡的数量
*/
public void batch(List<Integer> ids){
//1.遍历List集合
Iterator<Integer> iterator = ids.iterator();
while(iterator.hasNext()){
Integer id = iterator.next();
//2. 对每个id的信用卡执行还款操作
repaymentService.repay(id);
}
}
/**
* 查询成功还款的信用卡数
* 查询每条记录中待还款金额为0的记录数
* @return
*/
public String success() {
String sql="select count(*) from card where refund = 0";
return jdbcTemplate.queryForInt(sql)+"";
}
}
- 编写RepaymentService单个还款类
@Service
public class RepaymentService {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 还款指定id号的信用卡
* @param id 信用卡id
*/
public void repay(Integer id){
//1. 查询该id的余额和待还款金额
String sql = "select * from card where id = "+id;
List<Card> cards = jdbcTemplate.query(sql, new RowMapper<Card>() {
@Override
public Card mapRow(ResultSet rs, int i) throws SQLException {
Card card = new Card();
card.setId(rs.getInt("id"));
card.setMoney(rs.getInt("money"));
card.setRefund(rs.getInt("refund"));
return card;
}
});
Card card = cards.get(0);
//2. 如果余额>=待还款金额,则将余额减少待还款金额,并将待还款金额置为0
if (card.getMoney()>=card.getRefund()){
String sql2 = "update card set money=money-refund,refund=0 where id ="+id;
jdbcTemplate.update(sql2);
}else{
//3. 如果余额<待还款金额,抛出一个运行时异常-->余额不足
throw new RuntimeException("余额不足!");
}
}
}
- 编写测试类
- 现在开始执行测试代码,查看控制台输出和数据库数据结果
- 从结果中可以发现,只有id为1,2的信用卡还款成功,但是可以发现id为4,5的信用卡也满足还款条件,但是由于执行3号信用卡时,由于余额不足而导致抛出异常,阻断了代码的正常执行
- 现在使用事务将还款操作管理起来,我们对batch方法,添加事务注解,将批量还款的操作交由事务管理
- 重新修正一下数据库数据,执行测试,看此时的结果
- 咦,控制台输出的结果是抛出了余额不足的异常,通过数据可以知道这是由于3号信用卡余额不足导致的,但是最后一句输出结果说
没有任何一张信用卡还款成功
,这是为啥呢?我们来看下数据库数据
- 神奇发现,数据竟然一点都没变,这里我们就要回想一下,默认的传播行为是
如果当前方法已经处于一个事务中,则沿用该事务,如果没有处于一个事务中,则新建一个事务
,batch方法并没有处于任何一个事务方法中,所以当然是新建一个事务,在该方法中包含了对所有信用卡的还款操作,而默认情况下,事务中只要出现异常则全部回滚,否则全部提交,因此出现上述结果的原因就很明显了,原来是由于3号信用卡的异常,导致了之前所有的更新操作都回滚了. - 具体解决这种方式的方法前面已经提到过了,那就是给每一张信用卡还款的方法都新建一个新的事务,这样的话,一张卡的异常只会导致这张卡的数据回滚,而不会影响到整体,下面修改repay方法
- 还有一个
细节的地方需要修改一下,后面会讲为什么,将test中的try-catch删掉,将batch方法中添加try-catch
- 再次执行测试方法,并查看数据库数据
- 从结果中可以看到完全符合我们的预期,能还款的还款,不能还款的抛出异常并保持数据不懂.因为batch方法处于一个事务中,每次调用repay方法都会生成新的事务,在3号信用卡抛出的异常只会影响到自己所在的事务,不会影响其他操作
6.2.3 分析还款案例中的try-catch细节
- 首先把上面的还款案例划分成3个阶段
1) 第一阶段:第一次输出结果及之前,没有添加任何事务注解,只是单纯的运行一下测试代码,在该阶段中,repay方法会对每个信用卡的余额和待还金额进行比较,如果余额小于待还金额,那么会抛出"余额不足"的异常,我们现在看一下,这个异常的抛出流程,从repay方法抛出,回到调用repay方法的batch方法中,在该方法中没有对该异常进行处理,所以进一步上抛,到达Test类中的refund方法,其中使用了try-catch方法对异常进行了捕捉,异常捕获完,进入finally子句,程序就结束了,又由于使用ArrayList保存数据,输入顺序依次是12345,输出顺序也是12345,当执行到3的时候,程序就中断了,因此出现了第一次输出的结果
2) 第二阶段:第一次输出结果之后到第二次输出结果,在这里我对batch方法添加了@Transactional注解,并设置为默认传播行为,同时运行程序,又是到达3号信用卡,会抛出异常,repay方法没有捕获,将异常抛出到batch方法,batch方法也没有捕获,继续抛出,由于存在@Transactional注解
,会发现batch方法抛出了异常,因此会将batch方法之前进行的所有操作进行回滚,最后异常到达Test类的refund方法,捕获异常后程序结束
3) 第三阶段:第二次输出结果后到修改try-catch语句之前,第三次是在第二次基础上,在repay方法添加了@Transactional(propagation = Propagation.REQUIRES_NEW)的注解,表示会启动新的事务,如果我们没有删掉Test类中的try-catch以及在batch中添加try-catch,执行流程会怎么样呢?执行结果是下面第一张图.现在分析一下:同样是执行到3号信用卡会抛出异常,但是由于repay方法是REQUIRES_NEW
的传播行为,当之前执行1号和2号的的时候每次都会新建一个事务,1号和2号还款完毕后事务也就提交了,到3号的时候,会抛出异常到batch方法,batch方法又会继续抛出,尽管batch方法虽然存在@Transactional方法,但是由于repay方法的事务注解的作用,并不会将之前1号和2号的操作回滚
,异常继续上抛到Test类的refund方法,异常得到捕获,程序运行结束,4号和5号根本就没有执行对应的repay方法
第四阶段:修改try-catch语句到第三次输出结果,我将本该在Test类中捕获异常的代码放入batch的循环语句当中,对调用的repay方法进行捕获,现在重新梳理一下执行流程:依旧是1号和2号执行完毕,3号抛出异常,repay无法捕获,抛出给batch,由于repay方法有@Transactional注解,此时对3号卡的操作会因为异常而回滚,到了batch方法,存在异常捕获代码,会将抛出的异常捕获,由于处于while循环中,程序还没有运行完毕,因此会继续执行循环代码,对4号和5号信用卡进行还款操作.这样最后的结果就是第三次输出的结果. - 上面这个案例,挺复杂的,但是我觉得,你能够理清楚添加@Transactional注解及设置不同传播行为后程序的运行结果的关系,你对传播行为的理解就足够了