什么是事务(Transaction)?事务是数据库中的概念,是指访问并可能更新数据库中各类数据项的一个程序执行单元(unit)。java
有个很是经典的转帐问题:A向B转款1000元,A转出成功,扣除了A帐户的1000元,但当系统准备向B帐户增长1000元出现了故障,转入失败,可是A帐户的金额已经扣除,这样的结果是A帐户凭空少了1000元,很明显这样是不行的,正确的作法应该是当B帐户增长成功后,A帐户的扣款才能生效。mysql
简单总结一句话就是:事务逻辑上的一组操做,组成这组操做的各个逻辑单元,要么一块儿成功,要么一块儿失败spring
原子性(Atomicity):事务是一个原子操做,由一系列动做组成。事务的原子性确保动做要么所有完成,要么彻底不起做用。sql
一致性(Consistency):一旦事务完成(无论成功仍是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不该该被破坏。数据库
隔离性(Isolation):可能有许多事务会同时处理相同的数据,所以每一个事务都应该与其余事务隔离开来,防止数据损坏。编程
持久性(Durability):一旦事务完成,不管发生什么系统错误,它的结果都不该该受到影响,这样就能从任何系统崩溃中恢复过来。一般状况下,事务的结果被写到持久化存储器中。并发
事务隔离意味着对于某一个正在运行的事务来讲,好像系统中只有这一个事务,其余并发的事务都不存在同样。大部分状况下,不多使用彻底隔离的事务。但不彻底隔离的事务会带来以下一些问题。app
脏数据(Dirty Read):一个事务读到了另外一个事务的未提交的数据ide
不可重读(Unrepeatable Read):一个事务读到了另外一个事务已经提交的 update 的数据致使屡次查询结果不一致测试
幻读(Phantom Read):一个事务读到了另外一个事务已经提交的 insert 的数据致使屡次查询结果不一致
经过设置事务隔离级别解决这些读的问题,事务隔离级别分别是:
读操做未提交(Read Uncommitted):说明一个事务在提交前,其变化对于其余事务来讲是可见的。这样脏读、不可重读和幻读都是容许的。当一个事务已经写入一行数据但未提交,其余事务都不能再写入此行数据;可是,任何事务均可以读任何数据。这个隔离级别使用排写锁实现(脏读,不可重复读,虚读都有可能发生)
读操做已提交(Read Committed):它使用临时的共读锁和排写锁实现。这种隔离级别不容许脏读,但不可重读和幻读是容许的(避免脏读。可是不可重复读和虚读有可能发生)
可重读(Repeatable Read):说明事务保证可以再次读取相同的数据而不会失败。此隔离级别不容许脏读和不可重读,但幻读会出现(避免脏读和不可重复读.可是虚读有可能发生)
可串行化(Serializable):提供最严格的事务隔离。这个隔离级别不容许事务并行执行,只容许串行执行。这样,脏读、不可重读或幻读均可发生(避免以上全部读问题)
Mysql 默认:可重复读
Oracle 默认:读已提交
由于在不一样平台,操做事务的代码各不相同.spring提供了一个接口: PlatformTransactionManager 接口
PlatformTransactionManager主要有三个方法
TransactionStatus getTransaction(TransactionDefinition definition) ,事务管理器 经过TransactionDefinition,得到“事务状态”,从而管理事务。
void commit(TransactionStatus status) 根据状态提交
void rollback(TransactionStatus status) 根据状态回滚
事务的状态:
获取事务时带入的接口为TransactionDefinition
隔离级别取值详解:
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底层采用保存点机制,造成嵌套事务。
实现该接口的有DataSourceTransactionManager和HibernateTransitionmanager
spring的事务处理分为编程式事务处理与声明式事务处理
编程式事务处理:所谓编程式事务指的是经过编码方式实现事务,容许用户在代码中精肯定义事务的边界。即相似于JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务处理:管理创建在AOP之上的。其本质是对方法先后进行拦截,而后在目标方法开始以前建立或者加入一个事务,在执行完目标方法以后根据执行状况提交或者回滚事务。声明式事务最大的优势就是不须要经过编程的方式管理事务,这样就不须要在业务逻辑代码中掺琐事务管理的代码,只需在配置文件中作相关的事务规则声明(或经过基于@Transactional注解的方式),即可以将事务规则应用到业务逻辑中。
简单地说,编程式事务侵入到了业务代码里面,可是提供了更加详细的事务管理;而声明式事务因为基于AOP,因此既能起到事务管理的做用,又能够不影响业务代码的具体实现。
理论不少了,开始编码吧,以转帐做为例子
DAO帐户接口:
//帐户接口 public interface AccountDao { //转出 void out(String outer,Double money); //转入 void in(String in,Double money); }
转帐实现:
//转帐实现 public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { public void out(String outer, Double money) { this.getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer); } public void in(String in, Double money) { this.getJdbcTemplate().update("update account set money = money + ? where username = ?",money,in); } }
Service接口:
public interface AccountService { void transfer(String from,String to,Double money); }
Service实现
public class AccountServiceImpl implements AccountService { // 业务层注入 DAO: private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public void transfer(String from, String to, Double money) { accountDao.out(from,money); accountDao.in(to,money); }
测试:
@Test public void fun1(){ ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml"); AccountService account = (AccountService) context.getBean("accountService"); //张三 向 李四 转帐1000 account.transfer("zhangsan", "lisi", 1000d); }
数据库原先数据:
执行方法以后:
这个结果很正常,如今咱们让程序出一些错误,在两个操做之间增长一个异常
accountDao.out(from,money); int i= 1/0;//异常操做 accountDao.in(to,money);
运行报错:java.lang.ArithmeticException: / by zero
可是张三的帐户仍是扣除了1000元,李四帐户并未改动
如今须要咱们来改造代码,让转帐支持事务,改造以下:
在AccountServiceImpl注入事务管理,代码以下:
public class AccountServiceImpl implements AccountService { // 业务层注入 DAO: private AccountDao accountDao; //注入事务管理 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public void transfer(final String from,final String to, final Double money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus arg0) { accountDao.out(from, money); int i = 1/0; accountDao.in(to, money); } }); } }
配置文件也作相应修改:
<?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" 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"> <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/test"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> <bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.yuanqinnan.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="txManager"></property> </bean> <!-- 配置事务管理器 ,管理器须要事务,事务从Connection得到,链接从链接池DataSource得到 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
测试方法不改,仍然报错,可是张三的帐户未被修改,说明事务生效
这种方式不修改原有的逻辑代码,只是修改配置文件,配置文件以下:
<?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/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://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> <bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.yuanqinnan.transaction.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 1 事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2 事务详情(事务通知) , 在aop筛选基础上,好比对ABC三个肯定使用什么样的事务。例如:AC读写、B只读 等 <tx:attributes> 用于配置事务详情(属性属性) <tx:method name=""/> 详情具体配置 propagation 传播行为 , REQUIRED:必须;REQUIRES_NEW:必须是新的 isolation 隔离级别 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/> </tx:attributes> </tx:advice> <!-- 3 AOP编程,利用切入点表达式从目标类方法中 肯定加强的链接器,从而得到切入点 --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.yuanqinnan.transaction.AccountServiceImpl.transfer(..))"/> </aop:config> </beans>
测试数据无误
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/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"> <context:component-scan base-package="com.yuanqinnan.transaction" ></context:component-scan> <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/test"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> <bean id="accountDao" class="com.yuanqinnan.transaction.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.yuanqinnan.transaction.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 1 事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2 将管理器交予spring * transaction-manager 配置事务管理器 * proxy-target-class true : 底层强制使用cglib 代理 --> <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/> </beans>
AccountServiceImpl加上注解便可实现
@Transactional(propagation=Propagation.REQUIRED , isolation = Isolation.DEFAULT) public class AccountServiceImpl implements AccountService {}
以上三种方式都可实现事务的管理,整个spring的入门总结到此结束。