目录,更新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
;
public void batch(List
<Integer> ids
){
Iterator
<Integer> iterator
= ids
.iterator();
while(iterator
.hasNext()){
Integer id
= iterator
.next();
repaymentService
.repay(id
);
}
}
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
;
public void repay(Integer 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);
if (card
.getMoney()>=card
.getRefund()){
String sql2
= "update card set money=money-refund,refund=0 where id ="+id
;
jdbcTemplate
.update(sql2
);
}else{
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注解及设置不同传播行为后程序的运行结果的关系,你对传播行为的理解就足够了