• 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;
    	}
    }
    
  • 相关阅读:
    使用SandCastle生成代码注释文档
    如何修改默认的ModelState错误提示:字段{0}必须是一个数字
    2011总结 致:过去的30年
    CentOS下安装、配置Nginx,配合IIS做负载均衡
    灵活应用js调试技巧解决样式问题
    扩展IList对象,实现深拷贝扩展方法
    IE8对JS数组,采用属性遍历的解析差异
    如何开始Github
    Response.Clear 还是 Response.ClearHeaders
    URLRoutingModule如何处理静态文件?
  • 原文地址:https://www.cnblogs.com/zhy0720/p/10459112.html
Copyright © 2020-2023  润新知