Spring的事务传播性与隔离级别

Spring的事务传播性与隔离级别

 

一、事务的四个特性

原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做,要么全部做。

一致性:数据不会因为事务的执行而遭到破坏。

隔离性:一个事务的执行,不受其他事务(进程)的干扰。既并发执行的个事务之间互不干扰。

持久性:一个事务一旦提交,它对数据库的改变将是永久的。

 

二、事务的实现方式

      实现方式共有两种:编码方式;声明式事务管理方式。

      基于AOP技术实现的声明式事务管理,实质就是:在方法执行前后进行拦截,然后在目标方法开始之前创建并加入事务,执行完目标方法后根据执行情况提交或回滚事务。

      声明式事务管理又有两种方式:一种是基于XML配置文件的方式;另一种是在业务方法上添加@Transactional注解,将事务规则应用到业务逻辑中(一般定义在service层)。

 

三、创建事务的时机

      是否需要创建事务,是由事务传播行为控制的。读数据不需要或只为其指定只读事务,而数据的插入、修改、删除就需要进行事务管理了,这是由事务的隔离级别控制的。

      事务具有7个传播级别和4个隔离级别,传播级别定义的是事务创建的时机,隔离级别定义的是对并发事务数据读取的控制。

 

四、事务的传播级别

      以下是事务的7种传播级别:

PROPAGATION_REQUIRED

默认的Spring事务传播级别,使用该级别的特点是:如果上下文中已经存在事务,那么就加入到事务中执行;如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。

PROPAGATION_SUPPORTS

从字面意思就知道,supports,支持,该传播级别的特点是:如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包含在TransactionTemplate.execute方法中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作。应用场景较少。

PROPAGATION_MANDATORY

该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。

PROPAGATION_REQUIRES_NEW

从字面即可知道,new,每次都要一个新事务,该传播级别的特点是:每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。

怎么处理整个业务需求呢?就是通过这个PROPAGATION_REQUIRES_NEW级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。

PROPAGATION_NOT_SUPPORTED

这个也可以从字面得知,not supported,不支持,当前级别的特点是:若上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。

这个级别有什么好处?可以帮助你将事务尽可能的缩小。我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况,所以事务的这个传播级别就派上用场了。用当前级别的事务模板包含起来就可以了。

PROPAGATION_NEVER

该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行!这个级别上辈子跟事务有仇。

PROPAGATION_NESTED

从字面也可知道,nested,嵌套级别事务。该传播级别的特征是:如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

那么什么是嵌套事务呢?很多人都不理解,我看过一些博客,都是有些理解偏差。

嵌套是子事务嵌套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于那个save point。看几个问题就明了了:

(1)   如果子事务回滚,会发生什么?

父事务会回滚到进入子事务前建立的save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。

(2)   如果父事务回滚,会发生什么?

父事务回滚,子事务也会跟着回滚!为什么呢,因为父事务结束之前,子事务是不会提交的,我们说子事务是父事务的一部分,正是这个道理。

(3)   事务的提交,是什么情况?

是父事务先提交,然后子事务提交,还是子事务先提交,父事务再提交?答案是第二种情况,还是那句话,子事务是父事务的一部分,由父事务统一提交。

现在你再体会一下这个”嵌套“,是不是有那么点意思了?

以上是事务的7个传播级别,在日常应用中,通常可以满足各种业务需求,但是除了传播级别,在读取数据库的过程中,如果两个事务并发执行,那么彼此之间的数据是如何影响的呢?这就需要了解一下事务的另一个特性:数据隔离级别。

 

五、事务的隔离级别

      数据隔离级别分为不同的4种:

SERIALIZABLE

最严格的级别,事务串行执行,资源消耗最大。

REPEATABLE_READ

保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。

READ_COMMITTED

大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。

READ_UNCOMMITTED

保证了读取过程中不会读取到非法数据。

 

      上面的解释其实每个定义都有一些拗口,其中涉及到几个术语:脏读、不可重复读、幻读。这里解释一下:

脏读(Dirty Reads)

所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。

不可重复读(Non-RepeatableReads)

不可重复读字面含义已经很明了了,比如事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。

幻读

小的时候数手指,第一次数十10个,第二次数是11个,怎么回事?产生幻觉了?

幻读也是这样子,事务A首先根据条件索引得到10条数据,然后事务B改变了数据库一条数据,导致也符合事务A当时的搜索条件,这样事务A再次搜索发现有11条数据了,就产生了幻读。

 

事务隔离级别对照关系表:

 

脏读

不可重复读

幻读

SERIALIZABLE

不会

不会

不会

REPEATABLE_READ

不会

不会

READ_COMMITTED

不会

READ_UNCOMMITTED

      所以最安全的,是Serializable,但是伴随而来也是高昂的性能开销。

      另外,事务常用的两个属性:① readonly,设置事务为只读以提升性能;② timeout,设置事务的超时时间,一般用于防止大事务的发生。

 

六、Spring管理声明式事务的配置

1. 基于XML配置文件的AOP和TX配置方式

      applicationContext.xml配置文件:

<?xml version="1.0" encoding="gbk"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans-2.0.xsd

    http://www.springframework.org/schema/tx

    http://www.springframework.org/schema/tx/spring-tx-2.0.xsd

    http://www.springframework.org/schema/aop

    http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

 

    <bean id="dataSource"

        class="org.apache.commons.dbcp.BasicDataSource">

        <property name="driverClassName"

            value="com.microsoft.sqlserver.jdbc.SQLServerDriver">

        </property>

        <property name="url"

            value="jdbc:sqlserver://localhost:1500;databaseName=ssh">

        </property>

        <property name="username" value="sa"></property>

        <property name="password" value="sa"></property>

    </bean>

    <bean id="sessionFactory"

        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

        <property name="dataSource">

            <ref bean="dataSource" />

        </property>

        <property name="hibernateProperties">

            <props>

                <prop key="hibernate.dialect">

                    org.hibernate.dialect.SQLServerDialect

                </prop>

            </props>

        </property>

        <property name="mappingResources">

            <list>

                <value>bank/entity/Account.hbm.xml</value>

            </list>

        </property>

    </bean>

  

    <bean id="AccountDAO" class="bank.dao.AccountDAO">

        <property name="sessionFactory">

            <ref bean="sessionFactory" />

        </property>

    </bean>

 

    <bean id="AccountManager" class="bank.biz.AccountManager">

        <property name="dao">

            <ref bean="AccountDAO" />

        </property>

    </bean>

  

    <bean name="/account" class="bank.action.AccountAction">

        <property name="accountManager">

            <ref bean="AccountManager" />

        </property>

    </bean>  

  

    <!--通用事务管理器-->

    <bean id="TransactionManager"

        class="org.springframework.orm.hibernate3.HibernateTransactionManager">

        <property name="sessionFactory" ref="sessionFactory" />

    </bean>

    <!--指定事务策略,声明一个通知,用以指出要管理哪些事务方法及如何管理-->

    <tx:advice id="txAdvice" transaction-manager="TransactionManager">

        <tx:attributes>

            <!-- 对get/load/search开头的方法要求只读事务 -->

            <tx:method name="find*" propagation="SUPPORTS"

                read-only="true" />

            <!-- 对其它方法要求事务 -->

            <tx:method name="*" propagation="REQUIRED" />

        </tx:attributes>

    </tx:advice>

  

    <!--声明一个config,用以将事务策略和业务类关联起来-->

    <aop:config>

        <!-- 添加事务支持,因为前面配置的transactionManager是专对Hibernate的事务管理器-->

        <aop:pointcut id="bizMethods" expression="execution(* bank.biz..*.*(..))" />

        <!-- 织入 -->

        <aop:advisor advice-ref="txAdvice" pointcut-ref="bizMethods" />

    </aop:config>          

</beans>

 

2. 基于anotation注解形式的事务管理

      applicationContext.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: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/tx

    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd ">

 

       <!-- 需要引入tx的命名空间 -->

       <tx:annotation-driven transaction-manager="transactionManager" />

      

       <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">

              <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver">

              </property>

              <property name="url"

                     value="jdbc:sqlserver://localhost:1500;databaseName=ssh">

              </property>

              <property name="username" value="sa"></property>

              <property name="password" value="sa"></property>

       </bean>

      

       <bean id="sessionFactory"

              class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

              <property name="dataSource">

                     <ref bean="dataSource" />

              </property>

              <property name="hibernateProperties">

                     <props>

                            <prop key="hibernate.dialect">

                                   org.hibernate.dialect.SQLServerDialect

                            </prop>

                     </props>

              </property>

              <property name="mappingResources">

                     <list>

                            <value>bank/entity/Account.hbm.xml</value>

                     </list>

              </property>

       </bean>

      

       <bean id="tblUserDAO" class="com.angi.dao.TblUserDAO">

              <property name="sessionFactory">

                     <ref bean="sessionFactory" />

              </property>

       </bean>

       <bean id="tblUserService" class="com.angi.dao.service.TblUserService">

              <property name="tblUserDAO">

                     <ref bean="tblUserDAO" />

              </property>

       </bean>

      

       <!-- 声明一个 Hibernate3 的事务管理器供代理类自动管理事务用 -->

       <bean id="transactionManager"             class="org.springframework.orm.hibernate3.HibernateTransactionManager">

              <property name="sessionFactory">

                     <ref local="sessionFactory" />

              </property>

       </bean>

</beans>

 

Java代码片段:

    @Transactional

    public void doTransaction() {

        // step1 insert

        TblUser tblUser1 = new TblUser();

        tblUser1.setId(24);

        tblUser1.setUsername("Angi12");

        tblUser1.setPassword("Wang21");

        tblUserDAO.save(tblUser1);

        // step2 update

        TblUser tblUser2 = tblUserDAO.findById(2);

        tblUser2.setPassword(tblUser2.getPassword() + "a");

        tblUserDAO.update(tblUser2);

        // step3 insert

        TblUser tblUser = new TblUser();

        tblUser.setId(23);

        tblUser.setUsername("Angi");

        tblUser.setPassword("Wang");

        tblUserDAO.save(tblUser);

    }

 

七、简述Hibernate的SessionFactory和Session

l  SessionFactory对象

Hibernate中SessionFactory对象的创建代价很高,它是线程安全的对象,被设计成可以为所有的应用程序线程所共享。通常,SessionFactory会在应用程序启动时创建,一旦创建了SessionFactory将不会轻易关闭,只有当应用关闭时,SessionFactory才会关闭。

l  Session对象

Hibernate中Session对象是轻量级的,它是线程不安全的。对于单个业务进程、单个工作单元而言,Session只被使用一次。创建Session时,并不会立即打开与数据库之间的连接,Session只在需要进行数据库操作时,才会获取JDBC连接。因此,打开和关闭Session,并不会对性能造成很大的影响。甚至即使无法确定一个请求是否需要数据访问,也可以打开Session对象,因为如果不进行数据库访问,Session不会获取JDBC连接。

使用Spring管理Hibernate的事务,在每个DAO操作中使用SessionFactory.getCurrentSession()方法,该方法可以得到当前事务绑定的Session。同时当前的Session和关联的Hibernate事务被绑定到当前线程上,虽然Session不是线程安全的,但是通过这样的方式,每一个Session都处于单线程中,避免Session线程安全问题。

l  不通过Spring管理事务,开启事务的主动性

在SessionFactory.openSession()中,Hibernate会初始化数据库连接,与此同时,将其 AutoCommit设为关闭状态,这就是说,从SessionFactory获得Session,其自动提交属性就已经被关闭了,事务需要主动、显示的调用才能生效,下面的代码不会对事务性数据库产生任何效果:

        session = sessionFactory.openSession();

        session.save(user);

        session.close();

      如果要使得代码真正作用到数据库,必须显示的调用Transaction指令:

        session = sessionFactory.openSession();

        Transaction tx = session.beginTransaction();

        session.save(user);

        tx.commit();

        session.close();

 

八、Java的三种事务管理

JDBC事务

JDBC 事务是用 Connection 对象控制的。JDBC Connection 接口( java.sql.Connection )提供了两种事务模式:自动提交手工提交

java.sql.Connection 提供了以下控制事务的方法:

public void setAutoCommit(boolean)

public boolean getAutoCommit()

public void commit()

public void rollback()

使用 JDBC 事务界定时,您可以将多个 SQL 语句结合到一个事务中。

JDBC 事务的一个缺点是事务的范围局限于一个数据库连接。一个 JDBC 事务不能跨越多个数据库。

JTA(Java Transaction API)事务

JTA是一种高层的,与实现无关的,与协议无关的API,应用程序和应用服务器可以使用JTA来访问事务。

JTA允许应用程序执行分布式事务处理--在两个或多个网络计算机资源上访问并且更新数据,这些数据可以分布在多个数据库上。JDBC驱动程序的JTA支持极大地增强了数据访问能力。

如果计划用 JTA 界定事务,那么就需要有一个实现 javax.sql.XADataSource 、javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驱动程序。一个实现了这些接口的驱动程序将可以参与 JTA 事务。一个 XADataSource 对象就是一个 XAConnection 对象的工厂。 XAConnection s 是参与 JTA 事务的 JDBC 连接。

您将需要用应用服务器的管理工具设置XADataSource 。从应用服务器和 JDBC 驱动程序的文档中可以了解到相关的指导。

J2EE 应用程序用 JNDI 查询数据源。一旦应用程序找到了数据源对象,它就调用 javax.sql.DataSource.getConnection() 以获得到数据库的连接。

XA 连接与非 XA 连接不同。一定要记住 XA 连接参与了 JTA 事务。这意味着 XA 连接不支持 JDBC 的自动提交功能。同时,应用程序一定不要对 XA 连接调用 java.sql.Connection.commit() 或者java.sql.Connection.rollback() 。相反,应用程序应该使用UserTransaction.begin()、 UserTransaction.commit() 和 serTransaction.rollback() 。

容器事务

容器事务主要是J2EE应用服务器提供的,容器事务大多是基于JTA完成,这是一个基于JNDI的,相当复杂的API实现。相对编码实现JTA 事务管理,我们可以通过EJB容器提供的容器事务管理机制(CMT)完成同一个功能,这项功能由J2EE应用服务器提供。这使得我们可以简单的指定将哪个方法加入事务,一旦指定,容器将负责事务管理任务。这是我们常见的解决方式,因为通过这种方式我们可以将事务代码排除在逻辑编码之外,同时将所有困难交给 J2EE容器去解决。使用EJB CMT的另外一个好处就是程序员无需关心JTA API的编码,不过,理论上我们必须使用EJB。

 

三种事务差异:

1.   JDBC事务控制的局限性在一个数据库连接内,但是其使用简单。

2.   JTA事务的功能强大,事务可以跨越多个数据库或多个DAO,使用也比较复杂。

3.   容器事务,主要指的是J2EE应用服务器提供的事务管理,局限于EJB应用使用。

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值