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;
	}
}
posted @ 2019-03-01 22:24  24°独家KB  阅读(172)  评论(0编辑  收藏  举报