zoukankan      html  css  js  c++  java
  • Spring之旅第六篇-事务管理

    一、什么是事务

    什么是事务(Transaction)?事务是数据库中的概念,是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。

    有个非常经典的转账问题:A向B转款1000元,A转出成功,扣除了A账户的1000元,但当系统准备向B账户增加1000元出现了故障,转入失败,但是A账户的金额已经扣除,这样的结果是A账户凭空少了1000元,很明显这样是不行的,正确的做法应该是当B账户增加成功后,A账户的扣款才能生效。

    简单总结一句话就是:事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败

    二、事务的四个特性(ACID)

    • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。

    • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。

    • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。

    • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

    三、事务隔离(Isolation Level)

     事务隔离意味着对于某一个正在运行的事务来说,好像系统中只有这一个事务,其他并发的事务都不存在一样。大部分情况下,很少使用完全隔离的事务。但不完全隔离的事务会带来如下一些问题。

     脏数据(Dirty Read):一个事务读到了另一个事务的未提交的数据

     不可重读(Unrepeatable Read):一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致

     幻读(Phantom Read):一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致

     通过设置事务隔离级别解决这些读的问题,事务隔离级别分别是:

     读操作未提交(Read Uncommitted):说明一个事务在提交前,其变化对于其他事务来说是可见的。这样脏读、不可重读和幻读都是允许的。当一个事务已经写入一行数据但未提交,其他事务都不能再写入此行数据;但是,任何事务都可以读任何数据。这个隔离级别使用排写锁实现(脏读,不可重复读,虚读都有可能发生

     读操作已提交(Read Committed):它使用临时的共读锁和排写锁实现。这种隔离级别不允许脏读,但不可重读和幻读是允许的(避免脏读。但是不可重复读和虚读有可能发生

     可重读(Repeatable Read):说明事务保证能够再次读取相同的数据而不会失败。此隔离级别不允许脏读和不可重读,但幻读会出现(避免脏读和不可重复读.但是虚读有可能发生

     可串行化(Serializable):提供最严格的事务隔离。这个隔离级别不允许事务并行执行,只允许串行执行。这样,脏读、不可重读或幻读都可发生(避免以上所有读问题

     Mysql 默认:可重复读

    Oracle 默认:读已提交

    四、spring事务管理的核心接口

    因为在不同平台,操作事务的代码各不相同.spring提供了一个接口: PlatformTransactionManager 接口

    PlatformTransactionManager主要有三个方法

    • TransactionStatus getTransaction(TransactionDefinition definition) ,事务管理器 通过TransactionDefinition,获得“事务状态”,从而管理事务。

    • void commit(TransactionStatus status) 根据状态提交

    • void rollback(TransactionStatus status) 根据状态回滚

     事务的状态:

    获取事务时带入的接口为TransactionDefinition

    隔离级别取值详解:

    1. PROPAGATION_REQUIRED :required , 必须。默认值,A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务。

    2. PROPAGATION_SUPPORTS:supports ,支持。A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行。

    3. PROPAGATION_MANDATORY:mandatory ,强制。A如果有事务,B将使用该事务;如果A没有事务,B将抛异常。

    4. PROPAGATION_REQUIRES_NEW :requires_new,必须新的。如果A有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务。

    5. PROPAGATION_NOT_SUPPORTED :not_supported ,不支持。如果A有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行。

    6. PROPAGATION_NEVER :never,从不。如果A有事务,B将抛异常;如果A没有事务,B将以非事务执行。

    7. PROPAGATION_NESTED :nested ,嵌套。A和B底层采用保存点机制,形成嵌套事务。

    实现该接口的有DataSourceTransactionManager和HibernateTransitionmanager

    五、spring事务处理

    spring的事务处理分为编程式事务处理声明式事务处理

    编程式事务处理:所谓编程式事务指的是通过编码方式实现事务,允许用户在代码中精确定义事务的边界。即类似于JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

    声明式事务处理:管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

    简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。

    理论很多了,开始编码吧,以转账作为例子

     DAO账户接口:

    //账户接口
    public interface AccountDao {
        //转出
        void out(String outer,Double money);
        //转入
        void in(String in,Double money);
    }

    转账实现:

    //转账实现
    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao   {
        public void out(String outer, Double money) {
            this.getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer);
        }
    
        public void in(String in, Double money) {
            this.getJdbcTemplate().update("update account set money = money + ? where username = ?",money,in);
        }
    }

    Service接口:

    public interface AccountService {
        void transfer(String from,String to,Double money);
    }

    Service实现

    public class AccountServiceImpl implements AccountService {
        // 业务层注入 DAO:
        private AccountDao accountDao;
    
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        public void transfer(String from, String to, Double money) {
            accountDao.out(from,money);
            accountDao.in(to,money);
        }
    

    测试:

    @Test
    public void fun1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
        AccountService account = (AccountService) context.getBean("accountService");
        //张三 向 李四 转账1000
        account.transfer("zhangsan", "lisi", 1000d);
    }

    数据库原先数据:

    执行方法之后:

     

    这个结果很正常,现在我们让程序出一些错误,在两个操作之间增加一个异常

    accountDao.out(from,money);
    int i= 1/0;//异常操作
    accountDao.in(to,money);

    运行报错:java.lang.ArithmeticException: / by zero

    但是张三的账户还是扣除了1000元,李四账户并未改动

    5.1编程式事务处理实现转账

    现在需要我们来改造代码,让转账支持事务,改造如下:

    在AccountServiceImpl注入事务管理,代码如下:

    public class AccountServiceImpl implements AccountService {
        // 业务层注入 DAO:
        private AccountDao accountDao;
        //注入事务管理
        private TransactionTemplate transactionTemplate;
    
        public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
            this.transactionTemplate = transactionTemplate;
        }
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        public void transfer(final String from,final String to, final Double money) {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus arg0) {
                    accountDao.out(from, money);
                    int i = 1/0;
                    accountDao.in(to, money);
                }
            });
        }
    }

    配置文件也做相应修改:

    <?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:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           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">
    
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
            <property name="user" value="root"></property>
            <property name="password" value="123456"></property>
        </bean>
    
        <bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
    
        </bean>
    
        <bean id="accountService" class="com.yuanqinnan.transaction.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
            <property name="transactionTemplate" ref="transactionTemplate"></property>
        </bean>
    
        <!-- 创建模板 -->
        <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
            <property name="transactionManager" ref="txManager"></property>
        </bean>
    
        <!-- 配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 -->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    </beans>

    测试方法不改,仍然报错,但是张三的账户未被修改,说明事务生效

    5.2 声明式事务处理实现转账(基于AOP的 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: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">
    
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
            <property name="user" value="root"></property>
            <property name="password" value="123456"></property>
        </bean>
    
        <bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
    
        </bean>
    
        <bean id="accountService" class="com.yuanqinnan.transaction.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
        <!-- 1 事务管理器 -->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!-- 2 事务详情(事务通知)  , 在aop筛选基础上,比如对ABC三个确定使用什么样的事务。例如:AC读写、B只读 等
            <tx:attributes> 用于配置事务详情(属性属性)
                <tx:method name=""/> 详情具体配置
                    propagation 传播行为 , REQUIRED:必须;REQUIRES_NEW:必须是新的
                    isolation 隔离级别
        -->
        <tx:advice id="txAdvice" transaction-manager="txManager">
            <tx:attributes>
                <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
            </tx:attributes>
        </tx:advice>
    
        <!-- 3 AOP编程,利用切入点表达式从目标类方法中 确定增强的连接器,从而获得切入点 -->
        <aop:config>
            <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.yuanqinnan.transaction.AccountServiceImpl.transfer(..))"/>
        </aop:config>
    </beans>

    测试数据无误

    5.3 声明式事务处理实现转账(基于AOP的 注解 配置)

    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: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:component-scan base-package="com.yuanqinnan.transaction" ></context:component-scan>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    <bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    
    </bean>
    
    <bean id="accountService" class="com.yuanqinnan.transaction.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <!-- 1 事务管理器 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 2 将管理器交予spring
        * transaction-manager 配置事务管理器
        * proxy-target-class
            true : 底层强制使用cglib 代理
    -->
    <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
    </beans>

    AccountServiceImpl加上注解即可实现

    @Transactional(propagation=Propagation.REQUIRED , isolation = Isolation.DEFAULT)
    public class AccountServiceImpl implements AccountService {}

    以上三种方式均可实现事务的管理,事务管理讲完之后整个spring的入门总结也结束了,下面开始mybatis之旅

  • 相关阅读:
    Centos查看tomcat状态及操作
    python实现文件下载
    python深浅copy
    linux下jenkins实现自动化部署
    Linux下Java的安装
    通过filezilla将windows文件上传到linux中
    jmeter性能测试实现分布式
    性能测试-JMeter ServerAgent服务器资源监控
    vscode折叠/展开所有区域代码快捷键
    Linux的tar压缩解压缩命令
  • 原文地址:https://www.cnblogs.com/yuanqinnan/p/10512152.html
Copyright © 2011-2022 走看看