事务是指逻辑上要么全部成功、要么全部失败的一组操作。例如用户A给用户B转账,则用户A账户余额减少、用户B账户增加这两个操作就是一组事务,必须全部成功或失败撤回操作,不能出现A账户余额减少,B增加失败的情况。事务具有如下几个特性:
spring提供了三个接口用于实现事务的管理,在进行事务管理时,spring首先会读取TransactionDefinition中隔离、传播、超时等事务定义信息,然后PlatformTransactionManager会根据这些信息进行事务管理,然后将产生的事务状态保存在TransactionStatus中
事务定义信息接口中ISOLATION开头的常量用于定义事务的隔离级别、PROPAGATION开头定义事务传播行为、TIMEOUT_DEFAULT定义超时时间。
隔离级别用于解决多个事务提交时可能出现的脏读、不可重复读、
事务的隔离级别有如下四种类型,如果为DEFAULT则使用后端数据库默认的隔离级别,例如MySQL使用的是repeatable_read,Oracle数据库使用的是read_committed级别
READ_UNCOMMITED | 允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读 |
READ_COMMITTED | 允许在并发事努已经提交后读取。可防止脏读,但幻读和不可重复读仍可发生 |
REPEATABLE_READ | 对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。 |
SERIALIZABLE | 完全服从ACID的隔离级别,通过完全锁定事务中涉及的数据表来确保不发生脏、幻、不可重复读,但执行效率最低 |
事务的传播行为用于解决业务层方法互相调用时如何传递事务的问题。例如方法a和b中都用到了事务T,那么a在调用b时是新建一个事务T还是使用b中的事务T呢?有如下七种传播方式
PROPAGATION_REQUIRED | 支持当前事务,如果不存在就新建一个 |
PROPAGATION_SUPPORTS | 支持当前事务,如果不存在,就不使用事务 |
PROPAGATION_MANDATORY | 支持当前事务,如果不存在,抛出异常 |
PROPAGATION_REQUIRES_NEW | 如果有事务存在,挂起当前事劳,创建一个新的事务 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行,如果有事务存在,挂起当前事务 |
PROPAGATION_NEVER | 以非事务方式运行,如果有事务存在,抛出异常 |
PROPAGATION_NESTED | 如果当前事务存在,则嵌套执行事务 |
spring为不同的持久层框架提供了相应的PlatformTransactionManager接口实现,spring JDBC和MyBatis对应DataSourceTransactionManager、Hibernate对应HibernateTransactionManager,还有JPA、Jdo、JTA等,不同的持久层使用不同的实现类
org.springframework.jdbc.datasource.DataSourceTransaction.Manager
|
Spring JDBC或iBatis逬行持久化数据时使用
|
org.springframework.orm.hibernate3.HibernateTransactionManager
|
Hibernate3.0版本进行持久化数据时使用
|
org.springframework.orm.jpa.JpaTransactionManager
|
JPA进行持久化时使用
|
org.springframework.jdo.JdoTransaction Manager
|
当持久化机制是Jdo时使用
|
org.springframework.transactionjta.JtaTransactionManager
|
JTA管理事务,在一个事务跨越多 个资源时必须使用
|
用于记录事务是否完成、是否产生保存点、是否可回滚等状态。
在Spring中使用事务有两种方式,第一种是通过手动编写来实现一个事务。
如下左图所示为一个用户账户的数据表,通过事务管理实现根据id对账户的money进行转账,以实现一个账户money减少的同时另一个增加。
1、引入jar包。上面右图所示为项目的目录结构,lib文件夹中包含了项目依赖的jar包,包括Spring基础包commons-logging、spring-core、spring-beans、spring-context、spring-expression、spring-test,以及连接数据库的spring-jdbc、mysql-connector,事务管理spring-tx,此外要用到c3p0对数据库连接池进行管理,除了引入c3p0包之外还需要引入mchange-commons包才可以使用。
2、配置数据源连接,在jdbc.properties文件中完成数据库连接的配置
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=1234
接着在配置文件spring-transaction.xml中引入properties并将属性注入c3p0的配置中,完成dataSource的配置
3、实现数据对象的DAO类,在AccountDao类中通过继承spring的JdbcDaoSupport类,可以使用JdbcTemplate中的update()方法完成对数据库的更新操作。由于JdbcDaoSupport需要DataSource获取数据库的连接,因此在AccountDao的Bean配置中通过属性注入c3p0数据源dataSource
package com.transaction;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDao extends JdbcDaoSupport {
public void transferIn(int id,double money){
String sql="UPDATE customers SET money = money + ? WHERE id =?";
this.getJdbcTemplate().update(sql,money,id); //使用JdbDaoSupport类的方法操作数据库
}
public void transferOut(int id,double money){
String sql="UPDATE customers SET money = money - ? WHERE id =?";
this.getJdbcTemplate().update(sql,money,id);
}
}
4、实现事务操作Service类。在AccountService类中完成事务操作,spring编程式的事务管理通过Spring的TransactionTemplate类来实现,将一个事务操作放在其execute()方法内完成,execute()需要传入一个TransactionCallback类作为参数,这里采用匿名内部类的方式实现。在内部类中doIntransaction()方法中执行事务操作,通过调用DAO层的方法完成具体数据库操作
package com.transaction;
import org.springframework.transaction.support.TransactionTemplate;
public class AccountService {
private AccountDao accountDao;
private TransactionTemplate transactionTemplate;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public void transfer(int inAccount, int outAccount, double money) {
//使用spring的TransactionTemplate进行事务操作
transactionTemplate.execute(new TransactionCallback
AccountService类中用到了accountDao和transactionTemplate两个属性,因此在配置Bean时需要注入两个bean对象。accountDao类是之前创建的,那么transactionTemplate类哪里来的呢?transactionTemplate是DataSourceTransactionManager创建的,之前提到它是PlatformTransactionManager接口用于spring jdbc的一个实现类。所以需要先创建一个transactionManager的bean,而要创建它还需要注入DataSource作为属性。
项目的数据流动如下:
最后测试AccountService类,调用accountService对象的transfer()方法从id为2的向1转账50。由于在事务中进行了除以0的操作,事务执行失败并且回滚,数据库操作不生效
class AccountServiceTest {
@org.junit.jupiter.api.Test
void transfer() {
ApplicationContext appCtx=new ClassPathXmlApplicationContext("spring-transaction.xml");
AccountService accountService= appCtx.getBean("accountService",AccountService.class);
accountService.transfer(2,1,50);
}
}
Spring基于声明式的事务管理是利用AOP的思想,因此需要额外引入spring-aop和aopalliance两个jar包。由于SpringAOP的使用有三种方式,所以声明式事务管理也有三种:通过spring的TransactionProxy、使用
在accountService类的基础上通过TransactionProxyBeanFactory产生代理类serviceProxy来对事务进行管理,由于是通过AOP引入的方式,所以原来的accountService类不需要增加transactionTemplate
public class AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(int inAccount, int outAccount, double money) {
accountDao.transferIn(inAccount, money);
int i = 1/0; //除数为0会抛出异常
accountDao.transferOut(outAccount, money);
}
}
xml文件中也不需要对TransactionTemplate进行配置,只需要保留TransactionManager,但是需要新配置代理类serviceProxy,代理配置时需要注入属性--代理目标对象、事务管理器和事务属性。在transactionAttributes中通过
PROPAGATION_REQUIRED,ISOLATION_REPEATABLE_READ,+ArithmeticException
在使用时通过serviceProxy获取代理Bean,通过增强后的代理调用transfer()
@Test
void transfer() {
ApplicationContext appCtx=new ClassPathXmlApplicationContext("spring-transaction.xml");
AccountService accountService= appCtx.getBean("serviceProxy",AccountService.class);
accountService.transfer(1,2,50);
}
由于上面的方法需要为每一个事务对象单独配置一个代理类,较为繁琐,所以实际中不常用。使用AspectJ不产生代理类,而是直接织入到accountService中。由于需要使用aspectJ进行织入,所以需要引入aspectjweaver.jar包。此外在xml配置文件中使用到了
......
由于不产生代理类,可以直接使用accountService对象
@Test
void transfer() {
ApplicationContext appCtx=new ClassPathXmlApplicationContext("spring-transaction.xml");
AccountService accountService= appCtx.getBean("accountService",AccountService.class);
accountService.transfer(1,2,50);
}
通过注解来使用spring事务管理代码更为简洁,只需要在xml配置文件中配置事务管理器并开启事务注解即可
接着在具体要使用到事务的类上添加@Transactional注解即可。在注解中可以指定隔离级别、传播属性、回滚/不回滚异常等属性
package com.transaction;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,
rollbackFor = java.lang.ArithmeticException.class)
public class AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(int inAccount, int outAccount, double money) {
accountDao.transferIn(inAccount, money);
int i = 1/0; //除数为0会抛出异常
accountDao.transferOut(outAccount, money);
}
}