mybatis多数据源切换实现多用户SAAS软件
来源:未知 时间:2021-48-16 浏览次数:507次
mybatis多数据源切换实现多用户SAAS软件,使用多数据源的场景应该是很多的,如操作同一台服务器上不同的数据库,或者多地机器上的相同或不相同数据库。
1.mybatis切换数据源
1.mybatis切换数据源
[html] view plain copy
- <?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 />
- <!-- 标注类型 的事务配置 如果使用注解事务。就放开 <tx:annotation-driven /> -->
- <!-- 统一异常处理方式 -->
- <!-- <bean id="exceptionHandler" class="com.framework.exception.MyExceptionHandler"/> -->
- <!-- 初始化数据 -->
- <!-- <bean id="SpringIocUtils" class="com.framework.util.SpringIocUtils"
- lazy-init="default" init-method="setBean"/> -->
- <bean
- class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="locations">
- <list>
- <value>classpath:jdbc.properties</value>
- </list>
- </property>
- <property name="ignoreUnresolvablePlaceholders" value="true" />
- </bean>
- <bean id="dynamicDataSource" class="com.framework.plugin.test.DynamicDataSource">
- <property name="targetDataSources">
- <map key-type="java.lang.String">
- <entry value-ref="dataSource" key="dataSource"></entry>
- <entry value-ref="dataSource2" key="dataSource2"></entry>
- </map>
- </property>
- <property name="defaultTargetDataSource" ref="dataSource">
- </property>
- </bean>
- <bean id="dataSource" class="junit.test.JDBCTest">
- <property name="url" value="${jdbc.url}" />
- <property name="username" value="${jdbc.username}" />
- <property name="password" value="${jdbc.password}" />
- <property name="driverClassName" value="${jdbc.driverClass}" />
- </bean>
- <bean id="dataSource2" class="junit.test.JDBCTest">
- <property name="url" value="jdbc:mysql://127.0.0.1/db_shiro" />
- <property name="username" value="root" />
- <property name="password" value="123456" />
- <property name="driverClassName" value="${jdbc.driverClass}" />
- </bean>
- <!-- lpl 自定义注册 -->
- <bean id="springfactory" class="com.framework.util.SpringFactory"></bean>
- <bean id="pagePlugin" class="com.framework.plugin.PagePlugin">
- <property name="properties">
- <props>
- <prop key="dialect">mysql</prop>
- <prop key="pageSqlId">.*query.*</prop>
- </props>
- </property>
- </bean>
- <bean id="sqlSessionFactoryBean" class="com.framework.plugin.test.MySqlFatoryBean">
- <property name="dataSource" ref="dynamicDataSource" />
- <!-- 自动匹配Mapper映射文件 -->
- <property name="mapperLocations" value="classpath:mappings/*-mapper.xml" />
- <!-- <property name="typeAliasesPackage" value="com.framework.entity"/> -->
- <property name="plugins">
- <array>
- <ref bean="pagePlugin" />
- </array>
- </property>
- </bean>
- <!-- 通过扫描的模式,扫描目录在com.framework.mapper目录下,所有的mapper都继承SqlMapper接口的接口, 这样一个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>
- <!-- <aop:config> <aop:pointcut expression="execution(public * com.framework.controller.*(..))"
- id="pointcut" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"
- /> </aop:config> <tx:advice id="txAdvice" transaction-manager="transactionManager">
- <tx:attributes> <tx:method name="query*" propagation="REQUIRED" read-only="true"
- /> <tx:method name="find*" propagation="REQUIRED" read-only="true" /> <tx:method
- name="save*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED"
- /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="modify*"
- propagation="REQUIRED" /> <tx:method name="logicDelById" propagation="REQUIRED"
- /> </tx:attributes> </tx:advice> -->
- <!-- <aop:aspectj-autoproxy proxy-target-class="true"/> <bean id="log4jHandlerAOP"
- class="com.framework.logAop.LogAopAction"></bean> <aop:config proxy-target-class="true">
- <aop:aspect id="logAspect" ref="log4jHandlerAOP"> <aop:pointcut id="logPointCut"
- expression="execution(* org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(..))"
- /> <aop:around method="logAll" pointcut-ref="logPointCut" /> </aop:aspect>
- </aop:config> -->
- <!-- 使用Spring组件扫描的方式来实现自动注入bean -->
- <!-- <context:component-scan base-package="com.framework.task" /> -->
- <!-- 隐式地向 Spring 容器注册 -->
- <!-- <context:annotation-config /> -->
- </beans>
[java] view plain copy
- 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"));
- }
- }
通过继承DriverManagerDataSource类实现数据源动态加载
[java] view plain copy
- package com.framework.plugin.test;
- import org.mybatis.spring.SqlSessionFactoryBean;
- import org.mybatis.spring.SqlSessionTemplate;
- public class MySqlFatoryBean extends SqlSessionFactoryBean {
- public void test() {
- }
- }
ThreadLocal<String> contextHolder创建多线程实现对变量的保存
[java] view plain copy
- 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<String> contextHolder = new ThreadLocal<String>();
- 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,得到对象
[java] view plain copy
- JDBCTest jdbcTest = new JDBCTest();
- Properties properties = MapToProperties.map2properties(datacenterFormMap);
- String username = datacenterFormMap.getStr("username");
- jdbcTest.changeJDBC(properties);
- DynamicDataSource dataSource = (DynamicDataSource) SpringFactory.getObject("dynamicDataSource");
- HashMap<String, JDBCTest> hashMap = (HashMap<String, JDBCTest>) ReflectHelper.getValueByFieldName(dataSource,
- "resolvedDataSources");
- hashMap.put(username, jdbcTest);
- System.out.println(ReflectHelper.getFieldByFieldName(dataSource, "targetDataSources"));
- ContextHolder.setCustomerType(username);
3.如何满足saas多租户模式的需求。
通过拦截器interceptor。SaasInterceptor
不断拦截用户的请求,不断更换。
[java] view plain copy
- /**
- * 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。
[java] view plain copy
- 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的
进去看看,怎么得到的
[java] view plain copy
- protected Connection getConnection(Log statementLog) throws SQLException {
- Connection connection = transaction.getConnection();
- if (statementLog.isDebugEnabled()) {
- return ConnectionLogger.newInstance(connection, statementLog, queryStack);
- } else {
- return connection;
- }
- }
[java] view plain copy
- /*
- * 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有关系。
[java] view plain copy
- protected BaseExecutor(Configuration configuration, Transaction transaction) {
- this.transaction = transaction;
- this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
- this.localCache = new PerpetualCache("LocalCache");
- this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
- this.closed = false;
- this.configuration = configuration;
- this.wrapper = this;
- }
- 上一篇: 云软件(SAAS)的几种实现方式优缺点对比
- 下一篇: QT客户端开发之创建并使用Qt自定义控件