最近项目中需要用到多数据源管,数据访问层采用的是JDBCTemplate去做的,一开始是在数据源这块做了一个多数据源的操作类,通过拦截器注解去动态赋值指定的数据源操作,这种做法在查询中是没有问题的,但是DML操作时,会出现问题:事物中无法动态操作数据源,导致很多操作指针对第一个库。查询资料的时候发现:
DataSourceTransactionManager这个事物管理类只针对单个数据源进行事物控制.
解决的方案也有多种,这里只提及我实践过的两种:
- 开启多个数据源去进行操作,这种是可以自己去实现的.
- 利用JTA和Atomikos的多数据源分布事物管理
方案一:
思路:
- 自定义一个注解类,通过传递参数告诉这个业务需要用到哪几个数据源
- 然后仿照Spring中的@Transactional的实现模式,去构建一个集合来开启多个事物
- 然后通过拦截器去动态分配业务给这个集合告诉他,要开几个事物
代码:
applicationContext-datasource.xml
applicationContext-service.xml 业务层配置文件
多数据Aop配置
注解类 :
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MultiTransactional {
String[] values() default "";
// TODO 当然你如果想做的更完善一点,可以参考@Transactional这个类,自己去做,什么传播机制啊,隔离级别啊 等等,思路是一样的
}
注解实现类 - 其实这里差不多都是沿用Spring的事物实现方式:
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.lang.reflect.Method;
import java.util.Stack;
/**
* @Description: 多数据源动态管理
* @Author: Liukx on 2017/7/28 - 16:41
*/
@Component("multiTransactionalAspect")
public class MultiTransactionalAspect {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private SpringUtils springUtils;
/**
* 切入点
*
* @param point
* @return
* @throws Throwable
*/
public Object around(ProceedingJoinPoint point) throws Throwable {
Stack dataSourceTransactionManagerStack = new Stack();
Stack transactionStatuStack = new Stack();
try {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class classz = target.getClass();
Class[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
Method m = classz.getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(MultiTransactional.class)) {
MultiTransactional multiTransactional = m.getAnnotation(MultiTransactional.class);
if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, multiTransactional)) {
return null;
}
Object ret = point.proceed();
commit(dataSourceTransactionManagerStack, transactionStatuStack);
return ret;
}
Object ret = point.proceed();
return ret;
} catch (Exception e) {
rollback(dataSourceTransactionManagerStack, transactionStatuStack);
logger.error(String.format(
"MultiTransactionalAspect, method:%s-%s occors error:", point
.getTarget().getClass().getSimpleName(), point
.getSignature().getName()), e);
throw e;
}
}
/**
* 打开一个事物方法
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
* @param multiTransactional
* @return
*/
private boolean openTransaction(
Stack dataSourceTransactionManagerStack,
Stack transactionStatuStack,
MultiTransactional multiTransactional) {
// 获取注解中要打开的事物类型
String[] transactionMangerNames = multiTransactional.values();
if (ArrayUtils.isEmpty(multiTransactional.values())) {
return false;
}
for (String beanName : transactionMangerNames) {
// 创建一个新的事物管理器,用来管理接下来要用到的事物
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
// 根据注解中获取到的数据标识,从spring容器中去查找对应的数据源
DruidDataSource dataSource = (DruidDataSource) springUtils.getBean(beanName);
//然后交给事物管理器去管理
dataSourceTransactionManager.setDataSource(dataSource);
// 定义一个新的事物定义
DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
// 设置一个默认的事物传播机制,注意的是这里可以拓展注解中没有用到的属性
// defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(defaultTransactionDefinition);
// 将这个事物的定义放入Stack中
/**
* 其中为什么要用Stack来保存TransactionManager和TransactionStatus呢?
* 那是因为Spring的事务处理是按照LIFO/stack behavior的方式进行的。
* 如若顺序有误,则会报错:
*/
transactionStatuStack.push(transactionStatus);
dataSourceTransactionManagerStack
.push(dataSourceTransactionManager);
}
return true;
}
/**
* 提交事物方法实现
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void commit(
Stack dataSourceTransactionManagerStack,
Stack transactionStatuStack) {
while (!dataSourceTransactionManagerStack.isEmpty()) {
dataSourceTransactionManagerStack.pop().commit(
transactionStatuStack.pop());
}
}
/**
* 回滚事物方法实现
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void rollback(
Stack dataSourceTransactionManagerStack,
Stack transactionStatuStack) {
while (!dataSourceTransactionManagerStack.isEmpty()) {
dataSourceTransactionManagerStack.pop().rollback(
transactionStatuStack.pop());
}
}
}
service 1 - 2 :
/**
* 一些实验性Demo接口
*
* @author Liukx
* @create 2017-07-28 10:11
* @email [email protected]
**/
public interface IDemoService {
public void mvpManage() throws Exception;
}
/**
* 一些实验性Demo接口
*
* @author Liukx
* @create 2017-07-28 10:11
* @email [email protected]
**/
public interface IDemoService2 {
public void beidouMange() throws CoreException;
}
service实现类:
/**
* 实现性Demo接口实现
*
* @author Liukx
* @create 2017-07-28 10:12
* @email [email protected]
**/
@Service("demoService")
public class DemoServiceImpl implements IDemoService {
@Autowired
@Qualifier("demoTestDao")
private IDemoTestDao testDao;
@Autowired
private IDemoService2 demoService2;
// @Autowired
// private DataSourceTransactionManager transactionManager;
//TODO 这个注解很关键 - 必须是这个注解才能被拦截器拦截到
@MultiTransactional(values = {DataSource.mvp, DataSource.system})
public void mvpManage() throws CoreException {
System.out.println("=====================开始处理MVP");
testDao.insert();
List
service 2实现
/**
* @author Liukx
* @create 2017-07-28 11:07
* @email [email protected]
**/
@Service("demo2")
public class DemoServiceImpl2 implements IDemoService2 {
@Autowired
@Qualifier("beidouTestDao")
private IDemoTestDao testDao;
@MultiTransactional(values = DataSource.system)
public void beidouMange() throws CoreException {
System.out.println("=====================开始处理北斗");
testDao.insert();
List
dao实现: 这里就只列入实现类
@Repository("beidouTestDao")
public class BeiDouTestDaoImpl extends BaseDao implements IDemoTestDao {
/**
* 我们这块目前是有一些封装的,不过你不用太关注;
* 你只要把对应的数据源指定好就行了
*/
@Autowired
@Qualifier("jdbcTemplate2")
public JdbcTemplate jdbcTemplate;
public int insert() throws CoreException {
LinkedHashMap params = new LinkedHashMap<>();
params.put("name", "某某某" + RandomUtils.randomNum(3));
params.put("created", new Date());
return executeInsert("test.insert", params);
}
@Override
public List
@Repository("demoTestDao")
public class DemoTestDaoImpl extends BaseDao implements IDemoTestDao {
/**
* 我们这块目前是有一些封装的,不过你不用太关注;
* 你只要把对应的数据源指定好就行了
*/
@Autowired
@Qualifier("jdbcTemplate")
public JdbcTemplate jdbcTemplate;
public int insert() throws CoreException {
LinkedHashMap params = new LinkedHashMap<>();
params.put("name", "某某某" + RandomUtils.randomNum(2));
params.put("created", new Date());
return executeInsert("test.insert", params);
}
public List
另外有一个动态获取bean的工具类:
@Component
public class SpringUtils implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public Object getBean(String beanId) {
return applicationContext.getBean(beanId);
}
}
PS : 这个方案会存在一定的问题 -> 就是如果你下一个另一个数据库操作的业务serivce方法中如果定义了@Transactional它将会又开启一个事物去执行...
方案2 : 利用JTA+Atomikios实现事物管理,业务层代码和上面差不多,主要是配置文件这块.
applicationContext-datasource.xml
${default.driverClassName}
${default.url}
${default.password}
${default.username}
0
20
0
60000
${default.validationQuery}
false
false
true
true
1800
true
mergeStat
${system.driverClassName}
${system.url}
${system.password}
${system.username}
0
20
0
60000
${system.validationQuery}
false
false
true
true
1800
true
mergeStat
applicationContext-atomikos.xml
配置事物
true
pom.xml
javax.transaction
jta
1.1
com.atomikos
atomikos-util
4.0.2
com.atomikos
transactions
4.0.2
com.atomikos
transactions-jta
4.0.2
com.atomikos
transactions-jdbc
4.0.2
com.atomikos
transactions-api
4.0.2
cglib
cglib-nodep
3.2.2
业务层代码和方案一的差不多,看了一下大概的思路:
在调用被拦截器匹配的方法时,开启一个新的事物
当执行到DML操作时,会获取他对应的数据源,并且会和当前线程的事物管理器中的数据源进行匹配,如果不存在,则到连接池中获取一个新的连接,并把这个连接放入当前线程的事物管理器中进行管理
-
当所有业务执行完毕,并且没有报错的时候,会执行一个两阶段提交的方式
PREPARE TRANSACTION transaction_id PREPARE TRANSACTION 为当前事务的两阶段提交做准备。 在命令之后,事务就不再和当前会话关联了;它的状态完全保存在磁盘上, 它提交成功有非常高的可能性,即使是在请求提交之前数据库发生了崩溃也如此。这条命令必须在一个用BEGIN显式开始的事务块里面使用。
COMMIT PREPARED transaction_id 提交已进入准备阶段的ID为transaction_id的事务
ROLLBACK PREPARED transaction_id 回滚已进入准备阶段的ID为transaction_id的事务
推荐使用第二种方式,因为它有比较好的连接池以及相对完善的机制,第一种考虑的情况比较少,会出现问题,当然,你如果愿意折腾自己写一套,可以参考一下..
以上为个人学习参考,有什么不足的地方欢迎指正.
参考博客:
Mybatis + JTA http://blog.csdn.net/zmx729618/article/details/54344296
分布式事物提交以及JTA的概念 : http://www.jasongj.com/big_data/two_phase_commit/
JTA一些实现原理:https://www.ibm.com/developerworks/cn/java/j-lo-jta/
两阶段事物提交:http://m635674608.iteye.com/blog/2322853