zoukankan      html  css  js  c++  java
  • 08-Spring的事务管理

    Spring的事务管理

    作为企业级应用程序框架, Spring 在不同的事务管理 API 之上定义了一个抽象层. 而应用程序开发人员不必了解底层的事务管理 API, 就可以使用 Spring 的事务管理机制。
    Spring 既支持编程式事务管理, 也支持声明式的事务管理。
    编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚. 在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码。
    声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理. 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化. Spring 通过 Spring AOP 框架支持声明式事务管理。

    事务本来就是数据库中的概念,在DAO层。但一般情况下,需要将事务提升到业务层,即Service层。这样做是为了能够使用事务的特性来管理具体的业务。
    在Spring中通常可以通过以下三种方式来实现对事务的管理。
    (1)使用Spring的事务代理工厂管理事务
    (2)使用Spring的事务注解管理事务
    (3)使用AspectJ的AOP配置管理事务

    1. Spring事务管理API

    Spring 从不同的事务管理 API 中抽象了一整套的事务机制. 开发人员不必了解底层的事务 API, 就可以利用这些事务机制. 有了这些事务机制, 事务管理代码就能独立于特定的事务技术了。
    Spring 的核心事务管理抽象是PlatformTransactionManager接口,管理封装了一组独立于技术的方法,无论似乎用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
    Spring的事务管理,主要用到两个事务相关的接口。
    (1)平台事务管理器接口:PlatformTransactionManager。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
    A:常用的两个实现类
    PlatformTransactionManager接口有两个常用的实现类:

    • DataSourceTransactionManager:使用Spring JDBC或iBatis进行持久化数据时使用。
    • HibernateTransactionManager:使用Hibernate进行持久化数据时使用。
    • JtaTransactionManager:在JavaEE应用服务器上用JTA进行事务管理。
      (2)事务定义接口:TransactionDefinition
      事务定义接口TransactionDefinition中定义了事务描述相关的四类常量:事务隔离级别、事务传播行为、事务默认超时时限,是否只读操作。
      A:定义了五个事务隔离界别常量
      这些常量均是以ISOLATION开头。即形如ISOLATION_XXX。

    DEFAULT:采用DB默认的事务隔离级别。MySQL的默认为REPEATABLE_READ;Oracle默认为READ_COMMITTED。【ISOLATION_DEFAULT】
    READ_UNCOMMITTED:读未提交。未解决任何并发问题。【ISOLATION_READ_UNCOMMITTED】
    READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。【ISOLATION_READ_COMMITTED】
    REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读。【ISOLATION_PEPEATABLE_READ】
    SERIALIZABLE:串行化,不存在并发问题。【ISOLATION_SERIALIZABLE】

    B:定义了七个事务传播行为常量
    所谓事务传播行为,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如:A事务中的方法doSome()调用B事务中的方法doOther()。在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
    事务传播行为常量都是以PROPAGATION_开头,形如PROPAGETION_XXX。

    (1)REQUIRED:指定的方法必须在事务内执行,若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新的事务。这种传播行为是最常见的选择,也是Spring默认的事务传播行为。
    如该传播行为加在doOther()方法上。若doSome()方法在调用doOther()方法时就是在事务内运行的,则doOther()方法的执行也加入到该事务内执行。若doSome()方法在调用doOther()方法时没有在事务内执行,则doOther()方法会创建一事务,并在其中执行。

    (2)SUPPORTS:指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行

    (3)MANDATORY:指定的方法必须在当前事务内执行,若当前没有事务,则直接抛出异常。

    (4)REQUIRES_NEW:总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事物执行完毕。

    (5)NOT_SUPPORTED:指定的方法不能在事务环境中执行,若当前存在事务,就将当前事务挂起。

    (6)NEVER:指定的方法不能在事务环境下执行,若当前存在事务,就之际抛出异常。

    (7)NESTED:指定的方法必须在事务内执行。若当前存在事务,则在嵌套事务内执行:若当前没有事务,则创建一个新事物。

    总结:

    C:定义了默认事务超时时限
    事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源。
    常量TIMEOUT_DEFAULT定义了事务底层默认的超时时限,及不支持事务超时时限设置的none值。
    注意:事务的超时时限起作用的条件比较多,且超时的时间计算点比较复杂。所以,该值一般就使用默认值即可。
    D:是否只读
    表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务

    2. Spring使用事务的步骤

    事务环境的准备

    1)准备数据库表

    create table t_account(
        id int auto_increment,
        name varchar(255),
        money double
    );
    

    2)编写DAO层及其实现类

    public interface AccountDao{
        // 价钱
        void increaseMoney(Integer id,Double money);
        // 减钱
        void decreaseMoney(Integer id,Double money);
    }
    public AccountDaoImpl extends JdbcDaoSupport implements AccountDao{
        @Override
        public void increaseMoney(Integer id,Double money){
            this.getJdbcTemplate().update("update t_account set money=money+? where id=? ",money,id);
        }
        @Override
        public void decreaseMoney(Integer id,Double money){
            this.getJdbcTemplate().update("update t_account set money=money-? where id=? ",money,id);
        }
    }
    

    3)编写Service层及其实现类

    public interface AccountService{
        // 转账方法
        void transfer(Integer from,Integer to,Double money);
    }
    public class AccountServiceImpl implements AccountService{
        private AccountDao ad;
        public void setAd(AccountDao ad){
            this.ad = ad;
        }
        @Override
        public void transfer(Integer from,Integer to,Double money){
            // 减钱
            ad.decreaseMoney(from,money);
            // 加钱
            ad.increaseMoney(to,money);
        }
    }
    

    4)Spring配置文件的编写

    <!-- 1.指定spring读取db.properties配置 -->
    <context:property-placeholder location="classpath:db.properties"  />
    
    <!-- 2.配置连接池 -->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
    	<property name="jdbcUrl" value="${jdbc.jdbcUrl}" ></property>
    	<property name="driverClass" value="${jdbc.driverClass}" ></property>
    	<property name="user" value="${jdbc.user}" ></property>
    	<property name="password" value="${jdbc.password}" ></property>
    </bean>
    <!-- 3.注入DAO对象 -->
    <bean name="accountDao" class="com.zhy.dao.AccountDaoImpl" >
    	<property name="dataSource" ref="dataSource" ></property>
    </bean>
    
    <!-- 4.配置Service对象 -->
    <bean name="accountService" class="com.zhy.service.AccountServiceImpl" >
    	<property name="ad" ref="accountDao" ></property>
    </bean>
    

    5)编写测试类

    @Runwith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class Demo{
        @Resource(name="accountService");
        private AccountService as;
        @Test
        public void fun(){
            as.transfer(1,2,100d);
        }
    }
    

    (1)编码式【观光即可】

    1)导包:beans,context.core,expression,aop,2个日志相关,数据库连接驱动,C3P0数据库,jdbc,tx,test

    2)将核心事务管理器配置到spring容器

    3)配置TransactionTemplate模板

    4)将事务模板注入Service

    5)在Service中调用模板

    (2)XML配置【AOP】

    原理就是将通知切入到目标对象中,生成代理对象。

    1)导包:beans,context.core,expression,aop,2个日志相关,数据库连接驱动,C3P0数据库,jdbc,tx,test

    2)导入新的约束:beans:最基本;context:读取properties配置;aop:配置aop;tx:配置事务通知

    3)配置通知

    <!-- 配置事务通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager" >
    	<tx:attributes>
    		<!-- 以方法为单位,指定方法应用什么事务属性
    			isolation:隔离级别
    			propagation:传播行为
    			read-only:是否只读
    		 -->
    		<tx:method name="save*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    		<tx:method name="persist*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    		<tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    		<tx:method name="modify*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    		<tx:method name="delete*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    		<tx:method name="remove*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    		<tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
    		<tx:method name="find*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
    		<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    	</tx:attributes>
    </tx:advice>
    

    4)配置将通知织入目标

    <!-- 配置织入 -->
    <aop:config  >
    	<!-- 配置切点表达式 -->
    	<aop:pointcut expression="execution(* cn.itcast.service.*ServiceImpl.*(..))" id="txPc"/>
    	<!-- 配置切面 : 通知+切点
    		 	advice-ref:通知的名称
    		 	pointcut-ref:切点的名称
    	 -->
    	<aop:advisor advice-ref="txAdvice" pointcut-ref="txPc" />
    </aop:config>
    

    (3)注解配置【AOP】

    1)导包:beans,context.core,expression,aop,2个日志相关,数据库连接驱动,C3P0数据库,jdbc,tx,test

    2)导入新的约束

    3)开启注解管理事务

    将XML配置的配置通知和配置切入去掉,该换用开启注解的设置。

    <!-- 1. 指定spring读取db.properties配置 -->
    <context:property-placeholder location="classpath:db.properties"  />
    <!-- 2. 事务核心管理器,封装了所有事务操作. 依赖于连接池 -->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
    	<property name="dataSource" ref="dataSource" ></property>
    </bean>
    <!-- 3. 事务模板对象 -->
    <bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate" >
    	<property name="transactionManager" ref="transactionManager" ></property>
    </bean>
    <!-- 4.将连接池 -->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
    	<property name="jdbcUrl" value="${jdbc.jdbcUrl}" ></property>
    	<property name="driverClass" value="${jdbc.driverClass}" ></property>
    	<property name="user" value="${jdbc.user}" ></property>
    	<property name="password" value="${jdbc.password}" ></property>
    </bean>
    <!-- 5.Dao-->
    <bean name="accountDao" class="cn.itcast.dao.AccountDaoImpl" >
    	<property name="dataSource" ref="dataSource" ></property>
    </bean>
    <!-- 6.Service-->
    <bean name="accountService" class="cn.itcast.service.AccountServiceImpl" >
    	<property name="ad" ref="accountDao" ></property>
    	<property name="tt" ref="transactionTemplate" ></property>
    </bean>  
    <!-- 7. 开启使用注解管理aop事务 -->
    <tx:annotation-driven/>
    

    4)使用注解

    在方法上添加@Transactional注解即可

    @Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED,readOnly=true)
    public class AccountServiceImpl implements AccountService {
    	private AccountDao ad ;
    	private TransactionTemplate tt;
    	@Override
    @Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED,readOnly=false)
    	public void transfer(final Integer from,final Integer to,final Double money) {
    				//减钱
    				ad.decreaseMoney(from, money);
    				int i = 1/0;
    				//加钱
    				ad.increaseMoney(to, money);
    	}
    	public void setAd(AccountDao ad) {
    		this.ad = ad;
    	}
    	public void setTt(TransactionTemplate tt) {
    		this.tt = tt;
    	}
    }
    
  • 相关阅读:
    如何在Ubuntu Server 18.04上安装Microsoft的Procmon
    如何在Ubuntu 20.04上安装Wine 5.0
    如何在Kali Linux 2020中启用SSH服务
    如何在Ubuntu 20.04 LTS Focal Fossa上安装Apache Groovy
    如何使用命令在Ubuntu 20.04 Linux上安装Vmware Tools
    在Ubuntu 20.04 LTS Focal Fossa上安装Zabbix Agent
    hdu 2089 不要62
    hdu 2093 成绩排名
    hdu 2104 hide handkerchief
    leetcode147对链表进行插入排序
  • 原文地址:https://www.cnblogs.com/zhy0720/p/10459112.html
Copyright © 2011-2022 走看看