背景:
1: 接手的系统中,数据库操作部分如下,下文称为ECon的方式:
ECon con = null;
try {
con = ConMan.get("order");
...//do something with con
} catch (SQLException e) {
if (con!=null) {
try {
con.rollback();
} catch (Exception ex) {}
}
} finally {
Closer.close(con);
}
为了保证事务,只能是在不同的方法中将con传来传去。
2:由于1中的方式极其不方便。团队倾向于使用spring来处理,所以很多方法就是用spring来写
问题
由于代码不断的在修改,就会产生ECon调用Spring写得代码、Spring调用ECon的旧代码等等,如何保证这些代码的事务一致性?
问题分析与解决
事务一致性要解决的问题
1:数据库连接Connection的一致性。在同一个request线程中,无论何时获取连接,都保证是同一个。
解决方案:必然使用ThreadLocal来存储。要么改写ConMan.get("order") 来适配Spring,要么改写Spring来适配ConMan.get("order"). 目前倾向于Spring,因为Spring中有DataSourceUtils来取得当前事务的连接。
暂不下结论。继续后面的分析。
2:由于ECon的代码中会catch exception回滚和 finally 关闭连接。 因为要和Spring整合在一起,互相嵌套调用,为了保证事务一致性,我们要保证内层的rollback和close没有真实的rollback和close,应该仅仅是标记。
只有最外层的回滚或关闭,才是真实的操作。
解决方案:这个特性和Spring的事务管理非常的像。果断翻阅Spring事务管理的源代码。
最后得到Spring的解决方案:
public void doLogic1(){
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic here
doLogic2();
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
}
public void doLogic2(){
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic2 here
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
}
这个例子就是 doLogic1调用doLogic2,两个方法都显式调用rollback 和 commit。 那么他们是如何保证事务一致性的呢。
最后发现基本逻辑如下:
第一层事务开始(如doLogic1())
TransactionStatus.isNewTransaction 返回 true
第二层事务开始(如doLogic2)
TransactionStatus.isNewTransaction 返回 false
、 第二层事务结束:
if(TransactionStatus.isNewTransaction()){ doLogic2返回是false,所以不会真正的提交
status.commit()//真实的提交
}
第一层事务结束
if(TransactionStatus.isNewTransaction()){ doLogic1返回的true,所有会真正的提交
status.commit()//真实的提交
}
同理rollback 和 close,Spring会判断当前的TransactionStatus 是否是新起的事务,如果是则真实操作,如果不是则在事务管理器中标记一下,到最外层的新新起事务中进行判断操作。
Spring的事务管理方式完全满足需求。至此,问题的解决方案全部搞定。
编码:
1:修改EConMan:
SpringEconManager.java :
public static ECon get(String dsname, TransactionDefinition definition) {
DataSourceTransactionManager transactionManager = getTransactionManager(dsname);
TransactionStatus status = transactionManager.getTransaction(definition);
DataSource dataSource = transactionManager.getDataSource();
ECon eCon = (ECon) DataSourceUtils.getConnection(dataSource);
return SpringEcon.newSpringEcon(dsname, transactionManager, status, definition, dataSource, eCon);
}
/**
* 用 SpringEconManager.close(econ), 替换 Closer.close(econ)
*
* @param con
*/
public static void close(ECon con) {
if(con == null){
return;
}
if (con instanceof SpringEcon) {
SpringEcon wrapperEcon = (SpringEcon) con;
if (!wrapperEcon.getStatus().isCompleted()) {
if (wrapperEcon.getStatus().isRollbackOnly()) {
rollback(con);
} else {
commit(con);
}
}
DataSourceUtils.releaseConnection(wrapperEcon.getEcon(), wrapperEcon.getDatasource());
} else {
Closer.close(con);
}
}
/**
* 用 SpringEconManager.rollback(econ), 替换econ.rollback()
*
* @param con
*/
public static void rollback(ECon con) {
if(con == null){
return;
}
if (con instanceof SpringEcon) {
SpringEcon wrapperEcon = (SpringEcon) con;
TransactionStatus status = wrapperEcon.getStatus();
wrapperEcon.getTransactionManager().rollback(status);
} else {
try {
con.rollback();
} catch (SQLException e) {
Throwables.propagate(e);
}
}
}
/**
* 用 SpringEconManager.commit(econ), 替换econ.commit()
*
* @param con
*/
public static void commit(ECon con) {
if(con == null){
return;
}
if (con instanceof SpringEcon) {
SpringEcon wrapperEcon = (SpringEcon) con;
TransactionStatus status = wrapperEcon.getStatus();
if (!status.isRollbackOnly()) {
wrapperEcon.getTransactionManager().commit(status);
}
} else {
try {
con.commit();
} catch (SQLException e) {
Throwables.propagate(e);
}
}
}
2:使用SpringEcon继承Econ,覆盖相关方法:
SpringEcon.java :
@Override
public void commit() throws SQLException {
SpringEconManager.commit(this);
}
@Override
public void rollback() throws SQLException {
SpringEconManager.rollback(this);
}
@Override
public void close() {
SpringEconManager.close(this);
}
3:旧代码清理:
只需将出现
con = ConMan.get("order");
改为
con = SpringEconManager.get("order");
事务测试:
仅展示表明思路的代码:
1: Spring 调用ECon
@Transactional
public void testRollback(String name) {
insert1(name);
jdbcinsert2(name);
throw new RuntimeException();
}
@Transactional
public void testRollback1(String name) {
// rollback and catch excpetion
insert1WithRollback(name);
jdbcinsert2(name);
}
@Transactional
public void testRollback2(String name) {
jdbcinsert2(name);
// rollback and catch excpetion
insert1WithRollback(name);
}
@Transactional
public void testRollback3(String name) {
jdbcinsert2(name);
// rollback and catch excpetion
insert1WithRollback(name);
SpringEconManager.commit(SpringEconManager.get("order"));
}
@Transactional
public void testCommit(String name) {
insert1(name);
jdbcinsert2(name);
}
2: Econ调用Spring
@Test
public void testRollback() {
ECon con = SpringEconManager.get("order");
String name = UUID.randomUUID().toString();
try {
insert1(name);
jdbcinsert2(name);
SpringEconManager.rollback(con);
} finally {
SpringEconManager.close(con);
}
Assert.assertTrue(CollectionUtils.isEmpty(query1(name)));
Assert.assertTrue(CollectionUtils.isEmpty(query2(name)));
}
@Test
public void testRollback1() {
ECon con = SpringEconManager.get("order");
String name = UUID.randomUUID().toString();
try {
// rollback and catch excpetion
insert1WithRollback(name);
jdbcinsert2(name);
} finally {
SpringEconManager.close(con);
}
Assert.assertTrue(CollectionUtils.isEmpty(query1(name)));
Assert.assertTrue(CollectionUtils.isEmpty(query2(name)));
}
@Test
public void testRollback2() {
ECon con = SpringEconManager.get("order");
String name = UUID.randomUUID().toString();
try {
jdbcinsert2(name);
// rollback and catch excpetion
insert1WithRollback(name);
} finally {
SpringEconManager.close(con);
}
Assert.assertTrue(CollectionUtils.isEmpty(query1(name)));
Assert.assertTrue(CollectionUtils.isEmpty(query2(name)));
}
@Test
public void testRollback3() {
ECon con = SpringEconManager.get("order");
String name = UUID.randomUUID().toString();
try {
jdbcinsert2(name);
// rollback and catch excpetion
insert1WithRollback(name);
SpringEconManager.commit(con);
} finally {
SpringEconManager.close(con);
}
Assert.assertTrue(CollectionUtils.isEmpty(query1(name)));
Assert.assertTrue(CollectionUtils.isEmpty(query2(name)));
}