- 一、事务简介
- 二、程序举例环境搭建
- 1. 创建数据表
- 2. maven依赖pom.xml
- 3. 创建实体类
- 4. 定义dao接口
- 5. 定义dao接口对应的sql映射文件
- 6. 定义异常类
- 7. 定义service接口及实现类
- 8. mybatis.xml
- 9. Spring配置文件
- 10. 测试
- 三、使用 Spring 的事务注解管理事务
- 1. 声明事务管理器
- 2. 开启注解驱动
- 3. 完整Spring配置文件
- 4. 业务层 public 方法加入事务属性
- 5. 测试
- 四、使用 AspectJ 的 AOP 配置管理事务
- 1. maven依赖pom.xml
- 2. 在容器中添加事务管理器
- 3. 配置事务通知
- 4. 配置增强器
- 5. 完整Spring配置文件
- 6. 测试类
- 五、总结
- 1. Spring 的事务注解管理事务
- 2. AspectJ 的 AOP 配置管理事务
一、事务简介
具体的看数据库中关于事务的知识点,这里做一个大概
1. 什么是事务?
讲mysql的时候,提出了事务。 事务是指一组sql语句的集合, 集合中有多条sql语句可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。
2. 在什么时候想到使用事务
当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的
在java代码中写程序,控制事务,此时事务应该放在那里呢?
service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句
3. 通常使用JDBC访问数据库, 还是mybatis访问数据库怎么处理事务
jdbc访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback();
mybatis访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback();
hibernate访问数据库,处理事务, Session.commit(); Session.rollback();
4. 问题中事务的处理方式,有什么不足
- 不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理
- 掌握多种数据库中事务的处理逻辑。什么时候提交事务,什么时候回顾事务
- 处理事务的多种方法。
总结: 就是多种数据库的访问技术,有不同的事务处理的机制,对象,方法
5. 怎么解决不足
spring提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。
使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理
使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。
6. 处理事务,需要怎么做,做什么
spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了
-
事务内部提交,回滚事务,使用的事务管理器对象,代替你完成commit,rollback事务管理器是一个接口和他的众多实现类。
接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback
实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
mybatis访问数据库---spring创建好的是DataSourceTransactionManager
hibernate访问数据库----spring创建的是HibernateTransactionManager怎么使用:你需要告诉spring 你用是那种数据库的访问技术,怎么告诉spring呢?
声明数据库访问技术对于的事务管理器实现类, 在spring的配置文件中使用 声明就可以了
例如,你要使用mybatis访问数据库,你应该在xml配置文件中
-
你的业务方法需要什么样的事务,说明需要事务的类型。
1. 说明方法需要的事务:
事务的隔离级别:有4个值。
DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
➢ SERIALIZABLE:串行化。不存在并发问题。- 事务的超时时间: 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚
- 单位是秒, 整数值, 默认是 -1.
- 事务的传播行为 : 控制业务方法是不是有事务的, 是什么样的事务的, 7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的,记得前三个就行
- PROPAGATION_REQUIRED
- PROPAGATION_REQUIRES_NEW
- PROPAGATION_SUPPORTS
- PROPAGATION_MANDATORY
- PROPAGATION_NESTED
- PROPAGATION_NEVER
- PROPAGATION_NOT_SUPPORTED
- 事务提交事务,回滚事务的时机
- 事务的超时时间: 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚
1. 当你的业务方法,执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。事务管理器commit
2. 当你的业务方法抛出运行时异常或ERROR, spring执行回滚,调用事务管理器的rollback
运行时异常的定义: RuntimeException 和他的子类都是运行时异常, 例如NullPointException , NumberFormatException
3. 当你的业务方法抛出非运行时异常, 主要是受查异常时,提交事务
受查异常:在你写代码中,必须处理的异常。例如IOException, SQLException
7. 总结spring的事务
- 管理事务的是 事务管理和他的实现类
- spring的事务是一个统一模型
-
指定要使用的事务管理器实现类,使用
-
指定哪些类,哪些方法需要加入事务的功能
-
指定方法需要的隔离级别,传播行为,超时
你需要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为
二、程序举例环境搭建
购买商品,用户下单,向销售表中添加销售记录,从商品表中减少数据
1. 创建数据表
创建数据表sale(销售表)和goods(商品表)
sale:id自增,方便后面测试
goods:id不自增,
注意,平时开发price用decimal类型
向goods表示适当的添加两天数据,用于测试
2. maven依赖pom.xml
4.0.0
com.md
07-spring-trans
1.0-SNAPSHOT
UTF-8
1.8
1.8
junit
junit
4.11
test
org.springframework
spring-context
5.2.5.RELEASE
org.springframework
spring-tx
5.2.5.RELEASE
org.springframework
spring-jdbc
5.2.5.RELEASE
org.mybatis
mybatis
3.5.1
org.mybatis
mybatis-spring
1.3.1
mysql
mysql-connector-java
5.1.9
com.alibaba
druid
1.1.12
src/main/java
**/*.properties
**/*.xml
false
3. 创建实体类
package com.md.domain;
/**
* @author MD
* @create 2020-08-11 9:20
*/
public class Sale {
private Integer id;
private Integer gid;
private Integer nums;
public Sale() {
}
public Sale(Integer id, Integer gid, Integer nums) {
this.id = id;
this.gid = gid;
this.nums = nums;
}
public void setId(Integer id) {
this.id = id;
}
public void setGid(Integer gid) {
this.gid = gid;
}
public void setNums(Integer nums) {
this.nums = nums;
}
public Integer getId() {
return id;
}
public Integer getGid() {
return gid;
}
public Integer getNums() {
return nums;
}
@Override
public String toString() {
return "SaleDao{" +
"id=" + id +
", gid=" + gid +
", nums=" + nums +
'}';
}
}
//-----------------
package com.md.domain;
/**
* @author MD
* @create 2020-08-11 9:21
*/
public class Goods {
private Integer id;
private String name;
private Integer amount;
//实际开发中不用float
private Float price;
public Goods() {
}
public Goods(Integer id, String name, Integer amount, Float price) {
this.id = id;
this.name = name;
this.amount = amount;
this.price = price;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"id=" + id +
", name='" + name + '\'' +
", amount=" + amount +
", price=" + price +
'}';
}
}
//--------------------------------
4. 定义dao接口
package com.md.dao;
import com.md.domain.Sale;
/**
* @author MD
* @create 2020-08-11 9:24
*/
public interface SaleDao {
// 增加销售记录
int insertSale(Sale sale);
}
//----------------------
package com.md.dao;
import com.md.domain.Goods;
/**
* @author MD
* @create 2020-08-11 9:30
*/
public interface GoodsDao {
// 更新库存
int updateGoods(Goods goods);
// 查询商品的信息,根据id
Goods selectGoods(Integer id);
}
5. 定义dao接口对应的sql映射文件
SaleDao.xml
insert into sale(gid,nums) values(#{gid},#{nums})
GoodsDao.xml
update goods set amount = amount - #{amount} where id=#{id}
6. 定义异常类
定义service层可能抛出的异常类
package com.md.excep;
/**
* @author MD
* @create 2020-08-11 9:49
*/
// 自定义的运行时异常
public class NotEnoughException extends RuntimeException {
public NotEnoughException() {
super();
}
public NotEnoughException(String message) {
super(message);
}
}
7. 定义service接口及实现类
package com.md.service;
/**
* @author MD
* @create 2020-08-11 9:43
*/
public interface BuyGoodsService {
// 购买商品,goodsId:购买商品的编号,nums:购买的数量
void buy(Integer goodsId , Integer nums);
}
//-----------------------------------
package com.md.service.impl;
import com.md.dao.GoodsDao;
import com.md.dao.SaleDao;
import com.md.domain.Goods;
import com.md.domain.Sale;
import com.md.excep.NotEnoughException;
import com.md.service.BuyGoodsService;
/**
* @author MD
* @create 2020-08-11 9:45
*/
public class BuyGoodsServiceImpl implements BuyGoodsService {
private SaleDao saleDao;
private GoodsDao goodsDao;
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("=========buy方法开始===========");
// 记录销售记录,向sale表中添加数据
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
saleDao.insertSale(sale);
// 先查询该商品
Goods goods = goodsDao.selectGoods(goodsId);
if (goods == null){
// 商品不存在
throw new NullPointerException("编号:"+goodsId+" 的商品不存在");
}else if (goods.getAmount() < nums){
// 商品库存不足
throw new NotEnoughException("编号:"+goodsId+" 的商品不足,只能购买: "+goods.getAmount()+" 个");
}
// 更新库存
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(nums);
goodsDao.updateGoods(buyGoods);
System.out.println("=========buy方法结束===========");
}
}
8. mybatis.xml
9. Spring配置文件
applicationContext.xml
10. 测试
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 从容器中获取service,你声明service时候的id
BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService");
buyGoodsService.buy(1001,101);
}
此时程序一切正常
三、使用 Spring 的事务注解管理事务
通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理
主要记住前三个就行了
@Transactional 的所有可选属性如下所示:
- propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED
- isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为Isolation.DEFAULT。
- readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。
- timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。
- rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
- rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组
- noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
- noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
需要注意的是,
@Transactional 若用在方法上,只能用于 public 方法上
对于其他非 public方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。
若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
实现注解的事务步骤:
把上面写好的程序重新复制一份,内容不变
1. 声明事务管理器
还是在Spring的配置文件中加入
2. 开启注解驱动
3. 完整Spring配置文件
applicationContext.xml
4. 业务层 public 方法加入事务属性
在方法上面加@Transactional
这里也就是在service接口的实现类里的方法上,全部代码如下:
package com.md.service.impl;
import com.md.dao.GoodsDao;
import com.md.dao.SaleDao;
import com.md.domain.Goods;
import com.md.domain.Sale;
import com.md.excep.NotEnoughException;
import com.md.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @author MD
* @create 2020-08-11 9:45
*/
public class BuyGoodsServiceImpl implements BuyGoodsService {
private SaleDao saleDao;
private GoodsDao goodsDao;
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
/**
*rollbackFor:表示发生指定的异常一定回滚
*
*/
// @Transactional(
// propagation = Propagation.REQUIRED,
// isolation = Isolation.DEFAULT,
// readOnly = false,
// rollbackFor = {
// NullPointerException.class,NotEnoughException.class
// }
// )
// 都使用默认值也是可以的,默认的传播行为是REQUIRED,默认的隔离级别是DEFAULT
// 默认抛出运行时异常,回滚事务
@Transactional
@Override
public void buy(Integer goodsId, Integer nums) {
System.out.println("=========buy方法开始===========");
// 记录销售记录,向sale表中添加数据
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(nums);
saleDao.insertSale(sale);
// 先查询该商品
Goods goods = goodsDao.selectGoods(goodsId);
if (goods == null){
// 商品不存在
throw new NullPointerException("编号:"+goodsId+" 的商品不存在");
}else if (goods.getAmount() < nums){
// 商品库存不足
throw new NotEnoughException("编号:"+goodsId+" 的商品不足,只能购买: "+goods.getAmount()+" 个");
}
// 更新库存
Goods buyGoods = new Goods();
buyGoods.setId(goodsId);
buyGoods.setAmount(nums);
goodsDao.updateGoods(buyGoods);
System.out.println("=========buy方法结束===========");
}
}
5. 测试
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 从容器中获取service,你声明service时候的id
BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService");
// jdk动态代理对象com.sun.proxy.$Proxy16
//System.out.println(buyGoodsService.getClass().getName());
buyGoodsService.buy(1003,100);
}
假设此时的1003号商品不存在,即使上面已经向sale表中添加数据了,但是由于还没有执行到更新库存方法的时候出现了异常,此时由于添加了事务,这个时候就会回滚,你查看sale的数据表没有数据,但是你之后买一个存在的商品,发现id号已经不是连续的了,如图所示:
由于sale的id设置的自增,就是因为回滚的原因,id不连续
四、使用 AspectJ 的 AOP 配置管理事务
使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类较多,配置文件会变得非常臃肿。
使用 XML 配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可
还是使用上面举例的程序,复制一份
1. maven依赖pom.xml
还是直接把完整的pom.xml放这,主要就是添加了一个aspectj的
4.0.0
com.md
09-spring-trans-aspectj
1.0-SNAPSHOT
UTF-8
1.8
1.8
junit
junit
4.11
test
org.springframework
spring-aspects
5.2.5.RELEASE
org.springframework
spring-context
5.2.5.RELEASE
org.springframework
spring-tx
5.2.5.RELEASE
org.springframework
spring-jdbc
5.2.5.RELEASE
org.mybatis
mybatis
3.5.1
org.mybatis
mybatis-spring
1.3.1
mysql
mysql-connector-java
5.1.9
com.alibaba
druid
1.1.12
src/main/java
**/*.properties
**/*.xml
false
2. 在容器中添加事务管理器
还是在Spring的配置文件中加入
3. 配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。
例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务
4. 配置增强器
指定将配置好的事务通知,织入给谁
5. 完整Spring配置文件
applicationContext.xml
6. 测试类
@Test
public void test01(){
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
// 从容器中获取service,你声明service时候的id
BuyGoodsService buyGoodsService = (BuyGoodsService) ac.getBean("buyGoodsService");
buyGoodsService.buy(1001,10);
}
五、总结
1. Spring 的事务注解管理事务
适合中小项目使用的注解方案
spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务。
@Transactional注解是spring框架自己注解,放在public方法的上面,表示当前方法具有事务。
可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等
使用@Transactional的步骤:
-
需要声明事务管理器对象
-
开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务
- spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能
- spring给业务方法加入事务:在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知
@Around("你要增加的事务功能的业务方法名称") Object myAround(){ 开启事务,spring给你开启 try{ buy(1001,10); spring的事务管理器.commit(); }catch(Exception e){ spring的事务管理器.rollback(); } }
-
在你的方法的上面加入@Trancational
2. AspectJ 的 AOP 配置管理事务
适合大型项目,有很多的类,方法,需要大量的配置事务,使用aspectj框架功能,在spring配置文件中声明类,方法需要的事务
这种方式业务方法和事务配置完全分离
实现步骤: 都是在xml配置文件中实现
-
要使用的是aspectj框架,需要加入依赖
org.springframework spring-aspects 5.2.5.RELEASE -
声明事务管理器对象
-
声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)
-
配置aop:指定哪些哪类要创建代理