1.什么是事务(Transaction)
一组业务逻辑(如:ABCDEF)要么全部执行成功,要么全部执行不成功的操作就叫做事务。
1.1事务的特性:
原子性(Atomicity):一个事务就如同一个原子,一个不可分割的部分。
一致性(Consistency):如同执行数据库操作后,数据不会破坏。(假设给A账户转钱,A账户扣了钱,不可能B账户没有增加钱)
隔离性(Transaction isolation Level):关于事务的并发。(假设我们编写了一条update语句对数据库进行操作,同时别 人也编写了一条delete对相同的数据进行操作,可以想象,如果先执行了delete后,再执行update就会报错)
持久性(Durability):当我们对数据库执行了insert操作后,数据库就应该保证有一条数据永久性的存放在磁盘中。
1.2隔离性问题:
脏读(Dirty Read):一个事务读到了另一个事务没有提交的数据。(例如:在T1时刻事务A开始事务。在T2时刻事务A查询账户余额为1000,同时在T2时刻事务B开始事务。在T3时刻事务A从账户取出100元并把余额改为900。在T4时刻,事务B查询账户余额为900。在T5时刻事务A撤销事务,并把余额恢复为1000。在T6时刻事务B向账户存入100,并把余额改为1000。在T7时刻事务B提交事务。然而此时账户余额应该为1100,而实际上账户余额只有1000,这样就违背了事务的一致性)
不可重复读(Unrepeatable Read):一个事务读取到了另一个事务已经提交了的数据(update)。(例如:在T1时刻事务A开始事务。在T2时刻事务B开始事务。在T3时刻事务B查询账户余额为1000。在T4时刻事务A查询账户余额为1000。在T5时刻事务B从账户中存入100。在T6时刻事务B提交事务。在T7时刻事务A从账户取出100。在T8时刻事务A撤销事务将余额恢复为1000。然而事实上事务B向账户存入了100,账户余额为1100。而当事务A回滚的时候将账户余额改为了1000)
幻读(Phantom Read):一个事务读取到了另一个事务已经提交了的数据(insert)。(例如:在T1时刻事务A开始事务,在T2时刻事务B开始事务。在T3时刻事务A统计总存款为1000。在T4时刻事务B取出100,并把余额改为900。在T5时刻事务B提交事务。在T6时刻事务A向账号中汇入100并把余额改为1100。在T7时刻事务A提交事务。然而实际上事务B已经取出了100,事务A存入100是在事务B取出100前进行读取余额为1000,这样就导致了数据增多)
1.3隔离级别:
Read Committed(读已提交):解决了脏读问题,而没有解决不可重复读和幻读。
Read Uncommitted(读未提交):存在3个问题。
Repeatable Read(可重复读):解决脏读和不可重复读。存在一个问题。
Serializable(串行化):解决了所有问题。
JDBC也提供了这四种事务的隔离级别。四种隔离级别的执行效率按(Read Uncommitted》Read Committed》Repeatable Read》Serializable)递减。
2.Spring对事务的管理介绍
2.1需要导入jar包(Spring-tx.jar)
2.2spring-tx.jar包中的三个顶级接口介绍:
PlatformTransactionManager(平台事务管理器):要通过Spring管理事务就必须使用事务管理器,因此就必须配置事务管理器。
由于Spring-tx.jar包中就只定义了平台事务管理器的接口,并没实现事务管理,因此需要提供实现类,因此就需要导入以下jar包(spring-jdbc.jar这是Spring整合jdbc开发用的。spring-orm.jar这是Spring整合hibernate开发用的)。常见的事管理器有:DataSourceTransactionManager(jdbc开发时事务管理器)、HibernateTransactionManager(hibernate开发时事务管理器)
TransactionDefinition(事务详情、事务定义、事务属性):Spring用于确定事务的具体详情,例如:隔离级别、是否只读、超时时间等。
Spring在事务详情中提供了事务的传播行为(两个业务之间如何共享事务):
PROPAGATION_REQUIRED , required , 必须 【默认值】
支持当前事务,A如果有事务,B将使用该事务。
如果A没有事务,B将创建一个新的事务。
PROPAGATION_SUPPORTS ,supports ,支持
支持当前事务,A如果有事务,B将使用该事务。
如果A没有事务,B将以非事务执行。
PROPAGATION_MANDATORY,mandatory ,强制
支持当前事务,A如果有事务,B将使用该事务。
如果A没有事务,B将抛异常。
PROPAGATION_REQUIRES_NEW , requires_new ,必须新的
如果A有事务,将A的事务挂起,B创建一个新的事务
如果A没有事务,B创建一个新的事务
PROPAGATION_NOT_SUPPORTED ,not_supported ,不支持
如果A有事务,将A的事务挂起,B将以非事务执行
如果A没有事务,B将以非事务执行
PROPAGATION_NEVER ,never,从不
如果A有事务,B将抛异常
如果A没有事务,B将以非事务执行
PROPAGATION_NESTED ,nested ,嵌套
A和B底层采用保存点机制,形成嵌套事务。
TransactionStatus(事务状态):Spring用于记录当前事务运行状态。例如:是否有保存点、事务是否完成,Spring底层就会根据状态进行相应的操作。
3.通过TransactionTemplate手动方式管理事务
以转账为例进行以下程序的编写。
dao层接口:
public interface IAccountDao { /** * 收款方 * @param inner * @param money */ public void in(String inner,int money); /** * 汇款方 * @param outer * @param money */ public void out(String outer,int money); }
dao层接口实现类:dao层需要访问数据库,因此在dao层实现类中继承了JdbcDaoSupport的支持。因此在配置文件中需要对dao层的实现类注入DataSource。
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{ @Override public void in(String inner, int money) { this.getJdbcTemplate().update("update account set money=money+? where username=?", money,inner); } @Override public void out(String outer, int money) { this.getJdbcTemplate().update("update account set money=money-? where username=?", money,outer); } }
service层的接口:
public interface IAccountService { /** * 转账 * @param outer * @param inner * @param money */ public void transfer(String outer,String inner,int money); }service层接口的实现类:service层为业务逻辑层,因此在Service的实现类中需要注入dao层。由于service层需要事务管理,在这里我们采用的是TransactionTemplate来手动方式管理事务,因此也需要给service实现类中注入TransactionTemplate。
public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } //通过spring容器注入事务模板 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } @Override //通过transactionTemplate的execute(TransactionCallbackWithoutResult arg0)方法来管理事务 public void transfer(final String outer, final String inner,final int money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { accountDao.out(outer, money); int a = 1/0; accountDao.in(inner, money); }}); } }
applicationContext.xml配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" 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"> <!-- 注册数据源并注入数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///spring_study_day02"></property> <property name="user" value="root"></property> <property name="password" value="12345678"></property> </bean> <!-- 注册accountDao --> <bean id="accountDao" class="com.lc.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 注册accountService --> <bean id="accountService" class="com.lc.service.impl.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>
4.通过TransactionProxyFactoryBean来实现半自动管理事务
dao层接口:
public interface IAccountDao { /** * 收款方 * @param inner * @param money */ public void in(String inner,int money); /** * 汇款方 * @param outer * @param money */ public void out(String outer,int money); }
dao层接口实现类:dao层需要访问数据库,因此在dao层实现类中继承了JdbcDaoSupport的支持。因此在配置文件中需要对dao层的实现类注入DataSource。
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{ @Override public void in(String inner, int money) { this.getJdbcTemplate().update("update account set money=money+? where username=?", money,inner); } @Override public void out(String outer, int money) { this.getJdbcTemplate().update("update account set money=money-? where username=?", money,outer); } }
service层的接口:
public interface IAccountService { /** * 转账 * @param outer * @param inner * @param money */ public void transfer(String outer,String inner,int money); }
service接口的实现类:由于service是业务逻辑层,因此这里需要注入dao层。因为采用TransactionProxyFactoryBean半自动方式来管理事务,因此这里不需要注入其他。只需要在applicationContext.xml配置文件中使用TransactionProxyFactoryBean来生成service层的代理类来增强事务的管理。注意在使用service层的时候是使用Service的代理类也就是proxyAccountService
public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(final String outer, final String inner,final int money) { accountDao.out(outer, money); // int a = 1/0; accountDao.in(inner, money); } }applicationContext.xml:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" 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"> <!-- 注册数据源并注入数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///spring_study_day02"></property> <property name="user" value="root"></property> <property name="password" value="12345678"></property> </bean> <!-- 注册accountDao --> <bean id="accountDao" class="com.lc.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 注册accountService --> <bean id="accountService" class="com.lc.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 4.1 proxyInterfaces 接口 4.2 target 目标类 4.3 transactionManager 事务管理器 4.4 transactionAttributes 事务属性(事务详情) prop.key :确定哪些方法使用当前事务配置 prop.text:用于配置事务详情 格式:PROPAGATION,ISOLATION,readOnly,-Exception,+Exception 传播行为 隔离级别 是否只读 异常回滚 异常提交 例如: <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop> 默认传播行为,和隔离级别 <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly</prop> 只读 <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT,+java.lang.ArithmeticException</prop> 有异常扔提交 --> <bean id="proxyAccountService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="target" ref="accountService"></property> <property name="proxyInterfaces" value="com.lc.service.IAccountService"></property> <property name="transactionManager" ref="transactionManager"></property> <property name="transactionAttributes"> <props> <prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop> </props> </property> </bean> <!-- 注册事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
5.通AOP来实全自动管理事务
dao层:
public interface IAccountDao { /** * 收款方 * @param inner * @param money */ public void in(String inner,int money); /** * 汇款方 * @param outer * @param money */ public void out(String outer,int money); }
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{ @Override public void in(String inner, int money) { this.getJdbcTemplate().update("update account set money=money+? where username=?", money,inner); } @Override public void out(String outer, int money) { this.getJdbcTemplate().update("update account set money=money-? where username=?", money,outer); } }
service层:
public interface IAccountService { /** * 转账 * @param outer * @param inner * @param money */ public void transfer(String outer,String inner,int money); }
public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(final String outer, final String inner,final int money) { accountDao.out(outer, money); // int a = 1/0; accountDao.in(inner, money); } }
applicationContext.xml:这里需要在applicationContext的头文件中加入事务的命名空间:
xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 注册数据源并注入数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///spring_study_day02"></property> <property name="user" value="root"></property> <property name="password" value="12345678"></property> </bean> <!-- 注册accountDao --> <bean id="accountDao" class="com.lc.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 注册accountService --> <bean id="accountService" class="com.lc.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 4 事务管理 --> <!-- 4.1 事务管理器 --> <bean id="txManage" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 4.2 事务详情(事务通知) , 在aop筛选基础上,对ABC三个确定使用什么样的事务。例如:AC读写、B只读 等 <tx:attributes> 用于配置事务详情(事务属性) <tx:method name=""/> 详情具体配置 propagation 传播行为 , REQUIRED:必须;REQUIRES_NEW:必须是新的 isolation 隔离级别 --> <tx:advice id="txAdvice" transaction-manager="txManage"> <tx:attributes> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/> <tx:method name="find*" read-only="true"/> </tx:attributes> </tx:advice> <!-- 4.3 AOP编程,目标类有ABCD(4个连接点),切入点表达式 确定增强的连接器,从而获得切入点:ABC --> <aop:config > <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.lc.service.impl.AccountServiceImpl.*(..))"/> </aop:config> </beans>
6.通过注解的方式实现管理事务
dao层:
public interface IAccountDao { /** * 收款方 * @param inner * @param money */ public void in(String inner,int money); /** * 汇款方 * @param outer * @param money */ public void out(String outer,int money); }
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{ @Override public void in(String inner, int money) { this.getJdbcTemplate().update("update account set money=money+? where username=?", money,inner); } @Override public void out(String outer, int money) { this.getJdbcTemplate().update("update account set money=money-? where username=?", money,outer); } }
service层:
public interface IAccountService { /** * 转账 * @param outer * @param inner * @param money */ public void transfer(String outer,String inner,int money); }
service层的实现类:在Service实现类中需要加上@Transactional(propagation="事务的传播行为",isolation="事务的隔离界别")注解代表该类需要管理事务。
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT) public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public void transfer(final String outer, final String inner,final int money) { accountDao.out(outer, money); int a = 1/0; accountDao.in(inner, money); } }
applicationContext.xml需要配置让事务注解生效,只要需要事务,都需要配置事务管理器。
让@Transactional注解生效代码
<tx:annotation-driven transaction-manager="transactionManager" />
<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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 注册数据源并注入数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///spring_study_day02"></property> <property name="user" value="root"></property> <property name="password" value="12345678"></property> </bean> <!-- 注册accountDao --> <bean id="accountDao" class="com.lc.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 注册accountService --> <bean id="accountService" class="com.lc.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 4 事务管理 --> <!-- 4.1 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 4.2 将管理器交予spring * transaction-manager 配置事务管理器 * proxy-target-class true : 底层强制使用cglib 代理 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>