最近公司做一个saas模式的项目。在多数据源问题上卡了几天。有两个方案,这是1.0。
你们不知道我多辛苦,翻遍了中国网站,翻遍了外国网站。
前者不全面,不严谨,后者看不懂。
我们先从,多数据源切换开始。
- 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" xmlns:task="http://www.springframework.org/schema/task"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-4.1.xsd
- http://www.springframework.org/schema/tx
- http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
- http://www.springframework.org/schema/task
- http://www.springframework.org/schema/task/spring-task-4.1.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"
- default-lazy-init="false">
-
-
- <task:annotation-driven />
- <tx:annotation-driven />
-
-
-
-
-
- <bean id="springfactory" class="com.framework.util.SpringFactory">bean>
-
- <bean id="pagePlugin" class="com.framework.plugin.PagePlugin">
- <property name="properties">
- <props>
- <prop key="dialect">mysqlprop>
- <prop key="pageSqlId">.*query.*prop>
- props>
- property>
- bean>
- <bean id="sqlSessionFactoryBean" class="com.framework.plugin.test.MySqlFatoryBean">
- <property name="dataSource" ref="dynamicDataSource" />
-
- <property name="mapperLocations" value="classpath:mappings/*-mapper.xml" />
-
- <property name="plugins">
- <array>
- <ref bean="pagePlugin" />
- array>
- property>
- bean>
-
- <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
- <property name="basePackage" value="com.framework.mapper" />
- bean>
-
- <bean id="transactionManager"
- class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dynamicDataSource" />
- bean>
-
-
-
-
- beans>
- package junit.test;
-
-
- import java.sql.SQLException;
- import java.util.Properties;
-
- import org.springframework.jdbc.datasource.DriverManagerDataSource;
-
- import com.framework.util.JDBCConnectionUtil;
-
- public class JDBCTest extends DriverManagerDataSource{
-
-
- public void changeJDBC(String url,String username,String password) {
- setUrl(url);
- setUsername(username);
- setPassword(password);
- }
-
- public void changeJDBC(Properties properties) throws Exception {
- setConnectionProperties(properties);
- setUrl(properties.getProperty("jdbc.url"));
- setUsername(properties.getProperty("jdbc.username"));
- setPassword(properties.getProperty("jdbc.password"));
- }
- }
上面这个类,我只是贪图方便然后就继承了,来使用。
- package com.framework.plugin.test;
-
- import org.mybatis.spring.SqlSessionFactoryBean;
- import org.mybatis.spring.SqlSessionTemplate;
-
- public class MySqlFatoryBean extends SqlSessionFactoryBean {
-
- public void test() {
- }
- }
同理。
这些都不重要。
重要的是对于 ThreadLocal contextHolder,这就是一个线程,对于当前变量的保存,克隆。各个线程之间互不影响。
- package com.framework.plugin.test;
-
- public class ContextHolder {
- public static final String DATA_SOURCE_A = "dataSource";
- public static final String DATA_SOURCE_B = "dataSource2";
- private static final ThreadLocal contextHolder = new ThreadLocal();
- public static void setCustomerType(String customerType) {
- contextHolder.set(customerType);
- System.out.println(" contextHolder.set(customerType); ==="+customerType);
- }
- public static String getCustomerType() {
- return contextHolder.get();
- }
- public static void clearCustomerType() {
- contextHolder.remove();
- }
- }
只要每次调用setCustomerType方法,而且前提是,你的那个数据源的集合里面有,才行。
那么切换数据源就成功了。
如何动态增长数据源呢,map,就是突破口。
利用spring,得到对象
- JDBCTest jdbcTest = new JDBCTest();
- Properties properties = MapToProperties.map2properties(datacenterFormMap);
- String username = datacenterFormMap.getStr("username");
- jdbcTest.changeJDBC(properties);
- DynamicDataSource dataSource = (DynamicDataSource) SpringFactory.getObject("dynamicDataSource");
- HashMap hashMap = (HashMap) ReflectHelper.getValueByFieldName(dataSource,
- "resolvedDataSources");
- hashMap.put(username, jdbcTest);
- System.out.println(ReflectHelper.getFieldByFieldName(dataSource, "targetDataSources"));
- ContextHolder.setCustomerType(username);
详细就不给出了,抛砖引玉。
3.如何满足saas多租户模式的需求。
通过拦截器interceptor。SaasInterceptor
不断拦截用户的请求,不断更换。
这个方案很低级,不高效。
sqlsession,也还是单例的,没有saas的思想。就是一个伪saas。
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
真正实现自动切换的地方。。
现在,记录相关的dynamicdatasource与spring 的相关特性,
每一次,进入spring,都会自动执行一边这个方法。
还发现,就算又是明明切换,但是我的框架,切实数据源切换失败的,着实有点蛋疼。。
既然这样,我就改写底层框架,使他变成,适应多数据源的spring。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
handler.parameterize(stmt);
return stmt;
}
都是在这里执行connection的
进去看看,怎么得到的
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
我的乖乖,越来越复杂,,
/*
* Creates a logging version of a connection
*
* @param conn - the original connection
* @return - the connection with logging
*/
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
ClassLoader cl = Connection.class.getClassLoader();
return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
}
最后总结,connection,跟statementlog没有半毛钱关系,只跟transaction有关系。
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
翻了整个类,transaction,只是初始的时候,完成了初始化。。
transaction又是一个接口来的,必然有实现类,不翻了,猜想应该就是xml里配置的transactionManager,有关。
其实就是线索断了。。
那么实现就是,要么硬来,可以实现,我测试了。代码,很简单。
就是改变原有的connection,变成自定义。
这样的解决办法,相当暴力,那么耦合性就会很强。
其实还有下一篇的,等我BY了,我就写出来了。
QQ交流群315309006