模拟一个买书结账的环境
环境搭建:
bookDao类,与数据库交互
@Repository
public class bookDao {
@Autowired
private JdbcTemplate template;
//根据isbn获取价格
public int getPrice(String isbn) {
String sql = "select price from book where isbn = ?";
Integer price = template.queryForObject(sql, Integer.class, isbn);
return price;
}
//根据价格减少钱
public void reduceMoney(int price,String username) {
String sql = "update account set balance = balance - ? where username = ?";
template.update(sql,price,username);
}
//根据isbn减少库存
public void reduceStock(String isbn,int num) {
String sql = "update book_stock set stock = stock - ? where isbn = ?";
template.update(sql,num,isbn);
}
}
bookService类,逻辑操作
@Service
public class bookService {
@Autowired
private bookDao dao;
//实现结账操作
public void pay() throws IOException {
Scanner s = new Scanner(System.in);
System.out.print("请输入您的姓名:");
String username = s.next();
System.out.print("请输入购买的书籍编号:");
String isbn = s.next();
System.out.print("请输入购买数量:");
int num = s.nextInt();
System.out.println("正在结账...");
int price = dao.getPrice(isbn);
dao.reduceMoney((price*num),username);
dao.reduceStock(isbn,num);
System.out.println("购买成功");
}
}
ioc容器xml配置,开启注解配置
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--扫描指定包下配置了注解的bean-->
<context:component-scan base-package="cn.yzx.dao"/>
<context:component-scan base-package="cn.yzx.service"/>
<!--开启注解配置-->
<context:annotation-config/>
<!--引入外部配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--获取druid数据库连接池-->
<bean id="DataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${password}"/>
</bean>
<!--注册JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="DataSource"/>
</bean>
</beans>
测试类
public class mytest {
public static void main(String[] args) throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
bookService bean = context.getBean(bookService.class);
bean.pay();
}
}
实现需求,用户买了书,根据买书的数量以及书的价格,减少用户的钱,以及书的库存
-
无事务
显而易见一个很严重的问题,如果在减少用户钱、库存减少两次操作中,后执行的一个除了错误,则前一个执行的结果无法撤回,比如,我这里是用户先付钱再减少库存,在减少库存的sql语句中人为的制造错误
制造错误
此时,钱已经减少了,但是库存因为错误,并没有减少
运行结果
用户的钱
书的库存
此时,我们就需要使用事务,事务可以保证所有操作都正常执行数据库数据才会发生改变,之前我们使用的是手动的事务,较为麻烦,且在本案例中有多次数据库连接,要甄别他们也十分麻烦,而spring的AOP功能与事务的步骤基本吻合,所以可以使用AOP来实现事务,但是自己写AOP也很麻烦,所以spring为我们写好的基于AOP的声明式事务 -
使用声明式事务
首先修改IOC容器的配置文件,添加一下几行
xml
注意,使用tx需要导入tx的名称空间依赖
然后在进行操作的方法(Service层)上,使用@Transactional开启声明式事务
![](https://img.haomeiwen.com/i22864240/792d77b6f32c7003.png)
再次执行,依然报错
![](https://img.haomeiwen.com/i22864240/ec83ceaac4735c2e.png)
但此时用户的钱没有减少
![](https://img.haomeiwen.com/i22864240/bddf956b4b87a491.png)
书的库存也没有减少
![](https://img.haomeiwen.com/i22864240/72e746b68e9f8f44.png)
声明式事务的一些细节
声明式事务的注解中,有许多参数可以填入
- isolation:事务隔离级别 (重要)
![](https://img.haomeiwen.com/i22864240/f106add5f0e98cd4.png)
首先了解概念
为什么需要设置隔离级别?
![](https://img.haomeiwen.com/i22864240/da4d59ec6ad26eb4.png)
![](https://img.haomeiwen.com/i22864240/22f4dda457c4e220.png)
设置为读已提交隔离级别,虽然解决了读脏数据的问题,但是没有解决一次失误事务读取到的数据不同的问题
![](https://img.haomeiwen.com/i22864240/45abe8f3d9a8ea9b.png)
设置为可重复读级别,mysql在此级别下,已经解决了所有问题
![](https://img.haomeiwen.com/i22864240/cb29f3e7f7a2600b.png)
所以一般都设置为可重复读级别,默认也是该级别
隔离级别分为?
![](https://img.haomeiwen.com/i22864240/1e282eef2cefb9c9.png)
![](https://img.haomeiwen.com/i22864240/baac69917d08024a.png)
其中mysql在可重复读隔离级别下就可以防止所有问题了,表中给出的信息是错误的
![](https://img.haomeiwen.com/i22864240/c5b00ea248544240.png)
- propagation:事务传播行为 (重要)
![](https://img.haomeiwen.com/i22864240/00e6721ea9d32088.png)
解释:当有多个事务嵌套运行时,若子事务出现异常,父事务是否要回滚?其他子事务是否要回滚?这就是事务传播,可以通过propagation属性进行设置
![](https://img.haomeiwen.com/i22864240/b782e9b5f88e6119.png)
挂起:暂停
一二对应(常用),三四五六一一对应,七独立
![](https://img.haomeiwen.com/i22864240/193f02290f87f8ae.png)
![](https://img.haomeiwen.com/i22864240/20aa497072523bae.png)
注意:子事务如果是REQUIRED传播方式,那么子事务的所有配置,例如超时、设置回滚不回滚之类的全都不生效,只继承上一级事务的配置
-
readOnly:设置事务为只读事务,不能增删改,可以加快查询速度,因为不涉及增删改,则无关事务,就不用管事务的操作了
readOnly
![](https://img.haomeiwen.com/i22864240/7bc07746533e6db4.png)
![](https://img.haomeiwen.com/i22864240/3f0a68ad91c25e2c.png)
-
timeout:事务超出指定时间后,自动终止并回滚
timeout
![](https://img.haomeiwen.com/i22864240/9fcffdf98ef0dde3.png)
![](https://img.haomeiwen.com/i22864240/093b9692e42b7229.png)
异常相关
异常:
运行时异常:声明式事务默认在发生运行时异常时回滚
编译时异常:需要try抓取或throws抛出的异常,声明式事务默认在发生运行时异常时不回滚
所以就需要设置以下四个属性,来设置那些属性需要回滚,那些不需要
- rollbackFor:指定那些异常事务需要回滚 (常用)
![](https://img.haomeiwen.com/i22864240/59a571b6a2c7ac34.png)
可以让原本不回滚的异常回滚
例如:IO流读取文件不存在异常,不会触发事务的回滚,但设置后就会触发
![](https://img.haomeiwen.com/i22864240/1f483f9275e8e36c.png)
![](https://img.haomeiwen.com/i22864240/04b1603b8d9b8b86.png)
rollbackFor指定该异常回滚
![](https://img.haomeiwen.com/i22864240/7339931256bfaffd.png)
![](https://img.haomeiwen.com/i22864240/db77b52df1697ac7.png)
- noRollbackFor:指定那些异常事务不回滚 (常用)
![](https://img.haomeiwen.com/i22864240/6283404ea441b07b.png)
可以让原本回滚的异常不回滚
例如数学异常,会触发事务的回滚,指定后就不会触发
手动设置数学异常
![](https://img.haomeiwen.com/i22864240/0c5846f4325110d9.png)
测试
![](https://img.haomeiwen.com/i22864240/a449aa0ffb8208c8.png)
设置不回滚
![](https://img.haomeiwen.com/i22864240/590f7861c70e3e4d.png)
测试
![](https://img.haomeiwen.com/i22864240/c0d19a30836de41c.png)
![](https://img.haomeiwen.com/i22864240/ceafd50cebfffb66.png)
可见未回滚
此处要注意一个点:运行时异常默认是回滚的,设置不回滚后,若异常发生在修改sql语句之前,依然不会执行sql语句,因为运行时异常发生后,java程序会停止运行,所以要测试此异常,需要将异常放到所有sql语句之后
![](https://img.haomeiwen.com/i22864240/3102282d8c2b1614.png)
-
rollbackForClassName:指定那些异常事务需要回滚by异常全类名
rollbackForClassName
效果与rollbackFor一致,只是参数是全类名,更麻烦,不常用
-
noRollbackForClassName:指定那些异常事务不回滚by异常全类名
noRollbackForClassName
效果与noRollbackFor一致,只是参数是全类名,更麻烦,不常用
使用xml配置声明式事务
![](https://img.haomeiwen.com/i22864240/233debe3d8971e4c.png)
还是一个原则:重要的使用xml配置,不重要的使用注解配置
网友评论