Introduction
事务是什么?
事务的作用
- 事务的特性
- Atomic原子性、Consistency一致性、Isolation隔离性和Durability持久性。
- 原子性:指整个事务是不可以分割的工作单元。只有事务中所有的操作执行成功,才算整个事务成功,事务中任何一个SQL语句执行失败,那么已经执行成功的SQL语句也必须撤销,数据库状态应该回到执行事务前的状态。
- 一致性:指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。例如对于银行转账事务,不管事务成功还是失败,应该保证事务结束后两个转账账户的存款总额是与转账前一致的。
- 隔离性:指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。
- 持久性:指的是只要事务成功结束它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
BEGIN; COMMIT;
- 使用场景
简单举个例子就是你要同时修改数据库中两个不同表的时候,如果它们不是一个事务的话,当第一个表修改完,可是第二表改修出现了异常而没能修改的情况下,就只有第二个表回到未修改之前的状态,而第一个表已经被修改完毕。
jdbc处理事务的方法
- example
try{ Connection conn = getConnection(); // 不管如何我们得到了链接 conn.setAutoCommit(false); // 插入订单 // 修改库存 conn.commit(); // 成功的情况下,提交更新。 } catch(SQLException ex) { conn.rollback(); // 失败的情况下,回滚所有的操作 } finally { conn.close(); }
- 优点
简单、直观 - 缺点
- 代码罗嗦
- 多个连接时,处理起来更罗嗦
现有系统中,对事务的处理
- 取出 ExecutorType.BATCH的session
- 一次传入多个sqlId,
public static int[] batchUpdate(String sqlId, List<?> objList) { int[] idArr = new int[objList.size()]; SqlSession session = null; try { session = sqlSessionFactory.openSession(ExecutorType.BATCH); int index = 0; for (Object obj : objList) { idArr[index++] = session.update(sqlId, obj); } session.commit(); } catch (Exception e) { logger.warn("batch update error:" + sqlId, e); e.printStackTrace(); } finally { if (session != null) { session.close(); } } return idArr; }
- 缺点
- 直接操作sqlId,service 和 dao不好分层
- 不支持update 和 select、insert交叉的情况
- 不能简单的支持跨db操作
spring里对事务的支持
spring为多种不同类型的事务管理机制提供统一编程模型,简化了对事务的使用。
spring处理事务的结构
Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。
- DataSource
数据库连接源 - TransactionManager
Spring的事务管理通过org.springframework.transaction.PlatformactionManager接口完成.PlatformTransactionManager是一个服务提供商接口(SPI),针对不同类型的底层事务框架,Spring提供了不同的PlatFormTransactionManager实现版本
接口定义如下public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
- 代理机制
用代理类来替代你真实操作db的方法
spring配置事务的方法
Spring里对事务的使用
这里只介绍使用@Transactional注解的方法
简单的来说,只需要在DAO或者Service的类或者方法加上@Transactional的注解。 譬如
@Transactional
public class TransactionShowCaseService extends BaseService {
public int insertWithoutException(User record) {
return userMapper.insert(record);
}
}
查看代码 testTransactionAdd() 和 testTransactionAddThrowException()
testTransactionMethod() 和 testNoTransactionMethod()
查看代码testNotSupportTransAction()
实现方法
AOP,在注解的方法完成之后,调用commit;
查看 代码 testTransactionWithSleep(),等事务完成之后,最后才提交.数据库记录为
[searcher b2c_product [unknown] 192.168.10.65 2014-03-29 15:13:31.009 CST]LOG: execute S_1: BEGIN
[searcher b2c_product [unknown] 192.168.10.65 2014-03-29 15:13:31.011 CST]LOG: execute <unnamed>: INSERT INTO testUser (id,userName,phone)
VALUES ($1,$2,$3)
[searcher b2c_product [unknown] 192.168.10.65 2014-03-29 15:13:31.011 CST]DETAIL: parameters: $1 = '3033', $2 = NULL, $3 = '2123'
[searcher b2c_product [unknown] 192.168.10.65 2014-03-29 15:13:41.022 CST]LOG: execute S_2: COMMIT
系统中曾出现的问题:
在事务里,先更新订单状态,然后调用外部接口。由于外部接口看不到这个事务里的修改,发现订单状态错误,于是报错
spring事务行为
传播行为定义了调用方法之间的事务边界。传播规则回答了这样的问题:新的时候应该被启动,还是挂起? 方法是否要在事务环境中运行
PROPAGATION_REQUIRED
当前事务必须出现在事务中,如果有则使用原来的session。否则,创建一个新的.
– 默认的行为
PROPAGATION_NOT_SUPPORT
不应该运行在事务中,如果当前存在事务,那么在该方法运行期间,事务被挂起。
查看代码 testNotSupportTransAction()
猜测 testNotSupport 的运行结果
DEBUG java.sql.Connection 2014-04-03 12:19:54 - ooo Connection Opened
DEBUG java.sql.PreparedStatement 2014-04-03 12:19:54 - ==> Executing: INSERT INTO testUser (id,userName,phone) VALUES (?,?,?)
DEBUG java.sql.PreparedStatement 2014-04-03 12:19:54 - ==> Parameters: 4154(Integer), null, 2123(String)
DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-04-03 12:19:54 - Suspending current transaction
DEBUG org.springframework.jdbc.datasource.DataSourceUtils 2014-04-03 12:19:54 - Fetching JDBC Connection from DataSource
DEBUG org.springframework.jdbc.datasource.DataSourceUtils 2014-04-03 12:19:54 - Registering transaction synchronization for JDBC Connection
DEBUG java.sql.Connection 2014-04-03 12:19:54 - ooo Connection Opened
DEBUG java.sql.PreparedStatement 2014-04-03 12:19:54 - ==> Executing: SELECT id, userName, phone FROM testUser WHERE id = ?
DEBUG java.sql.PreparedStatement 2014-04-03 12:19:54 - ==> Parameters: 4154(Integer)
DEBUG org.springframework.jdbc.datasource.DataSourceUtils 2014-04-03 12:19:54 - Returning JDBC Connection to DataSource
DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-04-03 12:19:54 - Resuming suspended transaction after completion of inner transaction
DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-04-03 12:19:54 - Initiating transaction commit
Propagation.MANDATORY
如果上下文不在事务中,则抛出异常。
运行代码 testErrorMandatory() , testOkMandatory()
Propagation_NEVER
不应该出现在事务上下文中,如果出现在上下文中,则抛出异常
testNever
Propagation_requires_new
必须运行在自己的事务中。如果已经存在事务,则当前事务会被挂起,自己另起一个事务
Propagation_supports
不需要上下文,但是如果存在当前的事务,那么这个方法会在这个事务中进行
Propagation.NEST
如果存在一个事务,则单独起一个嵌套的事务,对事物单独的提交。如果没有起事务,就启动一个新的事务. 貌似比较少见。。。。。看不太明白. 具体请参考 http://ahujyy.iteye.com/blog/1544304
运行代码 testNest()
DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-04-03 11:44:16 - Switching JDBC Connection [DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-04-03 11:44:16 - Participating in existing transaction
DEBUG java.sql.Connection 2014-04-03 11:44:16 - ooo Connection Opened
DEBUG java.sql.PreparedStatement 2014-04-03 11:44:16 - ==> Executing: INSERT INTO testUser (id,userName,phone) VALUES (?,?,?)
DEBUG java.sql.PreparedStatement 2014-04-03 11:44:16 - ==> Parameters: 6238(Integer), null, 2123(String)
DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-DEBUG java.sql.PreparedStatement 2014-04-03 11:44:16 - ==> Executing: INSERT INTO testUser (id,userName,phone) VALUES (?,?,?)
DEBUG java.sql.PreparedStatement 2014-04-03 11:44:16 - ==> Parameters: 6238(Integer), null, 2123(String)
DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-04-03 11:44:16 - Releasing transaction savepoint
DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-04-03 11:44:16 - Initiating transaction commit
DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-0DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-0DEBUG org.springframework.jdbc.datasource.DataSourceUtils 2014-04-03 11:44:16 - Returning JDBC Connection to DataSource
回滚的策略
异常分类
- checked异常
表示无效,不是程序中可以预测的。比如无效的用户输入,文件不存在,网络或者数据库链接错误。这些都是外在的原因,都不是程序内部可以控制的。
必须在代码中显式地处理。比如try-catch块处理,或者给所在的方法加上throws说明,将异常抛到调用栈的上一层。
继承自java.lang.Exception(java.lang.RuntimeException除外)。熟悉的checked 异常包括
Java.lang.ClassNotFoundException
Java.lang.NoSuchMetodException
java.io.IOException - unchecked异常
表示错误,程序的逻辑错误。是RuntimeException的子类,比如IllegalArgumentException, NullPointerException和IllegalStateException。
不需要在代码中显式地捕获unchecked异常做处理。
继承自java.lang.RuntimeException(而java.lang.RuntimeException继承自java.lang.Exception)。
- Error
当程序发生不可控的错误时,通常做法是通知用户并中止程序的执行。与异常不同的是Error及其子类的对象不应被抛出。
Error是throwable的子类,代表编译时间和系统错误,用于指示合理的应用程序不应该试图捕获的严重问题。
Error由Java虚拟机生成并抛出,包括动态链接失败,虚拟机错误等。程序对其不做处理。
回滚策略
Spring的事务管理默认只对出现unchecked excpetion (java.lang.RuntimeException及其子类)进行回滚。
如果一个方法抛出Exception或者Checked异常,Spring事务管理默认不进行回滚
查看代码 testTransactionAddThrowError() 以及 testThrowException()
配置回滚策略
- rollbackFor
- noRollbackFor
提问,如果是子类,是否会回滚? 查看 testNoRollback() 和 testRollback()
超时
在达到超时时间之后,事务自动回滚. — 这个貌似有问题。稍后补充
看代码testTimeout()。 这个代码不会回滚
@Transactional(timeout = 3, propagation = Propagation.REQUIRED)
public void insertTimeout(User record) {
userMapper.insert(record);
try {
Thread.sleep(4000);
} catch (Exception e) {
e.printStackTrace();
}
userMapper.insert(record);
}
只读
数据库可能可以针对这个只读的事务做优化。
在只读业务里,更新操作会有异常.
事务的隔离性
多数据源+ 事务
代码 testInsertTwoDb(), 和 testInsertTwoUserReqireNew()
atomikos
http://iteye.blog.163.com/blog/static/1863080962012102945116222/
结论 && Future works
- 在spring里,事务比较强大。
- 还需要研究 事务的隔离性 、 timeout的具体用法
- 编程式事务也需要进一步研究
- 多数据源情况下,事务的使用
随意记录
- 如果想要使用@Transaction标注,必须在ApplicationContext里加上
<!-- 事务管理 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
- 输出JDBC相关操作的日志配置
<logger name="org.springframework" level="WARN" > </logger> <logger name="org.springframework.jdbc" level="DEBUG"/> <logger name="org.springframework.transaction" level="DEBUG"/> <logger name="java.sql" level="DEBUG" />
输出的日志文件类似于
DEBUG java.sql.Connection 2014-03-29 23:32:19 - ooo Connection Opened
DEBUG java.sql.PreparedStatement 2014-03-29 23:32:19 - ==> Executing: INSERT INTO testUser (id,userName,phone) VALUES (?,?,?)
DEBUG java.sql.PreparedStatement 2014-03-29 23:32:19 - ==> Parameters: 8983(Integer), null, 2123(String)
DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-03-29 23:32:29 - Initiating transaction commit
DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-0DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager 2014-0
附录
- http://blog.csdn.net/roroyangivan/article/details/7083385
- spring事务控制 http://www.cnblogs.com/ysxlin/archive/2008/06/06/1215300.html
- 异常的分类 http://woshixy.blog.51cto.com/5637578/1071966
- spring分布式事务 http://blog.chinaunix.net/uid-21162795-id-3424973.html 和http://iteye.blog.163.com/blog/static/1863080962012102945116222/