spring的事务管理

 

一:基于注解的方式

  1:在spring整合mybatis的.xml文件中进行如下配置

 1 <!--基于注解的方式进行事务管理。-->
 2     <bean name="transactionManager"
 3           class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 4         <property name="dataSource" ref="pooledDataSource"></property>
 5     </bean>
 6     <!--proxy-target-class="true" 不加上的话会出现Unsatisfied dependency expressed through field 'accountServiceimpl';
 7         字段accountServiceimpl不通过依赖关系。这个错误。
 8         这样就可以实现基于实现类类接收,proxy-target-class 默认未false 不过要这么实现的话,你得引入CGLIB来做代理才行。
 9     -->
10     <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

  没有加proxy-target-class抛出了这个异常:

  

 

  2:然后在要添加事务的方法上加上注解就行了

正常情况下。sourceName输出1000.targetName得到1000.

执行前:数据库的值

执行后:

 

现在加上异常。A移除1000后,抛出异常,然后B得不到1000。如果不加事务的话,A减少1000.B不加1000.

加上事务后。A不减少。B也不增加

 二:基于配置的方式进行事务管理

 1 <!--创建事务管理器-->
 2     <bean name="transactionManager"
 3           class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 4         <!--用事务管理器去管理数据源-->
 5         <property name="dataSource" ref="pooledDataSource"></property>
 6     </bean>
 7     <!--配置通知。哪些方法需要切入什么类型的事务。-->
 8     <tx:advice id="advice" transaction-manager="transactionManager">
 9         <tx:attributes>
          //这个是service的impl中的实现类下的所有方法都会加上事务 10 <tx:method name="*" propagation="REQUIRED"/> 11 </tx:attributes> 12 </tx:advice> 13 14 <!--配置切面表达式, 并且让 tx与切面表达式合二为一--> 15 <aop:config proxy-target-class="true"> 16 <!--表达式, 定义哪个包的哪些类需要切入事务,但是此处并且没有制定类中哪些方法,需要切入什么样 事务--> 17 <aop:pointcut expression="execution(* com.ssm.service.impl.*.*(..))" id="pointcut" /> 18 <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> 19 </aop:config>

 

 

三:事务的隔离级别和传播行为

isolation:事务隔离级别

第1级别:Read Uncommitted(读取未提交内容)

          事务A更新了一条数据,但是没有提交。B读取的是A更新后的数据。

第2级别:Read Committed(读取提交内容)

          事务A更新了一条数据,但是没有提交,B读取的是没有更新前的数据。只有提交后,读取的才是A更新的数据。

第3级别:Repeatable Read(可重读)

            事务A更新了一条数据,也提交了。但是B还是读取不到A更新的数据。

第4级别:Serializable(可串行化)

           在事务A没有commit前。B是打不开的。

  传播行为

1、PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

 

2、PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘

 

3、PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

 

4、PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

 

5、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

 

6、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

 

7、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

1.脏读(Dirty Read)——一个事务读取到了另外一个事务没有提交的数据。

 

详细解释:当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。

事务T1:更新一条数据
          -->事务T2:读取事务T1更新的记录
 事务T1:调用commit进行提交
 此时事务T2读取到的数据是保存在数据库内存中的数据,称为脏数据,这个过程称为脏读。

脏读发生在一个事务A读取了被另一个事务B修改,但是还未提交的数据。假如B回退,则事务A读取的是无效的数据。这跟不可重复读类似,但是第二个事务不需要执行提交。

解决脏读问题:修改时加排他锁,直到事务提交后才释放,读取时加共享锁,读取完释放事务1读取数据时加上共享锁后(这样在事务1读取数据的过程中,其他事务就不会修改该数据),不允许任何事务操作该数据,只能读取,之后1如果有更新操作,那么会转换为排他锁,其他事务更无权参与进来读写,这样就防止了脏读问题。但是当事务1读取数据过程中,有可能其他事务也读取了该数据,读取完毕后共享锁释放,此时事务1修改数据,修改完毕提交事务,其他事务再次读取数据时候发现数据不一致,就会出现不可重复读问题,所以这样不能够避免不可重复读问题。

2.幻读(Phantom——同一事务中,用同样的操作读取两次,得到的记录数不相同。 

 

详细解释:幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

事务T1:查询表中所有记录
          -->事务T2:插入一条记录
          -->事务T2:调用commit进行提交
事务T1:再次查询表中所有记录
            
此时事务T1两次查询到的记录是不一样的,称为幻读。

注意:幻读重点在新增或删除。

幻读发生在当两个完全相同的查询执行时,第二次查询所返回的结果集跟第一个查询不相同。

发生的情况:没有范围锁。

如何避免:实行序列化隔离模式,在任何一个低级别的隔离中都可能会发生。

解决幻读问题:采用的是范围锁RangeS RangeS_S模式,锁定检索范围为只读,这样就避免了幻读问题。

3.不可重复读(Nonrepeatable Read)——在同一事务中,两次读取同一数据,得到内容不同。

事务T1:查询一条记录
         -->事务T2:更新事务T1查询的记录
         -->事务T2:调用commit进行提交
事务T1:再次查询上次的记录
            
此时事务T1对同一数据查询了两次,可得到的内容不同,称为不可重复读。

注意:不可重复读重点在修改。

在基于锁的并行控制方法中,如果在执行select时不添加读锁,就会发生不可重复读问题。

在多版本并行控制机制中,当一个遇到提交冲突的事务需要回退但却被释放时,会发生不可重复读问题。

有两个策略可以防止这个问题的发生:

(1) 推迟事务2的执行,直至事务1提交或者回退。这种策略在使用锁时应用。

(2) 而在多版本并行控制中,事务2可以被先提交,而事务1继续执行在旧版本的数据上。当事务1终于尝试提交时,数据库会检验它的结果是否和事务1、事务2顺序执行时一样。如果是,则事务1提交成功;如果不是,事务1会被回退。

解决不可重复读问题:读取数据时加共享锁,写数据时加排他锁,都是事务提交才释放锁。读取时候不允许其他事物修改该数据,不管数据在事务过程中读取多少次,数据都是一致的,避免了不可重复读问题。
4.丢失更新(Lost Update) 

事务T1读取了数据,并执行了一些操作,然后更新数据。事务T2也做相同的事,则T1和T2更新数据时可能会覆盖对方的更新,从而引起错误。

5.处理以上隔离级别的问题,采用如下方法:

  事务隔离五种级别:
        (1)TRANSACTION_NONE  不使用事务。
        (2)TRANSACTION_READ_UNCOMMITTED  允许脏读。
        (3)TRANSACTION_READ_COMMITTED  防止脏读,最常用的隔离级别,并且是大多数数据库的默认隔离级别。
        (4)TRANSACTION_REPEATABLE_READ  可以防止脏读和不可重复读。
        (5)TRANSACTION_SERIALIZABLE  可以防止脏读,不可重复读取和幻读,(事务串行化)会降低数据库的效率。

  以上的五个事务隔离级别都是在Connection接口中定义的静态常量,使用setTransactionIsolation(int level) 方法可以设置事务隔离级别。

  如:con.setTransactionIsolation(Connection.REPEATABLE_READ)。

  注意:事务的隔离级别受数据库的限制,不同的数据库支持的的隔离级别不一定相同。

 

posted @ 2018-04-09 11:58  陆伟  阅读(146)  评论(0编辑  收藏  举报