针对我之前的个人博客我详细的分析了spring的aop的原理,作用。我们现在在来想想,我们经常对数据操作,一旦多表操作时可能会出现一些问题。我们希望有一个东西可以来解决,要成功一起成功,要失败一起失败对吧。这个是我们生活中最常见的。作为程序员,是不是有一种肯定有大神一种想到了。它是在哪里呢?spring的框架中就已经自带了这个功能,那就是spring的aop的事物。它就是做这个的。这就是事物的作用。
大家在想既然是springaop的事物,肯定有切入点,切面,代理对象(目标对象),连接点,织入,引入这一系列的点。既然框架我写好了,切面肯定大家用的同一个,剩下的需要看我自己的项目自己定义。
大家在思考一个问题,既然事物是对数据操作的话,而且每一个用户访问的话,就是一个线程。我们是不是需要有时数据改完了,而且下个用户可以根据上面别改的数据接着访问。或者如果当前事物在运行,这时用户来访问,事物是接着创建呢?还是等待呢?而且假如有时连接时,不能无限制的等待下去吧?我们自己能想到这些,作为spring的框架开发者们,肯定也想到了。应运而生,事物的特性就产生了,在这里可以根据自己的项目的具体需求,来自己做相应的处理。
现在我们来看看事物的特性:
事务的四大特征(ACID原则):
1.原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做
2.一致性:数据不会因为事务的执行而遭到破坏
3.隔离性:一个事物的执行,不受其他事务的干扰,即并发执行的事物之间互不干扰
4.持久性:一个事物一旦提交,它对数据库的改变就是永久的
以上介绍完事务的四大特性(简称ACID),现在重点来说明下事务的隔离性,当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题:
1,脏读
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:用户A向用户B转账100元,对应SQL命令如下
update account set money=money+100 where name=’B’; (此时A通知B)
update account set money=money - 100 where name=’A’;
当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。
2,不可重复读
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了……
3,虚读(幻读)
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
五个隔离级别
1.default:默认的事务隔离级别,跟具体的数据有关,mysql默认的事务隔离级别是repeatable_read
2.read_uncommitted: 读未提交,一个事务可以感知或者操作另外一个未提交的事务,可能会出现脏读、不可重复读、幻读
3.read_committed:读已提交,一个事务只能感知或者操作另一个已经提交的事务,可能会出现不可重复读、幻读
4.repeatable_read:可重复读,能够避免脏读,不可重复读,不能避免幻读
4.serializable:串行化,隔离级别最高,消耗资源最低,代价最高,能够防止脏读, 不可重复读,幻读。
七个传播特性
1、Propagation.REQUIRED
调用方已经存在事务,则加入到同一个事务中运行,否则,自启一个事务
2、Propagation.REQUIRES_NEW
无论何时自身都会开启新事务
3、Propagation.SUPPORTS
调用方存在事务,则加入到同一个事务中运行,若不存在事务,则以非事务的方式运行
4、Propagation.NOT_SUPPORTED
调用方存在事务,则会被挂起,直到被调用方运行完毕后,事务恢复。
5、Propagation.MANDATORY
调用方存在事务,则加入到同一个事务中运行,若不存在,则抛出异常
6、Propagation.NEVER
调用方存在事务,则抛出异常
7、Propagation.NESTED
若调用方存在事务,则运行一个嵌套事务,若调用方不存在事务,则以Propagation.REQUIRED的方式运行,即开启一个新的事务
现在来看看MySQL数据库为我们提供的四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
- TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
2.传播行为怎么理解,好比A和B两个线程的事物,如果A创建了事物,B是直接用A的呢,还是自己在创建一个事物的呢?这些我们自己说的算
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
3.事物的时间这个很好理解,好比我们定义执行事物的时间一个小时,结果超时了。事物关闭了,然后回滚。
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
4.事物回滚,好比一旦抛出了异常,我们之前所有执行的操作。全部当做没执行过。这个我们具体可以定义那些异常回滚
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
5.事物的只读,这个简单。你只能读数据,而不能改。正常默认情况,事物是读写的
“只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。
因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可
以下是我们常用的2中简单的事物的2种声明方式:
事物需要的jar是mybatis-spring.jar
1.使用tx标签配置的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:p
=
"http://www.springframework.org/schema/p"
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-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<
context:annotation-config
/>
<
tx:annotation-driven
transaction-manager
=
"transactionManager"
/>
<
context:component-scan
base-package
=
"com.edw.springmybatis.service"
/>
//这里可以放在一个文件的
<!-- middleware datasource -->
<
bean
id
=
"dataSource"
class
=
"org.apache.commons.dbcp.BasicDataSource"
destroy-method
=
"close"
p:driverClassName
=
"com.mysql.jdbc.Driver"
p:url
=
"jdbc:mysql://localhost/test"
p:username
=
"root"
p:password
=
""
p:initialSize
=
"2"
p:maxActive
=
"30"
p:maxIdle
=
"10"
p:minIdle
=
"3"
p:maxWait
=
"30000"
p:removeAbandoned
=
"true"
p:removeAbandonedTimeout
=
"30"
p:validationQuery
=
"SELECT 1"
/>
//通过Configuration来创建sqlSessionFactory
<
bean
id
=
"sqlSessionFactory"
class
=
"org.mybatis.spring.SqlSessionFactoryBean"
>
//这里可以抽成一个文件的
<
property
name
=
"dataSource"
ref
=
"dataSource"
/>
//这里是读取bean.xml的
<
property
name
=
"configLocation"
value
=
"/WEB-INF/configuration.xml"
/>
</
bean
>
//切面
<
bean
id
=
"transactionManager"
class
=
"org.springframework.jdbc.datasource.DataSourceTransactionManager"
>
<
property
name
=
"dataSource"
ref
=
"dataSource"
/>
</
bean
>
- <!-- 事务相关控制配置:例如配置事务的传播机制 -->
- //通知
- <tx:advice id="iccardTxAdvice" transaction-manager="transactionManager">
- <tx:attributes>
- <tx:method name="delete*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception" no-rollback-for="java.lang.RuntimeException"/>
- <tx:method name="insert*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.RuntimeException" />
- <tx:method name="add*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.RuntimeException" />
- <tx:method name="create*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.RuntimeException" />
- <tx:method name="update*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception" />
- <tx:method name="find*" propagation="SUPPORTS" />
- <tx:method name="get*" propagation="SUPPORTS" />
- <tx:method name="select*" propagation="SUPPORTS" />
- <tx:method name="query*" propagation="SUPPORTS" />
- </tx:attributes>
- </tx:advice>
- <!-- 把事务控制在service层 -->
- <aop:config>
- //切入点
- <aop:pointcut id="iccardTerm" expression="execution(public * com.shfft.iccardterm.service.*.*(..))" /> //连接点和切入点
- <aop:advisor pointcut-ref="iccardTerm" advice-ref="iccardTxAdvice" />
- </aop:config>
</
beans
>
2.通过注解的方式,这个也需要配置xml的
- <!-- 定义事务管理器 -->
- <bean id="transactionManager"
- class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource" />
- //这里是读取bean.xml的
<
property
name
=
"configLocation"
value
=
"/WEB-INF/configuration.xml"
/>
- </bean>
- <!--使用注释事务 -->
- <tx:annotation-driven transaction-manager="transactionManager" />
感谢作者我这里好多是总结他们的知识点:
1.https://www.cnblogs.com/aflyun/p/6421441.html?utm_source=itdadao&utm_medium=referral
2.https://blog.csdn.net/bear_wr/article/details/52730855
3.https://blog.csdn.net/jin5203344/article/details/52918986
4.https://blog.csdn.net/bao19901210/article/details/41724355