Spring 事务管理


数据库事物的基础知识

Spring 虽然提供了灵活方便的事务管理功能,但是这些功能都是基于数据库底层的的事务处理功能实现的,所以首先要了解数据库本身的事务

数据库事务的特性
  1. 原子性--只有所有的操作都执行成功 这个事务才会被提交

  2. 一致性--事务操作成功以后,数据库所处的状态和它的业务规则是一致的 A转给B 100 块钱, 不管成功与否, A和B 的存款总额是不会变得

  3. 隔离性-- 在并发操作数据库的时候, 不同的事务拥有各自的数据空间, 互相之间不影响 这个不是绝对的,要看数据库的事务隔离级别,级别越高,事务的一致性越好, 但并发性越弱

  4. 持久性-- 一旦事务被提及成功,事务中的数据都会持久化到数据库中,即使在提交成功后数据库马上崩溃, 在数据库重启时, 也能保证通过某种机制恢复数据

在这些特性中, 一致性时最终目的, 其他特性都是为了保证一致性而采取的方案

问: 数据库通过什么来保证事务的这4个特性

答: 数据库管理系统一般采用重执行日志来保证事务的这4个特性,重执行日志记录了数据库变化的每一个动作 数据库在执行一个事务的过程中发生了错误退出, 数据库可以根据重执行日志撤销已经执行的操作,对于已经提交的事务, 即使数据库崩溃了,数据库可以根据重执行日志对尚未执行持久化的数据进行相应的重执行操作

和java 程序采用对象锁机制进行线程同步类型, 数据库采用数据库锁机制保证事务的隔离性,当有多个事务试图对相同的数据进行操作时候, 只有拥有锁的事务才能操作数据, Oracle 数据库还采用了数据版本的机制

数据并发的问题

数据库中的相同数据可能会被读个事务访问,会引发5种问题,包括3种数据读问题和2种数据的更新问题

  1. 脏读 --一个事务处理过程里读取了另一个未提交的事务中的数据。

  2. 不可重复读--数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。 不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

  3. 虚读(幻读)--  幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

    幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同) 幻象读是读到了其他已经提交事务的新增数据,不可重复读是读到了其他事务的更改数据(更改或者删除)

  • 为了防止读到更改数据 (不可重复读) 只需要对操作的数据添加行级锁

  • 为了防止读到新增的数据, 需要添加表级锁(oracel采用多版本数据的方式实现)

数据更新丢失问题

  1. 第一类更新丢失 -- A事务撤销的时候,把已经提交的B事务的更新数据覆盖了

  2. 第二类更新丢失-- A事务覆盖B 事务已经提交的数据, 造成B 事务所做操作丢失

 

4种等级的事务隔离级别
  • ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

  • ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。

  • ③ Read committed (读已提交):可避免脏读的发生。

  • ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。

​ 级别越高,并发性能越差

  在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。

 

spring 对事务管理的支持

思维导图

 

 

使用xml 的方式 进行事物管理

<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: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.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 引入外部属性配置文件-->
    <context:property-placeholder location="classpath:db.properties"/> 
    
     <!-- 配置数据源 -->
    <!-- c3p0连接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.className}" />
        <property name="jdbcUrl" value="${jdbc.url}" />
        <property name="user" value="${jdbc.user}" />
        <property name="password" value="${jdbc.password}" />
    </bean><!-- 第一步:定义具体的平台事务管理器(DataSource事务管理器) -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 第二步:定义通知,通知中要处理的就是事务 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 配置事务的属性定义 -->
        <tx:attributes>
            <!-- 配置具体的方法的事务属性
            isolation//事务的隔离级别,默认是按数据库的隔离级别来
            propagation//事务的传播行为,默认是同一个事务
            timeout="-1":事务的超时时间,默认值使用数据库的超时时间。
            read-only="false":事务是否只读,默认可读写。
            rollback-for:遇到哪些异常就回滚,其他的都不回滚
            no-rollback-for:遇到哪些异常不回滚,其他的都回滚。和上面互斥的
             -->
            <tx:method name="transfer" isolation="DEFAULT"  propagation="REQUIRED" timeout="-1" read-only="false"/>
            
            <!-- 支持通配符
                要求service中 方法名字必须符合下面的规则
             -->
            <tx:method name="save*"/>
            <tx:method name="update*"/>
            <tx:method name="delete*"/>
            <tx:method name="find*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <!-- 第三步:配置切入点,让通知关联切入点,即事务控制业务层的方法 -->
    <aop:config>
        <!-- 切入点 -->
        <aop:pointcut expression="bean(*Service)" id="txPointcut"/>
        <!-- 切面 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
    
    <!-- dao -->
    <bean id="accountDao" class="cn.tencent.spring.dao.AccountDaoImpl">
        <!-- 注入数据源,才拥有jdbctemplate -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 业务层 -->
    <bean id="accountService" class="cn.tencent.spring.service.AccountServiceImpl">
        <!-- 注入dao -->
        <property name="accountDao" ref="accountDao"/>
    </bean>
    
</beans>

 

注解的方式进行事物管理

//掌握操作的业务层
/**
 * @Service("accountService")
 * 相当于spring容器中定义:<bean id="accountService" class="cn.tencent.spring.anntx.service.AccountServiceImpl">
 */
@Service("accountService")
@Transactional//会对该类中,所有的共有的方法,自动加上事务--全局的设置,默认是可写
public class AccountServiceImpl implements IAccountService{
    
    //注入dao
    @Autowired
    private IAccountDao accountDao;
​
    //转账操作的业务逻辑
//  @Transactional//在方法上添加事务
    public void transfer(String outName,String inName,Double money){
        
        //调用dao层
        //先取出
        accountDao.out(outName, money);
        int d = 1/0;
        //再转入
        accountDao.in(inName, money);
​
    }
    
    @Transactional(readOnly=true)//方法级别的事务覆盖类级别的事务
    public void findAccount(){
        System.out.println("查询帐号的信息了");
    }
​
}

 


注解需要的配置


<?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: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.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 引入外部属性文件 -->
    <context:property-placeholder location="classpath:db.properties" />
    <!-- 配置数据源 -->
    <!-- c3p0连接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.className}" />
        <property name="jdbcUrl" value="${jdbc.url}" />
        <property name="user" value="${jdbc.user}" />
        <property name="password" value="${jdbc.password}" />
    </bean> 
    <!-- 配置bean注解扫描 -->
    <context:component-scan base-package="cn.tencent.spring.anntx"/>
    
    <!-- 定义具体的平台事务管理器(DataSource事务管理器) -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    
    <!-- 配置事务注解驱动 :识别事务的注解@tr。。。
    transaction-manager:具体的平台事务管理器
    -->
    <!-- <tx:annotation-driven transaction-manager="transactionManager"/> -->
    <!-- 默认的平台事务管理器的名字叫:transactionManager,此时transaction-manager="transactionManager"可以不写 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
</beans>

 

posted @ 2018-06-28 22:54  平壤钢琴师  阅读(107)  评论(0编辑  收藏  举报