跨库事务管理器
本文介绍如何编写一个跨库的事务管理器,通过best effort 1pc模式在spring DataSourceTransactionManager的基础上改造出一个能够支持多数据源事务的事务管理器。
一、多数据源配置+事务管理器配置
<?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: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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.handserome.test.dao"/>
<!--直接配置数据库连接1-->
<bean id="testDbDs1" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="5" />
<property name="minIdle" value="10" />
<property name="maxActive" value="20" />
</bean>
<!--直接配置数据库连接2-->
<bean id="testDbDs2" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test1"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="5" />
<property name="minIdle" value="10" />
<property name="maxActive" value="20" />
</bean>
<bean id="shardRegister"
class="com.handserome.daat.datasource.DefaultDbShardRegister">
<property name="dbShardInfos">
<set>
<!-- 测试库1 -->
<bean class="com.handserome.daat.datasource.DbShardInfo">
<property name="id" value="db1"/>
<property name="dataSource" ref="testDbDs1"/>
<property name="description" value="测试1库数据源"/>
</bean>
<!-- 测试库1 -->
<bean class="com.handserome.daat.datasource.DbShardInfo">
<property name="id" value="db2"/>
<property name="dataSource" ref="testDbDs2"/>
<property name="description" value="测试2库数据源"/>
</bean>
</set>
</property>
</bean>
<!-- DAL客户端接口实现 -->
<bean id="sqlSession"
class="com.handserome.daat.session.DbShardSqlSession">
<property name="dbShardRegister" ref="shardRegister"/>
<property name="sqlMapConfigLocation" value="classpath*:conf/sqlMap/sqlMap_*.xml"/>
</bean>
<!-- 事务配置 -->
<bean id="transactionManager"
class="com.handserome.daat.transaction.MultiDataSourceTransactionManager">
<property name="dbShardRegister" ref="shardRegister"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="studentDao1" class="com.handserome.test.dao.StudentDaoImpl1">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
二、多数据源事务管理器
/**
* 功能描述:多数据源事务管理器<br>
* 采用Best Effort 1PC Pattern的事务策略
*
* @author handersome
*/
public class MultiDataSourceTransactionManager implements PlatformTransactionManager, InitializingBean {
private final Logger logger = LoggerFactory.getLogger(MultiDataSourceTransactionManager.class);
/**
* 各个数据源对应的事务管理器列表
*/
private List<DataSourceTransactionManager> transactionManagers;
/**
* 分库注册配置信息
*/
private DbShardRegister dbShardRegister;
public DbShardRegister getDbShardRegister() {
return dbShardRegister;
}
public void setDbShardRegister(DbShardRegister dbShardRegister) {
this.dbShardRegister = dbShardRegister;
}
/**
*
* 功能描述: 获取事务 每个数据源对应的事务管理器从数据源中拿到connection,并且关闭了connection的自动提交
*
* @param definition
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
@Override
public MultiTransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
// 所有数据源事务管理里共用一个MultiTransactionStatus,因此取第0个即可
MultiTransactionStatus mts = new MultiTransactionStatus(transactionManagers.get(0));
// 判断当前线程的事务同步器是否存在
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
// 创建当前线程的事务同步器
TransactionSynchronizationManager.initSynchronization();
mts.setNewSynchronization();
}
try {
// 遍历所有数据源的事务管理器
for (DataSourceTransactionManager transactionManager : transactionManagers) {
System.out.println(transactionManager.toString());
// 将各数据源事务管理器注册到多事务状态中
mts.registerTransactionManager(definition, transactionManager);
}
} catch (RuntimeException ex) {
// key 具体数据源对应的事务管理器,value 该事务管理器的事务
Map<DataSourceTransactionManager, TransactionStatus> transactionStatuses = mts.getTransactionStatuses();
// 对各个具体的数据源事务管理器进行回滚
for (DataSourceTransactionManager transactionManager : transactionManagers) {
try {
if (transactionStatuses.get(transactionManager) != null) {
// 获取当前事务管理器对应的具体事务
transactionManager.rollback(transactionStatuses.get(transactionManager));
}
} catch (Exception ex2) {
logger.warn("Rollback exception (" + transactionManager + ") " + ex2.getMessage(), ex2);
}
}
// 发生异常清空当前线程的事务同步器
if (mts.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
throw new CannotCreateTransactionException(ex.getMessage(), ex);
}
return mts;
}
/**
* 功能描述:提交操作<br>
*/
@Override
public void commit(TransactionStatus status) throws TransactionException {
MultiTransactionStatus multiTransactionStatus = (MultiTransactionStatus) status;
boolean commit = true;
Exception commitException = null;
PlatformTransactionManager commitExceptionTransactionManager = null;
// 反转事务管理器列表
Iterable<DataSourceTransactionManager> reverseManagerList = reverse(transactionManagers);
// 遍历所有数据源的事务管理器
for (DataSourceTransactionManager transactionManager : reverseManagerList) {
// 获取数据源对应的事务
TransactionStatus transactionStatus = multiTransactionStatus.getTransactionStatus(transactionManager);
// 如果存在某个数据源提交失败,后续不再提交
if (commit) {
try {
transactionManager.commit(transactionStatus);
} catch (Exception ex) {
commit = false;
commitException = ex;
// 发生异常的事务管理器
commitExceptionTransactionManager = transactionManager;
}
} else {
// 后面的事务回滚
try {
transactionManager.rollback(transactionStatus);
} catch (Exception ex) {
logger.warn("Rollback exception (after commit) (" + transactionManager + ") " + ex.getMessage(),
ex);
}
}
}
if (multiTransactionStatus.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
if (commitException != null) {
// 判断是否是第一个事务管理器提交就失败
boolean firstTransactionManagerFailed = commitExceptionTransactionManager == getLastTransactionManager();
int transactionState = firstTransactionManagerFailed ? HeuristicCompletionException.STATE_ROLLED_BACK
: HeuristicCompletionException.STATE_MIXED;
throw new HeuristicCompletionException(transactionState, commitException);
}
}
/**
* 功能描述:回滚操作<br>
*/
@Override
public void rollback(TransactionStatus status) throws TransactionException {
Exception rollbackException = null;
DataSourceTransactionManager rollbackExceptionTransactionManager = null;
MultiTransactionStatus multiTransactionStatus = (MultiTransactionStatus) status;
for (DataSourceTransactionManager transactionManager : reverse(transactionManagers)) {
TransactionStatus transactionStatus = multiTransactionStatus.getTransactionStatus(transactionManager);
try {
transactionManager.rollback(transactionStatus);
} catch (Exception ex) {
if (rollbackException == null) {
rollbackException = ex;
rollbackExceptionTransactionManager = transactionManager;
} else {
logger.warn("Rollback exception (" + transactionManager + ") " + ex.getMessage(), ex);
}
}
}
if (multiTransactionStatus.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
if (rollbackException != null) {
throw new UnexpectedRollbackException("Rollback exception, originated at ("
+ rollbackExceptionTransactionManager + ") " + rollbackException.getMessage(), rollbackException);
}
}
@Override
public void afterPropertiesSet() {
Assert.notNull(dbShardRegister, "DbShardRegister must not be null!");
transactionManagers = new ArrayList<DataSourceTransactionManager>();
// 遍历所有的数据源,给每个数据源创建一个事务管理器,并加入到事务管理器集合中
for (DbShardInfo dbShardInfo : dbShardRegister.getDbShardInfos()) {
DataSourceTransactionManager txManager = new DataSourceTransactionManager(dbShardInfo.getDataSource());
transactionManagers.add(txManager);
}
}
private <T> Iterable<T> reverse(Collection<T> collection) {
List<T> list = new ArrayList<T>(collection);
Collections.reverse(list);
return list;
}
private PlatformTransactionManager getLastTransactionManager() {
return transactionManagers.get(lastTransactionManagerIndex());
}
private int lastTransactionManagerIndex() {
return transactionManagers.size() - 1;
}
}
三、多事务状态
/**
* 功能描述:多事务状态
*
* @author handersome
*/
class MultiTransactionStatus implements TransactionStatus {
/**
* 主事务管理器
*/
private final DataSourceTransactionManager mainTransactionManager;
/**
* key 数据源对应的事务管理器, value:具体事务
*/
private final Map<DataSourceTransactionManager, TransactionStatus> transactionStatuses = new ConcurrentHashMap<DataSourceTransactionManager, TransactionStatus>();
/**
* 当前线程是否存在
*/
private boolean newSynchronization;
public MultiTransactionStatus(DataSourceTransactionManager mainTransactionManager) {
Assert.notNull(mainTransactionManager, "TransactionManager must not be null!");
this.mainTransactionManager = mainTransactionManager;
}
public void setNewSynchronization() {
this.newSynchronization = true;
}
public boolean isNewSynchronization() {
return newSynchronization;
}
public void registerTransactionManager(TransactionDefinition definition,
DataSourceTransactionManager transactionManager) {
// 获取数据源对应的事务状态
TransactionStatus transaction = transactionManager.getTransaction(definition);
transactionStatuses.put(transactionManager, transaction);
}
/**
*
* 功能描述: 获取事务管理器的当前事务
*
* @param transactionManager
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
public TransactionStatus getTransactionStatus(DataSourceTransactionManager transactionManager) {
return this.transactionStatuses.get(transactionManager);
}
@Override
public boolean isRollbackOnly() {
return getMainTransactionStatus().isRollbackOnly();
}
@Override
public boolean isCompleted() {
return getMainTransactionStatus().isCompleted();
}
@Override
public boolean isNewTransaction() {
return getMainTransactionStatus().isNewTransaction();
}
@Override
public boolean hasSavepoint() {
return getMainTransactionStatus().hasSavepoint();
}
@Override
public void setRollbackOnly() {
for (TransactionStatus ts : transactionStatuses.values()) {
ts.setRollbackOnly();
}
}
@Override
public Object createSavepoint() throws TransactionException {
SavePoints savePoints = new SavePoints();
for (TransactionStatus transactionStatus : transactionStatuses.values()) {
savePoints.save(transactionStatus);
}
return savePoints;
}
@Override
public void rollbackToSavepoint(Object savepoint) throws TransactionException {
SavePoints savePoints = (SavePoints) savepoint;
savePoints.rollback();
}
@Override
public void releaseSavepoint(Object savepoint) throws TransactionException {
((SavePoints) savepoint).release();
}
@Override
public void flush() {
for (TransactionStatus transactionStatus : transactionStatuses.values()) {
transactionStatus.flush();
}
}
private TransactionStatus getMainTransactionStatus() {
return transactionStatuses.get(mainTransactionManager);
}
public Map<DataSourceTransactionManager, TransactionStatus> getTransactionStatuses() {
return transactionStatuses;
}
/**
* 功能描述:保存点
*
* @author
*/
private static class SavePoints {
private final Map<TransactionStatus, Object> savePoints = new HashMap<TransactionStatus, Object>();
private void addSavePoint(TransactionStatus status, Object savepoint) {
Assert.notNull(status, "TransactionStatus must not be null!");
this.savePoints.put(status, savepoint);
}
private void save(TransactionStatus transactionStatus) {
Object savepoint = transactionStatus.createSavepoint();
addSavePoint(transactionStatus, savepoint);
}
private void rollback() {
for (TransactionStatus transactionStatus : savePoints.keySet()) {
transactionStatus.rollbackToSavepoint(savepointFor(transactionStatus));
}
}
private Object savepointFor(TransactionStatus transactionStatus) {
return savePoints.get(transactionStatus);
}
private void release() {
for (TransactionStatus transactionStatus : savePoints.keySet()) {
transactionStatus.releaseSavepoint(savepointFor(transactionStatus));
}
}
}
}
四、dao层实例
/**
* @author : 18073771
* @see [相关类/方法](可选)
* @since [产品/模块版本] (可选)
*/
public class StudentDaoImpl implements StudentDao {
private SqlSession sqlSession;
@Transactional
@Override
public void insertTransactionTest() {
insert();
insert1();
}
private void insert() {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("studentId", "222");
paramMap.put("name", "bbb");
paramMap.put("dbKey", "db1");
String insert = "student.saveData";
int execute = sqlSession.execute(insert, paramMap);
System.out.println("插入条数" + execute);
}
private void insert1() {
List<Object> objectList = new ArrayList<Object>();
Student student = new Student();
student.setStudentId("7878");
student.setName("7878");
student.setCreateTime(new Timestamp(new java.util.Date().getTime()));
student.setDbKey("db2");
objectList.add(student);
Student student1 = new Student();
student1.setStudentId("78781");
student1.setName("78781");
student1.setDbKey("db2");
student1.setCreateTime(new Timestamp(new java.util.Date().getTime()));
objectList.add(student1);
String insert = "student.saveData";
int i = sqlSession.batchUpdate(insert, objectList);
System.out.println("插入条数" + i);
}
public SqlSession getSqlSession() {
return sqlSession;
}
public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
}
五、测试方法
public class JdbcTemplateTest {
public static void main(String[] args) throws InterruptedException {
// 默认,非分库
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spes-da-shard.xml");
StudentDao studentDao = (StudentDao) context.getBean("studentDao1");
studentDao.insertTransactionTest();
multiThreadTest(context);
}
private static void multiThreadTest(ClassPathXmlApplicationContext context) throws InterruptedException {
final StudentDao studentDao = (StudentDao) context.getBean("studentDao1");
studentDao.insertTransactionTest();
for (int i = 0; i < 10000; i++) {
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
studentDao.insertTransactionTest();
}
});
thread.start();
}
}
}