1 事务的回顾
1.1 什么是事务
事务是针对数据库的一组操作,事务的语句要么都执行,要么都不执行。
1.2 事务特性
四大特性:ACID
原子性:整体
一致性:完整
隔离性:并发
持久性:结果
1.3 事务的隔离问题
脏读:一个事务读取到另一个事务未提交的数据。
不可重复读:一个事物读取到另一个事务已经提交的数据(update)。
虚读(幻读):一个事务读取到另一个事务已经提交的数据(insert)。
1.4 事务的隔离级别
read uncommitted:读未提交。存在上面的3个问题
read committed:读已提交。解决脏读,存在后2个问题
repeatable read:可重复读。解决:脏读、不可重复读,存在最后1个问题
serializable:串行化。都解决,相当于单事务。
1.5 事务的操作
当ABCD为一个事务时:
Connection conn = null; try{ conn = ...; conn.setAutoCommit(false); A B C D conn.commit(); } catch(){ conn.rollback(); }
当AB为必须事务,CD为可选事务时
Connection conn = null; Savepoint savepoint = null; //保存点,记录操作的当前位置,之后可以回滚到指定的位置。(可以回滚一部分) try{ conn = ...; conn.setAutoCommit(false); A B savepoint = conn.setSavepoint(); C D conn.commit(); } catch(){ if(savepoint != null){ //CD异常 // 回滚到CD之前 conn.rollback(savepoint); // 提交AB conn.commit(); } else{ //AB异常 // 回滚AB conn.rollback(); } }
1.6 Spring事务管理的三个核心接口
了解即可,因为在实际开发中一般不会手动地管理事务。
1.6.1 PlatformTransactionManager
平台事务管理器,用于管理事务。进行事务配置时,必须配置事务管理器。该接口中提供了三个事务操作方法:
-
- TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息
- void commit(TransactionStatus status):用于提交事务
- void rollback(TransactionStatus status):用于回滚事务
1.6.2 TransactionDefinition
事务详情(事务定义、事务属性),spring用于确定事务具体详情,进行事务配置时,必须配置详情。spring将配置项封装到该对象实例。
传播行为:在两个业务之间如何共享事务。
PROPAGATION_REQUIRED,必须【默认值】。支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将创建一个新的事务。
PROPAGATION_SUPPORTS,支持。支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将以非事务执行。
PROPAGATION_MANDATORY,强制。支持当前事务,A如果有事务,B将使用该事务。如果A没有事务,B将抛异常。
PROPAGATION_REQUIRES_NEW,必须新的。 如果A有事务,将A的事务挂起,B创建一个新的事务。如果A没有事务,B创建一个新的事务。
PROPAGATION_NOT_SUPPORTED,不支持。如果A有事务,将A的事务挂起,B将以非事务执行。如果A没有事务,B将以非事务执行。
PROPAGATION_NEVER,从不。如果A有事务,B将抛异常。如果A没有事务,B将以非事务执行。
PROPAGATION_NESTED,嵌套。A和B底层采用保存点机制,形成嵌套事务。
1.6.3 TransactionStatus
事务状态,spring用于记录当前事务运行状态。例如:是否有保存点,事务是否完成。我们不必关心这个,因为这是spring内部操作的事情。
2 搭建环境
2.1 创建表
创建数据库spring_transaction,并在数据库下创建表t_account,插入两条记录。
create database spring_transaction; use spring_transaction; create table t_account( id int(10) primary key auto_increment, username varchar(50), money int(10) ); insert into t_account(username,money) values('jack','10000'); insert into t_account(username,money) values('rose','10000');
2.2 导入jar包
创建项目,并且导入需要的jar包:
2.3 dao层
AccountDao.java
public interface AccountDao { public void in(String inner,Integer money); public void out(String outer,Integer money); }
AccountDaoImpl.java
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public void in(String inner, Integer money) { String sql = "update t_account set money=money+? where username=?"; Object[] args = {money,inner}; this.getJdbcTemplate().update(sql, args); } @Override public void out(String outer, Integer money) { String sql = "update t_account set money=money-? where username=?"; Object[] args = {money,outer}; this.getJdbcTemplate().update(sql,args); } }
2.4 service层
AccountService.java
public interface AccountService { public void transfer(String outer,String inner,Integer money); }
AccountServiceImpl.java
public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String outer,String inner,Integer money) { accountDao.in(inner, money); accountDao.out(outer, money); } }
2.5 Spring配置文件
applicationcontext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置DataSource的基本信息,需要注入四大参数 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_transaction"></property> <property name="user" value="root"></property> <property name="password" value="1122"></property> </bean> <!-- 创建AccountDao实例对象 需要注入dataSource --> <bean id="accountDao" class="com.tcxpz.spring_pro11.transaction.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 创建AccountService实例对象 需要注入accountDao --> <bean id="accountService" class="com.tcxpz.spring_pro11.transaction.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> </beans>
note:因为我们在AccountDaoImpl中继承了JdbcDaoSupport这个类,所以我们只用往AccountDaoImpl中注入dataSource,
AccountDaoImpl就可以通过this.getJdbcTemplate()方法获得JdbcTemplate实例对象并使用。
如果没有继承JdbcDaoSupport,那就需要我们在Spring中配置JdbcTemplate实例对象,并注入AccountDaoImpl。
2.6 测试
public class TestTransaction { @Test public void demo(){ String xmlPath = "applicationContext.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); AccountService accountService = (AccountService) applicationContext.getBean("accountService"); accountService.transfer("jack", "rose", 1000); } }
测试结果:
如果我们在service层模拟比如断电之类的出错情况。
控制台报错:
再看看数据库,转入账户增加了,而转出账户没有减少,这就是因为没有将转入转出当成一个事务进行管理。
3 手动管理事务(了解)
spring底层使用 TransactionTemplate 事务模板对事务进行管理。我们的service层拿到这个TransactionTemplate 事务模板就可以开启、提交、回滚事务了。
所以我们的Spring需要配置事务模板,并将模板注入给service层
而我们的TransactionTemplate 事务模板又依赖于事务管理器,所以Spring还要向TransactionTemplate 注入DataSourceTransactionManager对象
而事务管理器又要依赖于数据源,所以Spring还要向DataSourceTransactionManager注入DataSource对象
3.1 创建表
与上一个项目相同
3.2 导入jar包
与上一个项目相同
3.3 dao层
与上一个项目相同
AccountDao.java
public interface AccountDao { public void in(String inner,Integer money); public void out(String outer,Integer money); }
AccountDaoImpl.java
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public void in(String inner, Integer money) { String sql = "update t_count set money=money+? where username=?"; Object[] args = {money,inner}; this.getJdbcTemplate().update(sql, args); } @Override public void out(String outer, Integer money) { String sql = "update t_count set money=money-? where username=?"; Object[] args = {money,outer}; this.getJdbcTemplate().update(sql,args); } }
3.4 service层
要在service层对事务进行管理,主要是要修改service层的AccountServiceImpl类。
AccountService.java
public interface AccountService { public void transfer(String outer,String inner,Integer money); }
AccountServiceImpl.java
public class AccountServiceImpl implements AccountService { //向service层注入accountDao对象 private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } ////向service层注入transactionTemplate对象,用于对事务进行管理 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } @Override public void transfer(final String outer,final String inner,final Integer money) { transactionTemplate.execute(new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus status) { accountDao.in(inner, money); accountDao.out(outer, money); return null; } }); } }
3.5 Spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置DataSource的基本信息,需要注入四大参数 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_transaction"></property> <property name="user" value="root"></property> <property name="password" value="1122"></property> </bean> <!-- 配置AccountDao的实现类,需要注入DataSource,得到JdbcTemplate --> <bean id="accountDao" class="com.tcxpz.spring_pro10.transaction.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置AccountService的实现类,需要注入AccountDao的实现类 --> <bean id="accountService" class="com.tcxpz.spring_pro10.transaction.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> <property name="transactionTemplate" ref="transactionTemplate"></property> </bean> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
3.6 测试
public class TestTransaction { @Test public void demo(){ String xmlPath="applicationContext.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); AccountService accountService = (AccountService) applicationContext.getBean("accountService"); accountService.transfer("jack", "rose", 1000); } }
测试结果:
如果我们在service层模拟比如断电之类的出错情况。
控制台报错:
再看一下数据库,表中的数据没有发生变化。说明我们转入转出看作是一个事物了
4 TransactionProxyFactoryBean方式:半自动
上面的项目中,我们需要在service层开启事务,重写doInTransaction()方法,并且将我们的业务操作嵌在中间。
下面我们要使用TransactionProxyFactoryBean生成代理,它的优势在于代码中无须关注事务逻辑,而是交给Spring容器进行事务控制
4.1 创建表
与上一个项目相同
4.2 导入jar包
与上一个项目相同
4.3 dao层
与上一个项目相同
AccountDao.java
public interface AccountDao { public void in(String inner,Integer money); public void out(String outer,Integer money); }
AccountDaoImpl.java
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public void in(String inner, Integer money) { String sql = "update t_account set money=money+? where username=?"; Object[] args = {money,inner}; this.getJdbcTemplate().update(sql, args); } @Override public void out(String outer, Integer money) { String sql = "update t_account set money=money-? where username=?"; Object[] args = {money,outer}; this.getJdbcTemplate().update(sql,args); } }
4.4 service层
AccountService接口与上一个项目相同
AccountService.java
public interface AccountService { public void transfer(String outer,String inner,Integer money); }
AccountServiceImpl.java
public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(String outer,String inner,Integer money) { accountDao.in(inner, money); accountDao.out(outer, money); } }
4.5 Spring配置文件
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置DataSource的基本信息,需要注入四大参数 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_transaction"></property> <property name="user" value="root"></property> <property name="password" value="1122"></property> </bean> <!-- 配置AccountDao的实现类,需要注入DataSource,得到JdbcTemplate --> <bean id="accountDao" class="com.tcxpz.spring_peo12.proxy.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置AccountService的实现类,需要注入AccountDao的实现类 --> <bean id="accountService" class="com.tcxpz.spring_peo12.proxy.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- ************************************************************************ --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager"></property> <property name="target" ref="accountService"></property> <property name="proxyInterfaces" value="com.tcxpz.spring_peo12.proxy.AccountService"></property> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop> </props> </property> </bean> </beans>
4.6 测试
附上测试类程序吧,方便日后重现实验结果,测试结果与之前的类似,总之对事务进行了很好的管理
public class TestTransaction { @Test public void demo(){ String xmlPath = "applicationContext.xml"; ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); AccountService accountService = (AccountService) applicationContext.getBean("accountServiceProxy"); accountService.transfer("jack", "rose", 1000); } }
5 Spring AOP
5.1 基于XML方式
通过xml配置的方式,可以在applicationContext.xml中决定测试程序得到的是目标对象还是代理对象,而不用在测试类中修改getBean中的参数。
使用TransactionProxyFactoryBean实现声明式事务管理的方式的缺点是配置文件过于臃肿,难以阅读。因此Spring提供了tx/AOP配置的声明式事务管理方式,实际开发中最常用。
只需修改Spring配置文件applicationContext.xml即可。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 配置DataSource的基本信息,需要注入四大参数 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_transaction"></property> <property name="user" value="root"></property> <property name="password" value="1122"></property> </bean> <!-- 配置AccountDao的实现类,需要注入DataSource,得到JdbcTemplate --> <bean id="accountDao" class="com.tcxpz.spring_peo13.proxy.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置AccountService的实现类,需要注入AccountDao的实现类 --> <bean id="accountService" class="com.tcxpz.spring_peo13.proxy.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- ************************************************************************ --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- <tx:method> 给切入点方法添加事务详情 name:方法名称, *表示任意方法名称, save* 以save开头 propagation : 设置传播行为 isolation : 隔离级别 read-only:是否只读 --> <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" /> </tx:attributes> </tx:advice> <!-- aop 编写,让spring自动对目标生成代理,需要使用AspectJ的表达式 --> <aop:config> <!-- 切入点 --> <aop:pointcut expression="execution(* com.tcxpz.spring_peo13.proxy.*.*(..))" id="txPointCut" /> <!-- 切面:将切入点与通知整合 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" /> </aop:config> </beans>
测试结果与上面的项目相同,总之,对事务进行了很好的管理。
5.2 基于Annotation方式
Spring的声明式事务管理还可以通过Annotation注解的方式,这种方式非常简单,我们需要做以下两件事:
(1) 在Spring容器中注册驱动
(2) 在需要使用事务的业务类或者方法上添加注解@Transactional,这种方式的事务详情是通过@Transactional的参数进行配置的。
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 配置DataSource的基本信息,需要注入四大参数 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_transaction"></property> <property name="user" value="root"></property> <property name="password" value="1122"></property> </bean> <!-- 配置AccountDao的实现类,需要注入DataSource,得到JdbcTemplate --> <bean id="accountDao" class="com.tcxpz.spring_peo13.proxy.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置AccountService的实现类,需要注入AccountDao的实现类 --> <bean id="accountService" class="com.tcxpz.spring_peo13.proxy.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- ************************************************************************ --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
总结:
本篇博客主要讲了Spring事务管理,首先讲解了Spring事务管理的核心接口,这个只需要稍稍了解就行,毕竟后面用不着
然后通过银行转账的案例,分别介绍了没有进行事务管理,手动方式进行事务管理,基于TransactionProxyFactoryBean的事务管理和基于AOP的声明式事务管理几种方式。
其中,基于AOP的声明式事务管理又有xml和annotation两种方式,这个是最重要的,必须掌握。