事务基本特性
- 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
- 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
- 隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
- 持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
事务的隔离级别
- read uncommitted 读未提交
一个事务可以读取另一个未提交事务的数据。会引发:脏读、不可重复读、幻读。
示例:小明去商店买衣服,付款的时候,小明正常付款,钱已经打到商店老板账户,但是小明发起的事务还没有提交。就在这时,商店老板查看自己账户,发现钱已到账,于是小明正常离开。小明在走出商店后,马上回滚差点提交的事务,撤销了本次交易曹邹。
结果:小明未付钱买到了衣服,商店老板实际未收到小明的付款。
分析:商店老板查看自己的资金账户,这个时候看到的是小明还没有提交事务的付款。这就是脏读。
- read committed 读已提交
一个事务要等另一个事务提交后才能读取数据。会引发:不可重复读、幻读。
示例:小明卡里有1000元,准备与几个朋友聚餐消费,消费1000元,当他买单时(事务开启),收费系统检测到他卡里有1000元。就在检测完毕的时候,小明女朋友发现小明有私房钱,全部转走并提交。当收费系统准备扣款时,再检查小明卡里的金额,发现已经没钱了,付款不成功。小明此时就会很纳闷,明明有钱的呀,钱呢?
- repeatable read 可重复读
在开始读取数据(事务开启)时,不再允许修改操作。会引发:幻读。
示例:小明在公司上班,女朋友告诉他,拿着他的卡去逛街消费。花了一千元,然后小明去查看他银行卡的消费记录(事务开启),看到确实是花了一千元。就在这个时候,小明女朋友又花三千元买了一些化妆品和衣服,即新增了一些消费记录。当小明打印自己银行卡消费记录单的时候(女朋友事务提交),发现花了四千元,似乎出现了幻觉,小明很心疼。这就是幻读。
- serializable 串行化
数据库事务的最高隔离级别。在此级别下,事务串行执行。可以避免脏读、不可重复读、幻读等读现象。但是效率低下,耗费数据库性能,不推荐使用。
编程式事务实现
1、使用编程事务实现手动事务
@Component
public class TransactionUtils {
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
// 开启事务
public TransactionStatus begin() {
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
return transaction;
}
// 提交事务
public void commit(TransactionStatus transactionStatus) {
dataSourceTransactionManager.commit(transactionStatus);
}
// 回滚事务
public void rollback(TransactionStatus transactionStatus) {
dataSourceTransactionManager.rollback(transactionStatus);
}
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private TransactionUtils transactionUtils;
public void add() {
TransactionStatus transactionStatus = null;
try {
transactionStatus = transactionUtils.begin();
userDao.add("wangmazi", 27);
int i = 1 / 0;
System.out.println("我是add方法");
userDao.add("zhangsan", 16);
transactionUtils.commit(transactionStatus);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (transactionStatus != null) {
transactionStatus.rollbackToSavepoint(transactionStatus);
}
}
}
}
2、AOP技术封装手动事务
@Component
@Aspect
public class AopTransaction {
@Autowired
private TransactionUtils transactionUtils;
// // 异常通知
@AfterThrowing("execution(* com.wangcj.service.UserService.add(..))")
public void afterThrowing() {
System.out.println("程序已经回滚");
// 获取程序当前事务 进行回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
// 环绕通知
@Around("execution(* com.wangcj.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("开启事务");
TransactionStatus begin = transactionUtils.begin();
proceedingJoinPoint.proceed();
transactionUtils.commit(begin);
System.out.println("提交事务");
}
}
声明事务实现
自定义注解实现事务
1、自定义注解
// 事务注解 设置传播行为
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtTransaction {
}
2、编写事务开启、提交、关闭工具类
// 编程事务(需要手动begin 手动回滚 手都提交)
@Component
@Scope("prototype") // 每个事务都是新的实例 目的解决线程安全问题 多例子
public class TransactionUtils {
// 全局接受事务状态
private TransactionStatus transactionStatus;
// 获取事务源
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
// 开启事务
public TransactionStatus begin() {
System.out.println("开启事务");
transactionStatus = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
return transactionStatus;
}
// 提交事务
public void commit(TransactionStatus transaction) {
System.out.println("提交事务");
dataSourceTransactionManager.commit(transaction);
}
// 回滚事务
public void rollback() {
System.out.println("回滚事务...");
dataSourceTransactionManager.rollback(transactionStatus);
}
}
3、使用aop实现拦截
@Autowired
private TransactionUtils transactionUtils;
@AfterThrowing("execution(* com.wangcj.service.*.*.*(..))")
public void afterThrowing() throws NoSuchMethodException, SecurityException {
// isRollback(proceedingJoinPoint);
System.out.println("程序发生异常");
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// TransactionStatus currentTransactionStatus =
// TransactionAspectSupport.currentTransactionStatus();
// System.out.println("currentTransactionStatus:" +
// currentTransactionStatus);
transactionUtils.rollback();
}
// // 环绕通知 在方法之前和之后处理事情
@Around("execution(* com.wangcj.service.*.*.*(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 调用方法之前执行
TransactionStatus transactionStatus = begin(proceedingJoinPoint);
proceedingJoinPoint.proceed();// 代理调用方法 注意点: 如果调用方法抛出异常不会执行后面代码
// 调用方法之后执行
commit(transactionStatus);
}
public TransactionStatus begin(ProceedingJoinPoint pjp) throws NoSuchMethodException, SecurityException {
// // 判断是否有自定义事务注解
ExtTransaction declaredAnnotation = getExtTransaction(pjp);
if (declaredAnnotation == null) {
return null;
}
// 如果有自定义事务注解,开启事务
System.out.println("开启事务");
TransactionStatus transactionStatu = transactionUtils.begin();
return transactionStatu;
}
public void commit(TransactionStatus transactionStatu) {
if (transactionStatu != null) {
// 提交事务
System.out.println("提交事务");
transactionUtils.commit(transactionStatu);
}
}
public ExtTransaction getExtTransaction(ProceedingJoinPoint pjp) throws NoSuchMethodException, SecurityException {
// 获取方法名称
String methodName = pjp.getSignature().getName();
// 获取目标对象
Class<?> classTarget = pjp.getTarget().getClass();
// 获取目标对象类型
Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
// 获取目标对象方法
Method objMethod = classTarget.getMethod(methodName, par);
// // 判断是否有自定义事务注解
ExtTransaction declaredAnnotation = objMethod.getDeclaredAnnotation(ExtTransaction.class);
if (declaredAnnotation == null) {
System.out.println("您的方法上,没有加入注解!");
return null;
}
return declaredAnnotation;
}
// 回滚事务
public void isRollback(ProceedingJoinPoint pjp) throws NoSuchMethodException, SecurityException {
// // 判断是否有自定义事务注解
ExtTransaction declaredAnnotation = getExtTransaction(pjp);
if (declaredAnnotation != null) {
System.out.println("已经开始回滚事务");
// 获取当前事务 直接回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return;
}
}
4、使用注解控制事务
@ExtTransaction
public void add() {
userDao.add("test001", 20);
int i = 1 / 0;
System.out.println("################");
userDao.add("test002", 21);
}