服務(wù)近2000家企業(yè),依托一系列實(shí)踐中打磨過的技術(shù)和產(chǎn)品,根據(jù)企業(yè)的具體業(yè)務(wù)問題和需求,針對(duì)性的提供各行業(yè)大數(shù)據(jù)解決方案。
mybatis多數(shù)據(jù)源切換實(shí)現(xiàn)多用戶SAAS軟件
來源:未知 時(shí)間:2021-48-16 瀏覽次數(shù):507次
mybatis多數(shù)據(jù)源切換實(shí)現(xiàn)多用戶SAAS軟件,使用多數(shù)據(jù)源的場景應(yīng)該是很多的,如操作同一臺(tái)服務(wù)器上不同的數(shù)據(jù)庫,或者多地機(jī)器上的相同或不相同數(shù)據(jù)庫。
1.mybatis切換數(shù)據(jù)源
1.mybatis切換數(shù)據(jù)源
[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">
- <!-- 定時(shí)器開關(guān) 開始 -->
- <task:annotation-driven />
- <tx:annotation-driven />
- <!-- 標(biāo)注類型 的事務(wù)配置 如果使用注解事務(wù)。就放開 <tx:annotation-driven /> -->
- <!-- 統(tǒng)一異常處理方式 -->
- <!-- <bean id="exceptionHandler" class="com.framework.exception.MyExceptionHandler"/> -->
- <!-- 初始化數(shù)據(jù) -->
- <!-- <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 自定義注冊(cè) -->
- <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" />
- <!-- 自動(dòng)匹配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接口的接口, 這樣一個(gè)bean就可以了 -->
- <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
- <property name="basePackage" value="com.framework.mapper" />
- </bean>
- <!-- 事務(wù)配置 -->
- <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組件掃描的方式來實(shí)現(xiàn)自動(dòng)注入bean -->
- <!-- <context:component-scan base-package="com.framework.task" /> -->
- <!-- 隱式地向 Spring 容器注冊(cè) -->
- <!-- <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類實(shí)現(xiàn)數(shù)據(jù)源動(dòng)態(tài)加載
[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創(chuàng)建多線程實(shí)現(xiàn)對(duì)變量的保存
[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();
- }
- }
只要每次調(diào)用setCustomerType方法(集合中存在)就可以實(shí)現(xiàn)數(shù)據(jù)源的動(dòng)態(tài)切換
如何動(dòng)態(tài)增長數(shù)據(jù)源呢,map,就是突破口。
利用spring,得到對(duì)象
[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
不斷攔截用戶的請(qǐng)求,不斷更換。
[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;
- }
真正實(shí)現(xiàn)自動(dòng)切換的地方。。
現(xiàn)在,記錄相關(guān)的dynamicdatasource與spring 的相關(guān)特性,
每一次,進(jìn)入spring,都會(huì)自動(dòng)執(zhí)行一邊這個(gè)方法。
還發(fā)現(xiàn),就算又是明明切換,但是我的框架,切實(shí)數(shù)據(jù)源切換失敗的,著實(shí)有點(diǎn)蛋疼。。
既然這樣,我就改寫底層框架,使他變成,適應(yīng)多數(shù)據(jù)源的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;
- }
都是在這里執(zhí)行connection的
進(jìn)去看看,怎么得到的
[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沒有關(guān)系,只跟transaction有關(guān)系。
[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;
- }