以下为该应用篇章的结构,分为:声明式事务、编程式事务、基于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中通过@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,如果是,那么抛出异常,进行回滚,就不会出现余额为负数的情况。
基于tx:标签与AOP进行声明式事务的实现。
以下是在配置文件中需要添加的内容:
这种实现方式配置相比于注解的实现方式,配置文件的内容就比较复杂,如果对于Spring的AOP不熟悉,建议采用第一种基于注解的实现。
编程式事务每次实现都要单独实现,当业务量大功能复杂时,使用编程式事务无疑是痛苦的;但是编程式事务的好处就是事务的微粒可以精确到代码块。
使用TransactionTemplate
,该类继承了接口DefaultTransactionDefinition
,用于简化事务管理,事务管理由模板类定义,主要是通过TransactionCallback
回调接口或TransactionCallbackWithoutResult
回调接口指定,通过调用模板类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动启用事务管理。
TransactionTemplate模板类使用的回调接口:
以下还是以一个测试用例说明一下用法:
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);
}
}
接下来就讲基于SpringBoot使用声明式事务,在前面我们讲到使用@Transactionl
注解实现声明式事务,那么,在Spring中应用,我们只需要在程序的入口添加@EnableTransactionl
注解,即可,无需在配置文件中注入Bean。
@Transactionl
事务的执行优先级问题
@Transactional
,而不要在接口上使用,这是因为如果使用JDK代理机制(基于接口的代理)是没问题;而使用使用CGLIB代理(继承)机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional
注解是“不能继承的”