模拟一个买书结账的环境
环境搭建:
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开启声明式事务
@Transactional
再次执行,依然报错
结果
但此时用户的钱没有减少
用户的钱
书的库存也没有减少
书的库存
声明式事务的一些细节
声明式事务的注解中,有许多参数可以填入
- isolation:事务隔离级别 (重要)
首先了解概念
为什么需要设置隔离级别?
为什么需要设置隔离级别 脏读演示设置为读已提交隔离级别,虽然解决了读脏数据的问题,但是没有解决一次失误事务读取到的数据不同的问题
设置为可重复读级别,mysql在此级别下,已经解决了所有问题
所以一般都设置为可重复读级别,默认也是该级别
隔离级别分为?
隔离级别 隔离级别支持
其中mysql在可重复读隔离级别下就可以防止所有问题了,表中给出的信息是错误的
设置隔离级别- propagation:事务传播行为 (重要)
解释:当有多个事务嵌套运行时,若子事务出现异常,父事务是否要回滚?其他子事务是否要回滚?这就是事务传播,可以通过propagation属性进行设置
挂起:暂停
一二对应(常用),三四五六一一对应,七独立
设置传播行为
图示
注意:子事务如果是REQUIRED传播方式,那么子事务的所有配置,例如超时、设置回滚不回滚之类的全都不生效,只继承上一级事务的配置
-
readOnly:设置事务为只读事务,不能增删改,可以加快查询速度,因为不涉及增删改,则无关事务,就不用管事务的操作了
readOnly
-
timeout:事务超出指定时间后,自动终止并回滚
timeout
异常相关
异常:
运行时异常:声明式事务默认在发生运行时异常时回滚
编译时异常:需要try抓取或throws抛出的异常,声明式事务默认在发生运行时异常时不回滚
所以就需要设置以下四个属性,来设置那些属性需要回滚,那些不需要
- rollbackFor:指定那些异常事务需要回滚 (常用)
可以让原本不回滚的异常回滚
例如:IO流读取文件不存在异常,不会触发事务的回滚,但设置后就会触发
手动添加文件读取异常
测试结果
rollbackFor指定该异常回滚
指定回滚
测试结果
- noRollbackFor:指定那些异常事务不回滚 (常用)
可以让原本回滚的异常不回滚
例如数学异常,会触发事务的回滚,指定后就不会触发
手动设置数学异常
设置异常
测试
测试结果
设置不回滚
设置数学异常不回滚
测试
运行前库存
运行后库存
可见未回滚
此处要注意一个点:运行时异常默认是回滚的,设置不回滚后,若异常发生在修改sql语句之前,依然不会执行sql语句,因为运行时异常发生后,java程序会停止运行,所以要测试此异常,需要将异常放到所有sql语句之后
放到sql语句之后
-
rollbackForClassName:指定那些异常事务需要回滚by异常全类名
rollbackForClassName
效果与rollbackFor一致,只是参数是全类名,更麻烦,不常用
-
noRollbackForClassName:指定那些异常事务不回滚by异常全类名
noRollbackForClassName
效果与noRollbackFor一致,只是参数是全类名,更麻烦,不常用
使用xml配置声明式事务
xml还是一个原则:重要的使用xml配置,不重要的使用注解配置
网友评论