什么是事务?
是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);
多条sql语句,在同一个连接下,要么都执行,要么都不执行
既然说起了事务,就不得不说事务的四大特性,他们分别是:
-
原子性(Atomicity)
-
一致性(Consistency)
-
隔离性(Isolation)
-
持久性(durability)
原子性:
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
一致性:
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
隔离性:
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
持久性:
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
在java中,开启事务着重配置的两点就是隔离性和事务的传播途径。
下边来看看隔离性,如果不配置,会有什么样的结果:
1、脏读:
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。
简单说,a读取了b未提交的数据,就是发生了脏读;
2、不可重复读
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
简单来说,就i是两次查询语句查询出来的数据不一样,两次查询之间有人修改数据库了。
3、虚读(幻读)
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
简单来说,就是两次查询期间有人插入或者删除数据 了,造成可能我两次查询或者别的操作得出的数据条数不一致。
为了解决上述的三种问题,数据库为我们提供了四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
说完隔离性,下边我们来看看事务的七种传播级别:
PROPAGATION_NOT_SUPPORTED
不会受到父类事务影响而回滚,自己也不会影响父类函数,出现异常后会自动回滚。
PROPAGATION_REQUIRES_NEW
不会受到父类事务影响而回滚,自己也不会影响父类函数,出现异常后会自动回滚。
NESTED
会受到父类事务影响而回滚,出现异常后自身也回滚。如果不希望影响父类函数,那么可以通过使用try catch来控制操作。
MANDATORY
强制使用当期的事物,如果当前的父类方法没有事务,那么在处理数据的时候就会抛出异常
NEVER
当前如果存在事务则抛出异常
REQUIRED(默认) 默认的事务传播机制,如果当前不支持事务,那么就创建一个新的事务。
SUPPORTS 表示支持当前的事务,如果当前没有事务,则不会单独创建事务
下边来看看如何在spring中开启事务:
1.开启事务管理器spring中的mybatis xml配置文件
<!-- 数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/world"></property>
<property name="username" value="root"></property>
<property name="password" value="476988"></property>
</bean>
<!--
DataSourceTransactionManager:配置数据源事务管理器
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
2.使用事务的两种方式
第一种
a.开起事务注解
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
b、使用aop切面绑定事务:(用的少)
<!--开启事务注解 第二种方式-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- name:*表示任意方法名称 -->
<tx:method name="*" propagation="REQUIRED"
isolation="DEFAULT" read-only="false" />
</tx:attributes>
</tx:advice>
<!-- 6.编写aop,让spring自动对目标生成代理,需要使用AspectJ的表达式 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* com.wgz.spring.service.AccountServiceImpl.transMoney(..))"
id="txPointCut" />
<!-- 切面:将切入点与通知整合 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
</aop:config>
.使用 @Transactional 配置事务
@Transactional //用此注解就表明当前方法开启了事务 , 方法内执行的sql语句执行同一个sqlsession
( propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED
)
/* Propagation.REQUIRED 含义:我当前方法必须再事务内 propagation:隔离传播行为
一个开启事务的方法可以调用其他开启事务的方法,其他开启事务的方法也可以调用这个开启事务的方法。
相互调用之间使用同一个sqlsession链接。
普通方法调用事务也是如此
Propagation.REQUIREDNEW:不管我之前调用的方法等等有没有开启事务,我这里都创建一个新的事务。创建一个
事务的几大特性:
一致性
原子性
隔离性 两个事务之间的不受影响的程度()
持久性 数据库崩亏后 机器重启后 数据不丢失
isolation: 事务的隔离级别
READ_COMMITTED:只有别人提交了你才可以读取到
READ_UNCOMMITTED:读取不提交的数据
REPLACE_READ :
* */
/* com.aaa.springmvc.service.AccountSerImol
* * com.aaa.springmvc.service.*.*(..)
* */
@Override
public void transMoney(int from,int to, float money) {
accountDao.updateAccount(from,-money);
int i=1/0;
accountDao.updateAccount(to,money);
}