环境搭建
使用MyBatis操作数据库
准备好两个对象:
账户(Account):id,name,money(账户余额)
图书(Book):id,bookName,price,stock(库存)
几个方法:
1 | //账户余额减少 |
2 | void spendMoney(@Param("accountName") String accountName, @Param("qian")int qian); |
3 | //通过书名找到书本对象 |
4 | Book getBookByName(String bookName); |
5 | //更新书本库存 |
6 | void updateBook(Book book); |
7 | |
8 | //业务层(两个参数,账户名+书名) |
9 | public void checkout(String accountName,String bookName){ |
10 | Book abook=bookMapper.getBookByName(bookName); |
11 | //得到要买的书 |
12 | bookMapper.updateBook(abook); |
13 | //书本减少库存 |
14 | accountMapper.spendMoney(accountName,abook.getPrice()); |
15 | //账户余额减少 |
16 | } |
注意:MyBatis使用多参数操作数据库是,要在参数前加上@Param注解,例如这个花钱方法
1void spendMoney(@Param("accountName") String accountName, @Param("qian")int qian);
测试顺利,环境搭建完成
案列分析
这个过程分了两个操作完成,减少库存和减少余额,但是如果中间发生了一些差错时,库存减少了,余额确没减少,这样商家就亏了,所以要把它们弄成一个整体,引入事务控制。
声明式事务
事务概念
一组逻辑紧密关联的数据库操作合成的整体。
比如买书时,账户扣钱和书本减少库存,转账时一方扣钱另一方加钱。两者必须是一起的。
四个关键属性
原子性(不可再分,要么都执行,要么都不执行),
一致性(所有数据都处于满足业务规则的一致性状态,只能由一个一致的状态到另一个一致的状态,一旦某个操作失败就回滚到没执行前的状态),
隔离性(多个事务并发执行不会互相干扰),
持久性(对数据的修改会永久保存)。
原生编程式事务

Spring事务控制
只需要告诉Spring哪个方法是事务方法,Spring就会自动进行事务控制。
根据框架不同使用不同的管理器,MyBatis使用DataSourceTransactionManager
在这之前,由于这是基于切面的,所以要导入切面相关依赖。
配置事务管理器
让它控制住数据源
1 | <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> |
2 | <property name="dataSource" ref="dataSource"></property> |
3 | </bean> |
开启基于注解的事务控制模式
导入tx名称空间,在这里又学到了
1 | xmlns:tx="http://www.springframework.org/schema/tx" |
2 | <!--加了这一句后,还要在xsi:schemaLocation加上同一句和/spring-xxx.xsd--> |
3 | http://www.springframework.org/schema/tx |
4 | http://www.springframework.org/schema/tx/spring-tx.xsd |
这里的属性与刚刚的id对应
1 | <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> |
给事务方法加注解
用@Transactional注解
测试
先弄一个产生异常的语句加载两个数据库操作中间。
1 | public void checkout(String accountName,String bookName){ |
2 | Book abook=bookMapper.getBookByName(bookName); |
3 | //得到要买的书 |
4 | bookMapper.updateBook(abook); |
5 | //书本减少库存 |
6 | int x=5/0; |
7 | //造成异常 |
8 | accountMapper.spendMoney(accountName,abook.getPrice()); |
9 | //账户余额减少 |
10 | System.out.println("结账完成"); |
11 | } |
没加注解时,运行抛出异常,发现库存减少了,账户余额并没有减少
加了注解后,同样抛出异常,但是库存也没减少,余额也没减少,很成功。
细节
注解属性解析
readOnly(boolean):设置事务为只读事务,可以进行事务优化,如果操作都是只读数据库,调成true可以省去一些操作,比如加锁,加快查询速度。默认false
1 | (readOnly=true)//该事务只能对数据库进行读取操作 |
timeout(int):超时自动终止并回滚,以秒为单位
1 | (timeout=3)//如果3秒内不能执行完就GG |
运行时异常默认都回滚,比如数学运算异常(数字除以0),编译时异常默认不会滚,比如IO异常,读取文件不到。如果在完成数据库操作后发生编译时异常,数据操作已经执行了。
noRollbackFor(Class[]):指定哪些异常事务可以不回滚,可以让原本回滚的异常不再回滚。
1 | (noRollbackFor={NullPointerExceptioin.class,xxx.class}) |
noRollbackForClassName(String[]):上述异常的String类型全类名
1 | (noRollbackForClassName={"java.lang.xxxException","xxx"}) |
rollbackFor(Class[]):指定哪些异常需要回滚,让不回滚的异常回滚,用法与上面相反
rollbackForClassName(String[]):上述异常的String类型全类名
isolation(Isolation):调整事务隔离级别
几个概念:
脏读:事务A将20改为30,事务B读取到30,事务A回滚,此时30这个数据就是脏数据
不可重复读:事务A读取数据20,事务B修改为30,事务A再读取到了30,两次不一致
幻读:事务A读取到信息10条,事务B插入了10条,事务A再读成了20条,信息多了
隔离级别:不同级别应对不同现象
读未提交:允许事务B读取事务A未提交的数据,—以上都可能产生
读以提交:要求事务B只能读取事务A以提交的数据,—避免脏读,但产生另外两种
可重复读:在读取数据期间,不可修改数据,—避免不可重复读和脏读(实际上MySql这个级别能避免幻读)
串行化:变成单线程,啥都能避免,—但没啥用了,事务排队一万年
propagation(Propagation):指定事务传播行为,事务传播+事务行为
如果多个事务嵌套运行,子事务是否和父事务共用一个事务。比如:ta,tb,tc都是事务
1 | Service{ |
2 | tb(){} |
3 | tc(){} |
4 | } |
5 | Test{ |
6 | //注入Service s |
7 | ta(){ |
8 | s.tb(){} |
9 | s.tc(){} |
10 | } |
11 | } |
当tc回滚时,tb,ta要不要回滚。怎么控制就靠传播行为控制

REQUIRED在当前事务里执行自己,如果当前没有,则开新事务。REQUIRED_NEW必定会挂起当前事务,开新事务执行自己。
不管后面怎么异常,之前已经执行的带有REQUIRED_NEW不会回滚,而REQUIRED会受到影响。当REQUIRED_NEW中的异常没有捕获被抛出时,会影响到父事务。
当属性为REQUIRED时,其他属性都跟父事务一样,在子事务上调整不起作用。
REQUIRED:将之前事务的connection传递给这个方法使用
REQUIRED_NEW:创建新的connection
本类方法的嵌套调用,就会看作一个事务,比如C事务调用了本类的A,B事务
1 | Service{ |
2 | a(){}//A事务 |
3 | b(){}//B事务 |
4 | c(){//C事务 |
5 | a(); |
6 | b(); |
7 | } |
8 | } |
基于XML事务控制
配置事务管理器一样,导入名称空间一样。
告诉Spring哪些是事务方法
让事务切面按照定义的切入点表达式去切入事务方法
1 | <aop:config> |
2 | <aop:pointcut expression="execution(xxx)" id="point"/> |
3 | <!--用表达式指定要切入的方法--> |
4 | <aop:advisor advice-ref="myAdvice" pointcut-ref="point"/> |
5 | <!--指定事务管理器--> |
6 | </aop:config> |
7 | <!--配置事务管理器--> |
8 | <tx:advice id="myAdvice" transaction-manager="transactionManager"> |
9 | <tx:attributes> |
10 | <!--指明哪些方法是事务方法--> |
11 | <tx:method name="test" timeout="3"></tx:method> |
12 | <!--具体哪个方法,所有的参数都可以配置,这里以timeout为例--> |
13 | <tx:method name="*"></tx:method> |
14 | <!--所有方法--> |
15 | <tx:method name="get*" read-only="true"></tx:method> |
16 | <!--get开头的方法--> |
17 | </tx:attributes> |
18 | </tx:advice> |
总结
头发-1