事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作,这些操作作为一个整体像系统提交,要么都执行,要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)
通俗点说就是为了达到某个目的而做的一系列的操作要么一起成功(事务提交),要么一起失败(事务回滚)
最常见的例子就是转账:
小明给如花转账:
开启事务-------
① 从小明的账户扣除1000元
② 给如花的账户增加1000元
事务提交-------
从上面的例子的任何步骤一旦出现问题,都会导致事务回滚。
从搭讪到结婚就是事务提交,女方要求男方重新追求她一次就是事务回滚~
ACID是事务的基本特征:口诀(一原持久隔离)
try {
// 设置是否自动提交
connection.setAutoCommit(false)
// 数据库操作 insert,update,delete
connection.commit()
} catch(Exception ex) {
// 回滚
connection.rollback()
} finally {
connection.setAutoCommit(true)
}
数据库事务的隔离级别有4中,由低到高分别是:Read uncomomitted(读取未被提交的数据)
、Read committed(读取以被提交的数据)
、Repeatable read(可重复读)
、Serializable(完全隔离)
。而且,在事务的并发操作中,可能出现脏读、不可重复读、幻读、事务丢失
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据,会产生脏读
这种使用的概率比较高,因为很多时候我们就以最后一次读取的为准
读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据,会产生不可重复读。
相当于加锁,MySQL的默认级别
重复读,就是在开始读取数据(事务开启)时,不在允许修改操作,可能会产生幻读。
最高的事务隔离级别,在改级别下,事务串行化顺序执行,可以避免脏读,不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用
大多数数据库默认的事务隔离级别是:Read Committed
,比如SqlServer,Oracle
MySQL的默认隔离级别是Repeatable Read
读取了未提交的新事务,然后被回滚了
事务A读取了事务B中尚未提交的数据,如果事务B回滚,则A读取使用了错误的数据
读取了提交的新事物,指更新操作
不可重复读是指对于数据库中某个数据,一个事务范围内多次查询却反悔不同的数值,这是由于在查询间隔,被另一个事务修改并提交了
解决不可重复读的一个解决方案,就是调整隔离级别。
读取了提交的新事物,指增删操作
在事务A多次读取构成中,事务B对数据进行了新增操作,导致事务A多次读取的数据不一致
对于幻读的另外一个理解,可以为:
幻读就是指新增了数据记录条数,第一次查询数据记录数为1000,再次查询的时候,变成了1001,这个就是幻读
很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert
避免不可重复读需要锁行就行
避免幻读则需要锁表
所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题
称为:回滚丢失
对于第一类事务丢失,就是比如A和B同时在执行一个数据,然后B事务已经提交了,然后A事务回滚了,这样B事务的操作就因A事务回滚而丢失了。
称为:覆盖丢失
对于第二类事务丢失,也称为覆盖丢失,就是A和B一起执行一个数据,两个同时取到一个数据,然后B事务首先提交,但是A事务接下来又提交,这样就覆盖了B事务
具体说出一些关键的xml元素
准备数据表:
实体类:
public class User {
private Integer id;
private String name;
private Integer money;
}
Dao:
Service:默认一个出错的场景
XML配置事务:在applicationContext.xml中添加事务管理器和事务管理器模板的配置
注解方式配置事务管理器和事务管理器模板
基于AspectJ XML方式
注:基于TransactionProxyFactoryBean,代理的方式是比较古老的方式,我们这里就不叙述了
删除applicationContext.xml中的事务管理模版的配置,就是下面的配置:
<!-- 事务管理的模板 -->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
添加事务定义和AOP配置
<!--基于AspectJ 申明式事务XML配置方式-->
<!-- 定义一个增强 -->
<tx:advice id="txAdvice"
transaction-manager="transactionManager">
<!-- 增强(事务)的属性的配置 -->
<tx:attributes>
<!-- isolation:DEFAULT ,事务的隔离级别。
propagation:事务的传播行为.
read-only:false,不是只读
timeout:-1
no-rollback-for:发生哪些异常不回滚
rollback-for:发生哪些异常回滚事务
-->
<tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- aop配置定义切面和切点的信息 -->
<aop:config>
<!-- 定义切点:哪些类的哪些方法应用增强 -->
<aop:pointcut
expression="execution(* com.dk.beans.service..*.*(..))"
id="mypointcut" />
<!-- 定义切面: -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypointcut" />
</aop:config>
业务类改成原来的方式
/**
* 沒有事務的转账的业务
* @param fromName
* @param toName
* @param money
*/
public void transfer(String fromName, String toName, Integer money) {
userDAO.out(fromName, money);// 转出钱
int x = 10;
if(x == 10)
throw new RuntimeException("出错啦!");
userDAO.in(toName, money);// 收入钱
}
基于注解的申明式事务:
在配置类上配置@EnableTransactionManagement开启事务。删除注解类中和事务相关的@Bane
在UserService类上方或者方法上方通过@Transactional完成事务配置:
@Service
@Transactional
public class UserService {
}
申明式事务可以按照对应什么开头的方法 给配置事务
指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。举例子
public class PersonService {
@Transactional
public void laoda(){
System.out.println("老大的方法");
xiaodi();
}
@Transactional
public void xiaodi(){
System.out.println("小弟方法");
}
}
也就是当老大的方法,调用小弟的方法时,小弟的事务该怎么办?
假设老大的方法出现异常,那么小弟的需不需要回滚?
假设小弟的方法出现异常,那么老大的放法需不需要回滚?