网上有很多使用注解 + AOP进行数据源切换的代码,他们统一通过继承spring提供的AbstractRoutingDataSource去重写determineCurrentLookupKey去获得当前的数据源。
我通过查阅大量的资料发现网上基本主流的也是这种做法,但这种做法有一个致命的缺陷,那就是在事务内无法进行数据源切换。
下图展示了传统方式存在的问题:
我的解决思路是:
附上核心一段的伪代码,相信有一点技术功底的都可以写出来,如果大家有需要,可以留言,我给大家准备一下。
private class ConnectionProxy implements InvocationHandler {
private String userName;
private String password;
private boolean usePwd;
private boolean autoCommit = true;
/**
* cache connection
*/
private Map<String,Connection> cachedConnectionMap = new HashMap<>(8);
private ConnectionProxy(){}
private ConnectionProxy(String userName,String password){
this.userName = userName;
this.password = password;
this.usePwd = true;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if("close".equals(methodName)){
close();
}else if("commit".equals(methodName)){
commit();
}else if("rollback".equals(methodName)){
rollback();
} else if("setAutoCommit".equals(methodName)){
setAutoCommit((Boolean) args[0]);
}else{
// 获取当前上下文的数据源id
String dsId = DynamicDataSourceContextHolder.peek();
// 根据数据源id获取connection
Connection connection = cachedConnectionMap.get(dsId);
if(connection==null){
// 没有connection,手动创建一个,如果有说明当前在事务内,在之前已经被创建过了,直接复用以前的。
DataSource dataSource = determineDataSource();
connection = usePwd ? dataSource.getConnection(userName,password) : dataSource.getConnection();
if(!autoCommit){
connection.setAutoCommit(false);
}
cachedConnectionMap.put(dsId,connection);
}
return method.invoke(connection,args);
}
}
private void setAutoCommit(final boolean autoCommit) throws SQLException {
this.autoCommit = autoCommit;
for (Connection connection : cachedConnectionMap.values()) {
connection.setAutoCommit(autoCommit);
}
}
private void commit() throws SQLException {
for (Connection connection : cachedConnectionMap.values()) {
connection.commit();
}
}
private void rollback() throws SQLException {
for (Connection connection : cachedConnectionMap.values()) {
connection.rollback();
}
}
private void close() throws SQLException {
for (Connection connection : cachedConnectionMap.values()) {
connection.close();
}
}
}