zoukankan      html  css  js  c++  java
  • @Spring事务管理

    Spring虽然提供了灵活方便的事务管理功能,但是这些功能都是基于底层数据库本身的事务处理机制工作的,要深入了解Spring的事务管理和配置,有必要对数据库事务的基础知识进行学习!

    数据库事务(简称:事务)

    是数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

    并非任意的对数据库的操作序列都是数据库事务。数据库事务拥有以下四个特性,习惯上被称之为ACID特性
    原子性(Atomic):表示组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有的操作执行成功,整个事务才能提交,事务中任何一个数据操作失败,已经执行的任何操作都必须撤销,让数据库返回到初始状态。。
    一致性(Consistency):事务操作成功后,数据库所处的状态和他的业务规则是一致的,即数据不会被破坏。如从A账户转账100元到B账户,不管操作成功与否,A和B的存款总额是不变的。
    隔离性(Isolation):多个事务操作并发执行时,不同的事务拥有各自的数据空间,一个事务的执行不应影响其他事务的执行。准确的说并非要求做到完全无干扰,数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发性越弱。
    持久性(Durability):一旦事务提交成功后,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证能够通过某种机制恢复数据。

    在这些事务特性中,数据“一致性”是最终目标,其他的特性都是为达到这个目标的措施、要求或手段。 

    数据库管理系统一般采用重执行日志保证原子性、一致性和持久性,重执行日志记录了数据库变化的每一个动作,数据库在一个事务中执行一部分操作后发 生错误退出,数据库即可以根据重执行日志撤销已经执行的操作。此外,对于已经提交的事务,即使数据库崩溃,在重启数据库时也能够根据日志对尚未持久化的数 据进行相应的重执行操作。 

    和Java程序采用对象锁机制进行线程同步类似,数据库管理系统采用数据库锁机制保证事务的隔离性。当多个事务试图对相同的数据进行操作时,只有持有锁的事务才能操作数据,直到前一个事务完成后,后面的事务才有机会对数据进行操作。Oracle数据库还使用了数据版本的机制,在回滚段为数据的每个 变化都保存一个版本,使数据的更改不影响数据的读取。 

    数据库系统的四种事务隔离级别

    尽管数据库为用户提供了锁的DML操作方式,但直接使用锁管理是非常麻烦的,因此数据库为用户提供了自动锁机制。只要用户指定会话的事务隔离级 别,数据库就会分析事务中的SQL语句,然后自动为事务操作的数据资源添加上适合的锁。此外数据库还会维护这些锁,当一个资源上的锁数目太多时,自动进行 锁升级以提高系统的运行性能,而这一过程对用户来说完全是透明的。 

    Read Commited:读已提交数据(会出现不可重复读和幻读)

    Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)。

    Repeatable Read:可重复读(会出现幻读)

    Serializable:串行化

    SQL 92推荐使用REPEATABLE READ(MySQL默认隔离级别)以保证数据的读一致性,不过用户可以根据应用的需要选择适合的隔离等级。 

    数据并发的问题 

    一个数据库可能拥有多个访问客户端,这些客户端都可以并发方式访问数据库。数据库中的相同数据可能同时被多个事务访问,如果没有采取必要的隔离措 施,就会导致各种并发问题,破坏数据的完整性。这些问题可以归结为5类,包括3类数据读问题(脏读、不可重复读和幻象读)以及2类数据更新问题(第一类丢 失更新和第二类丢失更新)。下面,我们分别通过实例讲解引发问题的场景。 

    脏读

    A事务读取B事务尚未提交的更改数据,并在这个数据的基础上操作。如果恰巧B事务回滚,那么A事务读到的数据就是无效的。

    在Oracle数据库中,不会发生脏读的情况。 

    不可重复读:

    同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。

    幻读:

    A事务读取B事务提交的新增数据,这时A事务将出现幻象读的问题。象读一般发生在计算统计数据的事务中。

    幻象读和不可重复读是两个容易混淆的概念,前者是指读到了其他事务已经提交的新增数据,而后者是指读到了其他事务已经提交的更改数据(更改或删除), 为了避免这两种情况,采取的对策是不同的,防止读取到更改数据,只需要对操作的数据添加行级锁,阻止操作中的数据发生变化,而防止读取到新增数据,则往往 需要添加表级锁——将整个表锁定,防止新增数据(Oracle使用多版本数据的方式实现)。 

    数据库锁机制 

    数据并发会引发很多问题,在一些场合下有些问题是允许的,但在另外一些场合下可能却是致命的。数据库通过锁的机制解决并发访问的问题,虽然不同的数据库在实现细节上存在差别,但原理基本上是一样的。 

    按锁定的对象的不同,一般可以分为表锁定和行锁定,前者对整个表进行锁定,而后者对表中特定行进行锁定。从并发事务锁定的关系上看,可以分为共享 锁定和独占锁定。共享锁定会防止独占锁定,但允许其他的共享锁定。而独占锁定既防止其他的独占锁定,也防止其他的共享锁定。为了更改数据,数据库必须在进 行更改的行上施加行独占锁定,INSERT、UPDATE、DELETE和SELECT FOR UPDATE语句都会隐式采用必要的行锁定。下面我们介绍一下Oracle数据库常用的5种锁定。 

      • 行共享锁定:一般通过SELECT FOR UPDATE语句隐式获得行共享锁定,在Oracle中用户也可以通过LOCK TABLE IN ROW SHARE MODE语句显式获得行共享锁定。行共享锁定并不防止对数据行进行更改的操作,但是可以防止其他会话获取独占性数据表锁定。允许进行多个并发的行共享和行 独占性锁定,还允许进行数据表的共享或者采用共享行独占锁定。
      • 行独占锁定:通过一条INSERT、UPDATE或DELETE语句隐式获取,或者通过一条LOCK TABLE IN ROW EXCLUSIVE MODE语句显式获取。这个锁定可以防止其他会话获取一个共享锁定、共享行独占锁定或独占锁定。
      • 表共享锁定:通过LOCK TABLE IN SHARE MODE语句显式获得。这种锁定可以防止其他会话获取行独占锁定(INSERT、UPDATE或DELETE),或者防止其他表共享行独占锁定或表独占锁 定,它允许在表中拥有多个行共享和表共享锁定。该锁定可以让会话具有对表事务级一致性访问,因为其他会话在用户提交或者回溯该事务并释放对该表的锁定之前 不能更改这个被锁定的表。
      • 表共享行独占:通过LOCK TABLE IN SHARE ROW EXCLUSIVE MODE语句显式获得。这种锁定可以防止其他会话获取一个表共享、行独占或者表独占锁定,它允许其他行共享锁定。这种锁定类似于表共享锁定,只是一次只能 对一个表放置一个表共享行独占锁定。如果A会话拥有该锁定,则B会话可以执行SELECT FOR UPDATE操作,但如果B会话试图更新选择的行,则需要等待。
      • 表独占:通过LOCK TABLE IN EXCLUSIVE MODE显式获得。这个锁定防止其他会话对该表的任何其他锁定。

    不同的隔离级别采用不同的锁类型来实现,在四种隔离级别中,Serializable的隔离级别最高,Read Uncommited的隔离级别最低。

    • 大多数据库默认的隔离级别为Read Commited,如SqlServer
    • 当然也有少部分数据库默认的隔离级别为Repeatable_Read ,如Mysql,
    • Oracle数据库支持READ COMMITTED和SERIALIZABLE两种事务隔离性级别,不支持READ UNCOMMITTED和REPEATABLE READ这两种隔离性级别。虽然SQL标准定义的默认事务隔离性级别是SERIALIZABLE,但是Oracle数据库默认使用的事务隔离性级别却是READ COMMITTED.

    Spring对事务管理的支持

    Spring提供了两种事务管理方式:

    编程式事务管理:

    编写程序式的事务管理可以清楚的定义事务的边界,可以实现细粒度的事务控制,比如你可以通过程序代码来控制你的事务何时开始,何时结束等,与后面介绍的声明式事务管理相比,它可以实现细粒度的事务控制。

    声明式事务管理:

    如果你并不需要细粒度的事务控制,你可以使用声明式事务,在Spring中,你只需要在Spring配置文件中做一些配置,即可将操作纳入到事务管理中,解除了和代码的耦合, 这是对应用代码影响最小的选择。当你不需要事务管理的时候,可以直接从Spring配置文件中移除该设置

    如果使用Spring, 我们就不再需要手工控制事务

    事务传播规则(传播行为)

    传播行为:定义关于客户端和被调用方法的事物边界

    传播行为

    意义

    REQUIRED

    (默认值)

    业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务

    NOT_SUPPORTED

    声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行

    REQUIRESNEW

    属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行

    MANDATORY

    该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。

    SUPPORTS

    这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行

    Never

    指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行

    NESTED

    如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效

    事务管理器

    spring没有直接管理事务,而是将管理事务的责任委托给JTA或相应的持久性机制所提供的某个特定平台的事物实现

    事务管理器实现 目标
    org.springframework.jdbc.datasource.DataSourceTransactionManager 在单一的JDBC Datasource中的管理事务
    org.springframework.orm.hibernate3.HibernateTransactionManager 当持久化机制是hibernate时,用它来管理事务
    org.springframework.jdo.JdoTransactionManager 当持久化机制是Jdo时,用它来管理事务。
    org.springframework.transaction.jta.JtaTransactionManager 使用一个JTA实现来管理事务。在一个事务跨越多个资源时必须使用
    org.springframework.orm.ojb.PersistenceBrokerTransactionManager 当apache的ojb用作持久化机制时,用它来管理事务。

     声明式事务管理——基于XML配置

    在spring配置文件中引入tx命名空间

    <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: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-2.5.xsd
                http://www.springframework.org/schema/aop 
                http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
                http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context-2.5.xsd
                http://www.springframework.org/schema/tx 
                http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
    <!-- 加载外部的配置文件 -->
    <context:property-placeholder location="classpath:cn/itcast/spring/p_jdbc/jdbc.properties"/>
    
    
    <!-- 一、配置数据库连接池 ComboPooledDataSource -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 基本的连接信息 -->
        <property name="jdbcUrl" value="${jdbcUrl}"></property>
        <property name="driverClass" value="${driverClass}"></property>
        <property name="user" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <!-- 一些管理的配置 -->
        <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
        <property name="initialPoolSize" value="3"></property>
        <!--连接池中保留的最小连接数。Default: 3 -->
        <property name="minPoolSize" value="3"></property>
        <!--连接池中保留的最大连接数。Default: 15 -->
        <property name="maxPoolSize" value="5"></property>
        <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
        <property name="acquireIncrement" value="3"></property>
        <!--最大空闲时间,1800秒内未使用则连接被丢弃,若为0则永不丢弃。Default: 0 -->
        <property name="maxIdleTime" value="1800"></property>
    </bean>
    <!-- 三、配置声明式事务管理(告诉它哪些方法需要事务) -->
    <!-- 1,配置事务管理器(应根据情况使用合适的事务管理器) -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 2,配置管理事务的“通知” -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 配置哪些方法要使用什么样的事务配置,没有匹配到的方法不会为其管理事务 -->
        <tx:attributes>
            <!-- 指定所有以add,delete,update开头的方法都使用事务,
    默认read-only="false" -->
            <tx:method name="add*" />
            <tx:method name="delete*" />
            <tx:method name="update*" />
            <!-- 指定所有以query开头的方法都是使用只读的事务 -->
            <tx:method name="query*" read-only="true" />
        </tx:attributes>
    </tx:advice>
    
    <!-- 3,配置切面 -->
    <aop:config>
        <!-- 指定拦截所有Dao中的方法 -->
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* *..*Dao*.*(..))" />
    </aop:config>

    声明式事务管理——基于注解配置

    在spring中要做的配置

    <!-- 加载外部的配置文件 -->
    <context:property-placeholder location="classpath:cn/itcast/spring/p_jdbc/jdbc.properties"/>
    
    <!-- 一、配置数据库连接池 ComboPooledDataSource -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 基本的连接信息 -->
        <property name="jdbcUrl" value="${jdbcUrl}"></property>
        <property name="driverClass" value="${driverClass}"></property>
        <property name="user" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <!-- 一些管理的配置 -->
        <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
        <property name="initialPoolSize" value="3"></property>
        <!--连接池中保留的最小连接数。Default: 3 -->
        <property name="minPoolSize" value="3"></property>
        <!--连接池中保留的最大连接数。Default: 15 -->
        <property name="maxPoolSize" value="5"></property>
        <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
        <property name="acquireIncrement" value="3"></property>
        <!--最大空闲时间,1800秒内未使用则连接被丢弃,若为0则永不丢弃。Default: 0 -->
        <property name="maxIdleTime" value="1800"></property>
    </bean>

    <!-- 三、配置声明式事务管理(告诉它哪些方法需要事务) --> <!-- 1,配置事务管理器(应根据情况使用合适的事务管理器) --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
    <!-- ==== 基于注解的方式配置事务 ==== --> <tx:annotation-driven transaction-manager="transactionManager"/>

    在类上或方法上写注解声明要使用事务

    @Transactional(readOnly = true)
    public class UserDao {
    
        @Transactional(readOnly = false)
        public void save(final User user) {
    //....
    }
    }

    @Transactional注解说明

    方法的事务设置将被优先执行。例如: UserServiceImpl类在类的级别上被注解为只读事务,但是,这个类中的 addUser(User uesr) 方法的@Transactional注解的事务设置将优先于类级别注解的事务设置。

    @Transactional注解可以被继承,即:在父类上声明了这个注解,则子类中的所有public方法也都是会开事务的。

  • 相关阅读:
    P2832 行路难
    P2634 [国家集训队]聪聪可可
    模拟退火算法
    洛谷 P2986 [USACO10MAR]Great Cow Gat…(树形dp+容斥原理)
    bzoj1040: [ZJOI2008]骑士(基环树dp)
    洛谷P2014 选课(树形dp)
    洛谷P3047 [USACO12FEB]Nearby Cows(树形dp)
    bzoj1026: [SCOI2009]windy数(数位dp)
    hdu3555Bomb(数位dp)
    hdu3652B-number(数位dp)
  • 原文地址:https://www.cnblogs.com/winner-0715/p/5427682.html
Copyright © 2011-2022 走看看