Spring事务应用篇(一)声明式事务与编程式事务

Spring事务应用篇(一)声明式事务与编程式事务_第1张图片
以下为该应用篇章的结构,分为:声明式事务、编程式事务、基于SpringBoot的声明式事务应用三个部分,最后的坑提示不列入篇章结构

  • Spring事务如何应用
    • 声明式事务
    • 编程式事务
    • 基于SpringBoot的声明式事务应用
    • 实际应用可能存在的坑

说明:应用篇适合对Spring事务入门的简单应用,若要深入理解事务框架,请期待下一篇章,《Spring事务详细篇(二)事务框架的故事》

引言——事务

在讲解Spring事务的应用之前,我们必须清楚的一个概念就是何为事务,其实,事务这个术语不只是存在于Spring,它是一个属于计算机的术语,例如在MySQL数据库中也存在同样的术语——事务。简单来说,事务就是一系列操作序列总和,它有ACID四个特性,不了解的可以Google一下。
OK,接下来举个例子说明事务是什么——比如A在做一个对学号为XX的学生的账户余额进行查询并扣除25元再查询余额的操作,这个业务操作可能涉及到三个数据库操作,一个是查询,一个是修改,再一个也是查询;那么这一整个业务过程假如划分为3个事务,即每个操作都是一个事务;这样的话,假如原账户有30元,A在查询为30元的情况下,假如B直接对该学号账户进行了第4个事务(这个事务执行的时机刚好在A第一次查询之后),即从账户中扣除了20块,且该事务已经提交,这个时候账户就剩下10块钱;而A此时要扣除25元,但是账户中只剩下10块钱,这个事务提交之后,账户的余额就是-15元,接下来进行A的最后一个事务,查询过后,发现余额只剩下-15,就会一脸懵!?怎么回事。这就是并发或者事务划分带来的问题,那么怎么解决呢?从事务的4个特性中,我们可以了解到,有一个特性就是隔离性,即一个事务的执行过程不能被其它事务干扰;还有一个特性是原子性,即一个事务的操作序列要么都成功,要么都失败回滚。
这个时候,你是否已经发现,如果我们将A的三个操作在划分的时候整合为一个事务,那么就不会存在余额为-15的问题;哪怕B在A操作之前先扣除了20块钱,那么A这一整个事务就会回滚,而不会导致最后余额为-15的结果。
讲了这么多,相信大家对事务有所理解了!


一.Spring的事务

正如上面的例子,我们在实际的开发中经常会遇到事务的问题,特别是在高并发的场景下,我们可以用锁机制来解决并发事务所带来的问题,但是,Spring给我们带来另外一种处理方式,让我们可以在应用层既不需要用到锁的开销,也不需要写一大堆冗余的判断代码来解决这个问题。即Spring的事务。


1.声明式事务

声明式事务是属于无侵入式的,不会影响业务逻辑的实现。事务的微粒只能精确到方法,但是,如果我们想精确到方法块,就可以将该代码块封装成一个方法,再对这个方法进行事务声明。总之,基本上声明式事务已经替代了编程式事务


1.1声明式事务的实现方式一

在Spring中通过@Transactional注解实现如下:
以下是在Spring的配置文件中添加

   
     
    

 

以下是在操作数据库的方法上应用该注解的代码:

public class DataServiceImpl {
    @AutoWired
    private TestDao testDao;
    @Transactional(rollbackFor={RuntimeException.class, SQLException.class,Exception.class},propagation = Propagation.REQUIRES_NEW)
    public void testTransactional() {
        testDao.add(1, "tran");
        throw new RuntimeException();
    }
}

以下是Dao接口的定义:

@Mapper
public interface TestDao {
    @Insert("insert into  t1 (id,time_come) values(#{id},#{t})")
    void add(@Param("id")int id,@Param("t") String t);
}

以上面的代码为例,@Transactional注解加在了testTransactional的方法之上,这个方法内的全部数据库操作就是一个事务,如若以引言中的例子来说明,那么,A的三个数据库操作就可以放在该方法中,从而形成一个事务。
@Transactional注解有多个参数,rollbackFor={RuntimeException.class, SQLException.class,Exception.class}即在执行过程中如果抛出这三个异常之一即会发生事务的回滚。如果,我在最后一个查询余额的操作中,判断余额是否小于0,如果是,那么抛出异常,进行回滚,就不会出现余额为负数的情况。


1.2声明式事务的实现方式二

基于tx:标签与AOP进行声明式事务的实现。
以下是在配置文件中需要添加的内容:



	
	    
		
		
		
	



    
    
    

这种实现方式配置相比于注解的实现方式,配置文件的内容就比较复杂,如果对于Spring的AOP不熟悉,建议采用第一种基于注解的实现。


2.编程式事务

编程式事务每次实现都要单独实现,当业务量大功能复杂时,使用编程式事务无疑是痛苦的;但是编程式事务的好处就是事务的微粒可以精确到代码块。


1.编程式事务的实现

使用TransactionTemplate,该类继承了接口DefaultTransactionDefinition,用于简化事务管理,事务管理由模板类定义,主要是通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动启用事务管理。

TransactionTemplate模板类使用的回调接口:

  • TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
  • TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。

以下还是以一个测试用例说明一下用法:

import java.util.Map;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
 
@RunWith(SpringJUnit4ClassRunner.class)
//下面是配置文件
@ContextConfiguration(locations = { "classpath:spring.xml" })
public class TestTransaction {
	@Resource
	private PlatformTransactionManager txManager;
	@Resource
	private  DataSource dataSource;
	private static JdbcTemplate jdbcTemplate;
	Logger logger=Logger.getLogger(TestTransactiont.class);
    private static final String INSERT_SQL = "insert into t1 (number) values(?)";
    private static final String COUNT_SQL = "select count(*) from t1;
	
	@Test
public void testTransactionTemplate(){
	jdbcTemplate = new JdbcTemplate(dataSource);
    int i = jdbcTemplate.queryForInt(COUNT_SQL);  
    System.out.println("插入前表中记录总数:"+i);
	//初始化TransactionTemplate
	TransactionTemplate template = new TransactionTemplate(txManager);
	template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);  
	//重写execute方法实现事务管理
	template.execute(new TransactionCallbackWithoutResult() {
		@Override
		protected void doInTransactionWithoutResult(TransactionStatus status) {
			jdbcTemplate.update(INSERT_SQL, "测试字段");   //字段number为int型,所以插入肯定失败报异常,自动回滚,代表TransactionTemplate自动管理事务
		}}
	);
	i = jdbcTemplate.queryForInt(COUNT_SQL);  
    System.out.println("回滚后记录总数:"+i);
}
}

3.基于SpringBoot的声明式事务应用

接下来就讲基于SpringBoot使用声明式事务,在前面我们讲到使用@Transactionl注解实现声明式事务,那么,在Spring中应用,我们只需要在程序的入口添加@EnableTransactionl注解,即可,无需在配置文件中注入Bean。

4.可能遇到的坑

  • 事务出错但是不回滚
    • 事务应用的方法必须是public修饰的方法
    • Spring事务默认的回滚异常是RuntimException或者Error,其它异常需要回滚需要添加回滚属性
  • @Transactionl事务的执行优先级问题
    • 根据Spring的事务框架源码,可以了解到,优先级是从:方法->类->接口 。所以如果在方法和类上都进行了事务声明,则优先采用方法。
    • 建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制(基于接口的代理)是没问题;而使用使用CGLIB代理(继承)机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”

你可能感兴趣的:(原创,Spring)