文章目录
- 1.事务分类
- 1.传统方式解决事务
- 2.声明式事务
- 2.声明式事务案例
- 1.需求分析
- 2.解决方案分析
- 3.数据表创建
- 4.编写GoodsDao.java
- 1.编写配置文件JdbcTemplate_ioc.xml
- 2.单元测试
- 5.编写GoodsService.java
- 6.配置事务管理器JdbcTemplate_ioc.xml
- 7.进行测试
- 3.debug事务管理器DataSourceTransactionManager
- 1.基本步骤解释
- 1.配置文件
- 2.注解
- 3.具体AOP示意图
- 2.debug出现异常情况
- 1.下断点
- 2.执行
- 3.下一个断点
- 4.再打一个断点,跳过去
- 5.下一个断点
- 6.在con.rollback();打一个断点,跳过去然后下一步
- 4.声明式事务传播机制
- 1.基本介绍
- 2.事务传播机制种类
- 3.图解
- 1.默认事务传播机制(REQUIRED)
- 2.REQUIRES_NEW事务传播机制
- 3.两种事务传播机制的区别
- 4.声明式事务传播机制的设置方法
- 5.应用实例
- 1.文件目录
- 2.GoodsDao.java
- 3.GoodsService.java
- 4.MultiplyService.java
- 5.默认传播机制测试
- 1.故意修改GoodsDao.java第一套的updateBalance方法
- 2.初始数据表
- 3.测试代码
- 4.结果展示
- 6.REQUIRES_NEW传播机制演示
- 1.故意修改GoodsDao.java第二套的updateBalance方法(第一套的改回来)
- 2.修改GoodsService.java的两个事务传播机制
- 3.修改MultiplyService.java的一个事务传播机制
- 4.初始数据表
- 5.测试代码
- 6.结果展示
- 7.注意事项
- 5.事务隔离级别
- 1.基本介绍
- 2.应用实例
- 1.简要介绍
- 2.结果展示
- 1.原goods表
- 2.启动测试程序
- 3.修改id为1的价格
- 4.打开断点,查看第二次结果
- 3.修改事务隔离级别
- 6.事务超时回滚
- 1.基本介绍
- 2.代码实例
- 1.GoodsService.java的buyGoodsByTxSOLATION方法
- 2.测试程序
- 3.原账户表
- 4.执行测试程序
- 7.课后练习
- 1.要求
- 2.数据表设计
- 3.代码实现
- 1.文件目录
- 2.jdbc.properties
- 3.txhomework.xml
- 4.HomeWorkDao.java
- 5.HomeWorkService.java
- 6.测试类
- 7.原数据表
- 8.执行测试类
- 1.执行失败
- 2.执行成功
1.事务分类
1.传统方式解决事务
2.声明式事务
2.声明式事务案例
1.需求分析
2.解决方案分析
3.数据表创建
-- 声明式事务要创建的表
-- 账户表
CREATE TABLE `user_account`(
user_id INT UNSIGNED PRIMARY KEY auto_increment,
user_name VARCHAR(32) NOT NULL DEFAULT'',
money DOUBLE NOT NULL DEFAULT 0.0
);
INSERT INTO `user_account` VALUES(NULL, '张三', 1000);
INSERT INTO `user_account` VALUES(NULL, '李四', 2000);
SELECT * FROM user_account;
-- 商品表
CREATE TABLE `goods`(
goods_id INT UNSIGNED PRIMARY KEY auto_increment,
goods_name VARCHAR(32) NOT NULL DEFAULT'',
price DOUBLE NOT NULL DEFAULT 0.0
);
INSERT INTO goods VALUES(NULL, '小风扇',10.00);
INSERT INTO goods VALUES(NULL, '小台灯',12.00);
INSERT INTO goods VALUES(NULL, '可口可乐',1.00);
SELECT * FROM goods;
-- 商品库存表
CREATE TABLE `goods_amount`(
goods_id INT UNSIGNED PRIMARY KEY auto_increment,
goods_num INT UNSIGNED DEFAULT 0
);
INSERT INTO goods_amount VALUES(NULL, 200);
INSERT INTO goods_amount VALUES(NULL, 20);
INSERT INTO goods_amount VALUES(NULL, 23);
SELECT * FROM goods_amount;
4.编写GoodsDao.java
package com.sxs.spring.tx;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
* @author 孙显圣
* @version 1.0
*/
//反射创建bean对象
@Repository
public class GoodsDao {
//依赖注入
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 根据商品id返回价格
*
* @param id
* @return
*/
public Double queryPriceById(Integer id) {
String sql = "select price from goods where goods_id = ?";
Double price = jdbcTemplate.queryForObject(sql, Double.class, id);
System.out.println(id + "号商品价格为" + price);
return price;
}
/**
* 根据用户名id减少用户余额
*
* @param user_id
* @param money
*/
public void updateBalance(Integer user_id, Double money) {
String sql = "update user_account set money = money - ? where user_id = ?";
int update = jdbcTemplate.update(sql, money, user_id);
if (update == 1) {
System.out.println("用户余额减少成功");
}
}
/**
* 修改商品库存
*
* @param goods_id
* @param amount
*/
public void updateAmount(Integer goods_id, Integer amount) {
String sql = "update goods_amount set goods_num = goods_num - ? where goods_id = ?";
int update = jdbcTemplate.update(sql, amount, goods_id);
if (update == 1) {
System.out.println("商品库存减少成功!");
}
}
}
1.编写配置文件JdbcTemplate_ioc.xml
2.单元测试
package com.sxs.spring.tx;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
* @author 孙显圣
* @version 1.0
*/
//反射创建bean对象
@Repository
public class GoodsDao {
//依赖注入
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 根据商品id返回价格
*
* @param id
* @return
*/
public Double queryPriceById(Integer id) {
String sql = "select price from goods where goods_id = ?";
Double price = jdbcTemplate.queryForObject(sql, Double.class, id);
System.out.println(id + "号商品价格为" + price);
return price;
}
/**
* 根据用户名id减少用户余额
*
* @param user_id
* @param money
*/
public void updateBalance(Integer user_id, Double money) {
String sql = "update user_account set money = money - ? where user_id = ?";
int update = jdbcTemplate.update(sql, money, user_id);
if (update == 1) {
System.out.println("用户余额减少成功");
}
}
/**
* 修改商品库存
*
* @param goods_id
* @param amount
*/
public void updateAmount(Integer goods_id, Integer amount) {
String sql = "update goods_amount set goods_num = goods_num - ? where goods_id = ?";
int update = jdbcTemplate.update(sql, amount, goods_id);
if (update == 1) {
System.out.println("商品库存减少成功!");
}
}
}
5.编写GoodsService.java
package com.sxs.spring.tx.service;
import com.sxs.spring.tx.GoodsDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* @author 孙显圣
* @version 1.0
*/
@Service
public class GoodsService {
@Resource
private GoodsDao goodsDao;
@Transactional //会将方法中对数据库的操作作为一个事务管理
public void buyGoodsByTx(Integer userId, Integer goodsId, Integer amount) {
//得到商品的价格
Double price = goodsDao.queryPriceById(goodsId);
//减少用户的余额
goodsDao.updateBalance(userId, amount * price);
//减少库存
goodsDao.updateAmount(goodsId, amount);
}
}
6.配置事务管理器JdbcTemplate_ioc.xml
<!--配置事务管理器对象-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<!--需要与数据源关联,因为操作数据库的jdbcTemplate要与dataSource关联,他们要一致-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置启动注解的声明式事务管理功能-->
<tx:annotation-driven transaction-manager="transactionManager"/>
7.进行测试
//通过声明式事务管理测试
@org.junit.jupiter.api.Test
public void byGoodsByTx() {
GoodsService bean1 = ioc.getBean(GoodsService.class);
bean1.buyGoodsByTx(1, 1, 3);
}
执行前后并没有出现数据不一致
3.debug事务管理器DataSourceTransactionManager
1.基本步骤解释
1.配置文件
-
首先配置了一个事务管理器对象,绑定与jdbcTemplate一致的数据源
-
然后启动tx注解,绑定这个事务管理器对象
2.注解
- 当使用这个注解时,表名这个方法就会被AOP作为一个事务管理
3.具体AOP示意图
2.debug出现异常情况
1.下断点
2.执行
3.下一个断点
4.再打一个断点,跳过去
5.下一个断点
6.在con.rollback();打一个断点,跳过去然后下一步
4.声明式事务传播机制
1.基本介绍
2.事务传播机制种类
3.图解
1.默认事务传播机制(REQUIRED)
- 可以理解为将方法1和方法2的事务功能去掉了,将方法1和方法2的代码合并交给Tx事务管理
- 具体例子
- 方法1出错:方法1和方法2和Tx事务全部回滚
- 方法2出错:方法1和方法2和Tx事务全部回滚
- Tx事务出错(除了方法1和方法2的部分):方法1和方法2和Tx事务全部回滚
2.REQUIRES_NEW事务传播机制
- 可以理解为Tx事务、方法1事务、方法2事务都是各自独立的
- 具体例子
- 方法1出错:方法1事务回滚
- 方法2出错:方法2事务回滚
- Tx事务出错(除了方法1和方法2的部分):Tx事务(除了方法1和方法2的部分)回滚
3.两种事务传播机制的区别
4.声明式事务传播机制的设置方法
5.应用实例
1.文件目录
2.GoodsDao.java
package com.sxs.spring.tx;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
* @author 孙显圣
* @version 1.0
*/
//反射创建bean对象
@Repository
public class GoodsDao {
//依赖注入
@Resource
private JdbcTemplate jdbcTemplate;
//第一套方法
/**
* 根据商品id返回价格
*
* @param id
* @return
*/
public Double queryPriceById(Integer id) {
String sql = "select price from goods where goods_id = ?";
Double price = jdbcTemplate.queryForObject(sql, Double.class, id);
System.out.println(id + "号商品价格为" + price);
return price;
}
/**
* 根据用户名id减少用户余额
*
* @param user_id
* @param money
*/
public void updateBalance(Integer user_id, Double money) {
String sql = "update user_account set money = money - ? where user_id = ?";
int update = jdbcTemplate.update(sql, money, user_id);
if (update == 1) {
System.out.println("用户余额减少成功");
}
}
/**
* 修改商品库存
*
* @param goods_id
* @param amount
*/
public void updateAmount(Integer goods_id, Integer amount) {
String sql = "update goods_amount set goods_num = goods_num - ? where goods_id = ?";
int update = jdbcTemplate.update(sql, amount, goods_id);
if (update == 1) {
System.out.println("商品库存减少成功!");
}
}
//第二套方法
/**
* 根据商品id返回价格
*
* @param id
* @return
*/
public Double queryPriceById2(Integer id) {
String sql = "select price from goods where goods_id = ?";
Double price = jdbcTemplate.queryForObject(sql, Double.class, id);
System.out.println(id + "号商品价格为" + price);
return price;
}
/**
* 根据用户名id减少用户余额
*
* @param user_id
* @param money
*/
public void updateBalance2(Integer user_id, Double money) {
String sql = "update user_account set money = money - ? where user_id = ?";
int update = jdbcTemplate.update(sql, money, user_id);
if (update == 1) {
System.out.println("用户余额减少成功");
}
}
/**
* 修改商品库存
*
* @param goods_id
* @param amount
*/
public void updateAmount2(Integer goods_id, Integer amount) {
String sql = "update goods_amount set goods_num = goods_num - ? where goods_id = ?";
int update = jdbcTemplate.update(sql, amount, goods_id);
if (update == 1) {
System.out.println("商品库存减少成功!");
}
}
}
3.GoodsService.java
package com.sxs.spring.tx.service;
import com.sxs.spring.tx.GoodsDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* @author 孙显圣
* @version 1.0
*/
@Service
public class GoodsService {
@Resource
private GoodsDao goodsDao;
//调用第一套方法
@Transactional //会将方法中对数据库的操作作为一个事务管理
public void buyGoodsByTx(Integer userId, Integer goodsId, Integer amount) {
//得到商品的价格
Double price = goodsDao.queryPriceById(goodsId);
//减少用户的余额
goodsDao.updateBalance(userId, amount * price);
//减少库存
goodsDao.updateAmount(goodsId, amount);
}
//调用第二套方法
@Transactional //会将方法中对数据库的操作作为一个事务管理
public void buyGoodsByTx2(Integer userId, Integer goodsId, Integer amount) {
//得到商品的价格
Double price = goodsDao.queryPriceById2(goodsId);
//减少用户的余额
goodsDao.updateBalance2(userId, amount * price);
//减少库存
goodsDao.updateAmount2(goodsId, amount);
}
}
4.MultiplyService.java
package com.sxs.spring.tx.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* @author 孙显圣
* @version 1.0
*/
@Service
public class MultiplyService {
@Resource
private GoodsService goodsService;
@Transactional //默认事务传播机制
public void multiBuyGoodsByTx() {
//默认事务传播机制
goodsService.buyGoodsByTx(1,1,1);
goodsService.buyGoodsByTx2(1,1,1);
}
}
5.默认传播机制测试
1.故意修改GoodsDao.java第一套的updateBalance方法
2.初始数据表
- 账户表
- 库存表
3.测试代码
- 此时是默认传播机制,所以如果第一套方法出现异常,则第二套方法也会回滚
- 所以结果应该是数据表没有变化
//事务传播机制测试
@org.junit.jupiter.api.Test
public void multiBuyGoodsByTx() {
MultiplyService bean1 = ioc.getBean(MultiplyService.class);
bean1.multiBuyGoodsByTx();
}
4.结果展示
6.REQUIRES_NEW传播机制演示
1.故意修改GoodsDao.java第二套的updateBalance方法(第一套的改回来)
2.修改GoodsService.java的两个事务传播机制
3.修改MultiplyService.java的一个事务传播机制
4.初始数据表
- 账户表
- 库存表
5.测试代码
- 此时是REQUIRES_NEW传播机制,所以如果第二套方法出现异常,第一套方法不会回滚
- 所以结果应该是第一套方法执行成功,张三购买一件商品1成功
//事务传播机制测试
@org.junit.jupiter.api.Test
public void multiBuyGoodsByTx() {
MultiplyService bean1 = ioc.getBean(MultiplyService.class);
bean1.multiBuyGoodsByTx();
}
6.结果展示
7.注意事项
- 第一次我将第一套方法改错了,本来以为第二套方法会正常执行
- 但是虽然事务是独立的,但是第一套方法报错了后面的就不继续执行了
- 最外面的事务设置成默认的还是REQUIRES_NEW类型都不会影响里面的两个事务的独立性
5.事务隔离级别
1.基本介绍
2.应用实例
1.简要介绍
2.结果展示
1.原goods表
2.启动测试程序
3.修改id为1的价格
4.打开断点,查看第二次结果
3.修改事务隔离级别
6.事务超时回滚
1.基本介绍
2.代码实例
1.GoodsService.java的buyGoodsByTxSOLATION方法
@Transactional(timeout = 2) //设置超时回滚时间为2秒
public void buyGoodsByTxSOLATION(){
//查询两次商品的价格
Double aDouble = goodsDao.queryPriceById(1);
//减少用户的余额
goodsDao.updateBalance2(1, 1000.0);
try {
//设置4秒休眠时间
Thread.sleep(4000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Double aDouble1 = goodsDao.queryPriceById(1);
System.out.println(aDouble1);
}
2.测试程序
@org.junit.jupiter.api.Test
public void buyGoodsByTxSOLATION() {
GoodsService bean1 = ioc.getBean(GoodsService.class);
bean1.buyGoodsByTxSOLATION();
}
3.原账户表
- 如果没有回滚则money应该减少1000
4.执行测试程序
7.课后练习
1.要求
2.数据表设计
-- seller
CREATE TABLE seller (
seller_id INT UNSIGNED PRIMARY KEY auto_increment,
seller_name VARCHAR(32) NOT NULL DEFAULT'',
money DOUBLE NOT NULL DEFAULT 0.0
);
INSERT INTO seller VALUES(NULL, '李白', 100.0)
SELECT * FROM seller
-- buyer
CREATE TABLE buyer (
buyer_id INT UNSIGNED PRIMARY KEY auto_increment,
buyer_name VARCHAR(32) NOT NULL DEFAULT'',
money DOUBLE NOT NULL DEFAULT 0.0
);
INSERT INTO buyer VALUES(NULL, '杜甫', 100.0)
SELECT * FROM buyer
-- goods
CREATE TABLE goods (
goods_id INT UNSIGNED PRIMARY KEY auto_increment,
goods_name VARCHAR(32) NOT NULL DEFAULT'',
price DOUBLE NOT NULL DEFAULT 0.0,
goods_num INT UNSIGNED DEFAULT 0
);
INSERT INTO goods VALUES(NULL, 100.0, 10, '酒')
SELECT * FROM goods
-- taoBao
CREATE TABLE taoBao (
taoBao_id INT UNSIGNED PRIMARY KEY auto_increment,
goods_name VARCHAR(32) NOT NULL DEFAULT'',
money DOUBLE NOT NULL DEFAULT 0.0
)
SELECT * FROM taoBao
3.代码实现
1.文件目录
2.jdbc.properties
jdbc.user=root
jdbc.pwd=root
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
3.txhomework.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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--容器扫描-->
<context:component-scan base-package="com.sxs.spring.tx"/>
<!--数据库配置-->
<!--引入外部配置文件,读取数据源信息-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="driverClass" value="${jdbc.driver}"/>
</bean>
<!--配置JdbcTemplate对象-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<!--配置数据源属性-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理器对象-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--启动事务管理器注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
4.HomeWorkDao.java
package com.sxs.spring.tx.homework.dao;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
/**
* @author 孙显圣
* @version 1.0
*/
@Repository
public class HomeWorkDao {
@Resource
private JdbcTemplate jdbcTemplate;
//根据卖家id增加money
public int addMoneyBySellerId(Integer seller_id, Double money) {
String sql = "update seller set money = money + ? where seller_id = ?";
return jdbcTemplate.update(sql, money, seller_id);
}
//根据买家id减少money
public int subMoneyByBuyerId(Integer buyer_id, Double money) {
String sql = "update buyer set money = money - ? where buyer_id = ?";
return jdbcTemplate.update(sql, money, buyer_id);
}
//根据id减少商品库存量
public int subGoodsNumByGoodsId(Integer goods_id, Integer goods_num) {
String sql = "update goods se goods_num = goods_num - ? where goods_id = ?";
return jdbcTemplate.update(sql, goods_num, goods_id);
}
//根据id查找商品价格
public Double findPriceById(Integer goods_id) {
String sql = "select price from goods where goods_id = ?";
//public <T> T queryForObject(String sql, @Nullable Object[] args, Class<T> requiredType)
//传入sql, Object[]填充sql的值,Class<T> requiredType传入要返回的类型
Double aDouble = jdbcTemplate.queryForObject(sql, new Object[]{goods_id}, Double.class);
return aDouble;
}
//给taoBao添加一条记录
public int addtoTaoBao(Double money) {
String sql = "insert into taoBao values(?, ?, ?)";
return jdbcTemplate.update(sql, null, "name", money);
}
}
5.HomeWorkService.java
package com.sxs.spring.tx.homework.service;
import com.sxs.spring.tx.homework.dao.HomeWorkDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* @author 孙显圣
* @version 1.0
*/
@Service
public class HomeWorkService {
@Resource
private HomeWorkDao homeWorkDao;
//买家买卖家的商品
@Transactional //启动事务管理器
public boolean getGoods(Integer seller_id, Integer buyer_id, Integer goods_id) {
try {
//1.根据id查找商品价格
Double priceById = homeWorkDao.findPriceById(goods_id);
//2.计算给淘宝的money和卖家得到的money
Double taobao_money = priceById * 0.1;
Double seller_money = priceById * 0.9;
//3.根据id修改卖家的money
homeWorkDao.addMoneyBySellerId(seller_id, seller_money);
//4.根据id修改买家的money
homeWorkDao.subMoneyByBuyerId(buyer_id, priceById);
//5.增加taobao表的记录
homeWorkDao.addtoTaoBao(taobao_money);
//6.减少库存
homeWorkDao.subGoodsNumByGoodsId(goods_id, 1);
} catch (Exception e) {
System.out.println("购买商品逻辑出现问题!");
throw new RuntimeException(e);
}
return true;
}
}
6.测试类
package com.sxs.spring.tx.homework;
import com.sxs.spring.tx.homework.service.HomeWorkService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 孙显圣
* @version 1.0
*/
public class Test {
public static void main(String[] args) {
ApplicationContext ioc = new ClassPathXmlApplicationContext("txhomework.xml");
HomeWorkService bean = ioc.getBean(HomeWorkService.class);
bean.getGoods(1, 1, 1);
}
}
7.原数据表
- seller
- buyer
- goods
- taoBao
8.执行测试类
- 如果执行成功
- 李白money = 190
- 杜甫money = 0
- goods表goods_num = 7
- taoBao表有一条新记录
- 如果执行失败
- 事务回滚