多資料庫,多資料來源,資料庫路由
阿新 • • 發佈:2018-12-14
首先,要明白為什麼要使用多資料庫?
正常情況下,如果,百萬資料在單個mysql其實沒太大影響的,但是如果資料達到了,千萬,億,甚至更多,從資料庫著手我們得對資料庫進行拆分成多個庫,但是多個庫之間的資料操作,這就涉及到了我們的資料來源之間的路由的,但是具體程式碼怎麼實現的呢?這裡我用spring+springmvc+hibernate+maven做成案例,廢話不多說,直接上程式碼。
專案結構圖
資料庫
pom.xml引入的jar包
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lcj.blog</groupId> <artifactId>datasourceDynamic</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.2.6.RELEASE</spring.version> </properties> <dependencies> <!-- springframework 4 dependencies begin --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <!-- springframework 4 dependencies end --> <!-- hibernate 配置 begin --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>3.6.9.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.6.9.Final</version> </dependency> <!-- hibernate 配置 end --> <!-- mysql資料庫的驅動包 start --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <!-- 引入jstl包 --> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl-api</artifactId> <version>1.2</version> </dependency> <!-- 引入servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <!-- 引入資料庫連線池 --> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>commons-pool</groupId> <artifactId>commons-pool</artifactId> <version>1.6</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.6</version> </dependency> </dependencies> </project> !
spring.xml配置,主要的是多資料來源初始化,sessionFactory工廠,事物,切面
<?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:aop="http://www.springframework.org/schema/aop" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:jms="http://www.springframework.org/schema/jms" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:oxm="http://www.springframework.org/schema/oxm" xmlns:p="http://www.springframework.org/schema/p" xmlns:task="http://www.springframework.org/schema/task" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.3.xsd http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-4.3.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-4.3.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd"> <!-- 資料來源,BasicDataSource,commons-dbcp --> <bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/dataSource1?useUnicode=true&characterEncoding=UTF-8" /> <property name="username" value="root" /> <property name="password" value="123456" /> </bean> <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/dataSource2?useUnicode=true&characterEncoding=UTF-8" /> <property name="username" value="root" /> <property name="password" value="123456" /> </bean> <!-- 配置資料來源 --> <bean id="dataSource" class="com.liu.core.datasource.DynamicDatasource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry key="dataSource1" value-ref="dataSource1" /> <entry key="dataSource2" value-ref="dataSource2" /> </map> </property> <property name="defaultTargetDataSource" ref="dataSource1" /> </bean> <!-- Hibernate SesssionFactory --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> <property name="mappingLocations" value="classpath:/com/liu/model/*.hbm.xml" /> </bean> <!-- 事物模板 --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager"> <ref bean="transactionManager" /> </property> </bean> <!-- 配置資料來源切換切面 引數配置 --> <bean id="dataSourceChangeAspect" class="com.liu.core.datasource.DatasourceAspect"> <property name="defaultDatasource" value="dataSource1"></property> <property name="datasourceMap"> <map key-type="java.lang.String"> <!-- 通過不同的key決定用哪個dataSource --> <entry key="com.liu.dao.jdbc.UserDaoImpl" value="dataSource1"></entry> <entry key="com.liu.dao.jdbc.BookDaoImpl" value="dataSource2"></entry> </map> </property> </bean> <!--在執行事務前必須切換好事據源,注意執行順序 order越小執行越早 --> <!-- 切面aop --> <!-- 1、execution(): 表示式主體。 2、第一個*號:表示返回型別,*號表示所有的型別。 3、包名:表示需要攔截的包名,後面的兩個句點表示當前包和當前包的所有子包,com.sample.service.impl包、子孫包下所有類的方法。 4、第二個*號:表示類名,*號表示所有的類。 5、*(..):最後這個星號表示方法名,*號表示所有的方法,後面括弧裡面表示方法的引數,兩個句點表示任何引數。 --> <aop:config> <aop:aspect id="datasourceAspect" ref="dataSourceChangeAspect" order="0"> <aop:pointcut id="daoAop" expression="execution(* com.liu.dao..*.*(..))" /> <aop:before pointcut-ref="daoAop" method="tabDataSource" /> <aop:after-returning pointcut-ref="daoAop" method="doAfterReturning" /> </aop:aspect> </aop:config> </beans>
springmvc.xml配置,主要是註解開啟,掃描包,檢視層字尾配置(要不要無所謂)
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:jms="http://www.springframework.org/schema/jms" xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:p="http://www.springframework.org/schema/p" xmlns:task="http://www.springframework.org/schema/task"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.3.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-4.3.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-4.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-4.3.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
<aop:aspectj-autoproxy />
<!-- 註解支援 -->
<context:annotation-config />
<!-- 定義掃描根路徑為com,不使用預設的掃描方式 -->
<context:component-scan base-package="com"></context:component-scan>
<!-- 啟用基於註解的配置 @RequestMapping, @ExceptionHandler,資料繫結 ,@NumberFormat ,
@DateTimeFormat ,@Controller ,@Valid ,@RequestBody ,@ResponseBody等 -->
<mvc:annotation-driven />
<!-- 檢視層配置 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置JSTL表示式 -->
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<!-- 字首 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 字尾 -->
<property name="suffix" value=".jsp" />
</bean>
</beans>
web.xml,spring容器,轉發器,監聽器等
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>datasourceDynamic</display-name>
<welcome-file-list>
<welcome-file>index.do</welcome-file>
</welcome-file-list>
<!-- 配置spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<!-- 配置監聽器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 請求都交給DispatcherServlet處理 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 增加中文亂碼過濾器 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 清除jsp空格 -->
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<trim-directive-whitespaces>true</trim-directive-whitespaces>
</jsp-property-group>
</jsp-config>
</web-app>
最後我放幾個關鍵的類,其餘dao,service,controller就不放,意義沒有太大,理解原理就行了
DatasourceAspect動態資料來源切面
/**
*
*/
package com.liu.core.datasource;
import java.util.Map;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;
/**
* 動態資料來源切面
*
* @author liuchaojun
* @date 2018-12-12 上午10:42:11
*/
@Component
@Aspect
public class DatasourceAspect {
private Map<String, String> datasourceMap;
private String defaultDatasource;
/*
* 切入後執行的方法
*/
public void tabDataSource(JoinPoint joinPoint) {
boolean flag = false;
// 首先通過切點獲得類名
String className = joinPoint.getTarget().getClass().getName();
System.out.println(className);
for (String datasourceKey : datasourceMap.keySet()) {
if (datasourceKey.equals(className)) {
// 初始化資料來源
DatasourceTabUtils.setDatasourceName(datasourceMap
.get(datasourceKey));
System.out.println("切換資料來源...."+datasourceMap
.get(datasourceKey));
flag = true;
break;
}
}
if (!flag) {
DatasourceTabUtils.setDatasourceName(defaultDatasource);
}
}
/*
* 做完後的執行的方法 清除執行緒變數
*/
public void doAfterReturning(JoinPoint joinPoint) {
DatasourceTabUtils.clear();
}
/**
* @return the datasourceMap
*/
public Map<String, String> getDatasourceMap() {
return datasourceMap;
}
/**
* @param datasourceMap
* the datasourceMap to set
*/
public void setDatasourceMap(Map<String, String> datasourceMap) {
this.datasourceMap = datasourceMap;
}
/**
* @return the defaultDatasource
*/
public String getDefaultDatasource() {
return defaultDatasource;
}
/**
* @param defaultDatasource
* the defaultDatasource to set
*/
public void setDefaultDatasource(String defaultDatasource) {
this.defaultDatasource = defaultDatasource;
}
}
DatasourceTabUtils 資料來源切換工具類 本地執行緒變數操作
/**
*
*/
package com.liu.core.datasource;
/**
* @author liuchaojun
* @date 2018-12-12 上午10:43:47
*/
public class DatasourceTabUtils {
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void setDatasourceName(String datasourceName) {
threadLocal.set(datasourceName);
}
public static Object getDatasourceName() {
return threadLocal.get();
}
public static void clear() {
threadLocal.remove();
}
}
DynamicDatasource 動態資料來源路由類 這個類必須實現AbstractRoutingDataSource覆蓋determineCurrentLookupKey的方法
AbstractRoutingDataSource是Spring2.0後增加的。
這個其實也是資料來源切換的關鍵類,我們看下原始碼,簡單瞭解下
/*jadclipse*/// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) radix(10) lradix(10)
// Source File Name: AbstractRoutingDataSource.java
package org.springframework.jdbc.datasource.lookup;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.util.Assert;
// Referenced classes of package org.springframework.jdbc.datasource.lookup:
// JndiDataSourceLookup, DataSourceLookup
public abstract class AbstractRoutingDataSource extends AbstractDataSource
implements InitializingBean
{
public AbstractRoutingDataSource()
{
lenientFallback = true;
dataSourceLookup = new JndiDataSourceLookup();
}
public void setTargetDataSources(Map targetDataSources)
{
this.targetDataSources = targetDataSources;
}
public void setDefaultTargetDataSource(Object defaultTargetDataSource)
{
this.defaultTargetDataSource = defaultTargetDataSource;
}
public void setLenientFallback(boolean lenientFallback)
{
this.lenientFallback = lenientFallback;
}
public void setDataSourceLookup(DataSourceLookup dataSourceLookup)
{
this.dataSourceLookup = ((DataSourceLookup) (dataSourceLookup == null ? ((DataSourceLookup) (new JndiDataSourceLookup())) : dataSourceLookup));
}
public void afterPropertiesSet()
{
if(targetDataSources == null)
throw new IllegalArgumentException("Property 'targetDataSources' is required");
resolvedDataSources = new HashMap(targetDataSources.size());
Object lookupKey;
DataSource dataSource;
for(Iterator iterator = targetDataSources.entrySet().iterator(); iterator.hasNext(); resolvedDataSources.put(lookupKey, dataSource))
{
java.util.Map.Entry entry = (java.util.Map.Entry)iterator.next();
lookupKey = resolveSpecifiedLookupKey(entry.getKey());
dataSource = resolveSpecifiedDataSource(entry.getValue());
}
if(defaultTargetDataSource != null)
resolvedDefaultDataSource = resolveSpecifiedDataSource(defaultTargetDataSource);
}
protected Object resolveSpecifiedLookupKey(Object lookupKey)
{
return lookupKey;
}
protected DataSource resolveSpecifiedDataSource(Object dataSource)
throws IllegalArgumentException
{
if(dataSource instanceof DataSource)
return (DataSource)dataSource;
if(dataSource instanceof String)
return dataSourceLookup.getDataSource((String)dataSource);
else
throw new IllegalArgumentException((new StringBuilder()).append("Illegal data source value - only [javax.sql.DataSource] and String supported: ").append(dataSource).toString());
}
public Connection getConnection()
throws SQLException
{
return determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password)
throws SQLException
{
return determineTargetDataSource().getConnection(username, password);
}
public Object unwrap(Class iface)
throws SQLException
{
if(iface.isInstance(this))
return this;
else
return determineTargetDataSource().unwrap(iface);
}
public boolean isWrapperFor(Class iface)
throws SQLException
{
return iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface);
}
protected DataSource determineTargetDataSource()
{
Assert.notNull(resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = (DataSource)resolvedDataSources.get(lookupKey);
if(dataSource == null && (lenientFallback || lookupKey == null))
dataSource = resolvedDefaultDataSource;
if(dataSource == null)
throw new IllegalStateException((new StringBuilder()).append("Cannot determine target DataSource for lookup key [").append(lookupKey).append("]").toString());
else
return dataSource;
}
protected abstract Object determineCurrentLookupKey();
private Map targetDataSources;
private Object defaultTargetDataSource;
private boolean lenientFallback;
private DataSourceLookup dataSourceLookup;
private Map resolvedDataSources;
private DataSource resolvedDefaultDataSource;
}
/*
DECOMPILATION REPORT
Decompiled from: F:\mavenJarBaoLcj\.m2\org\springframework\spring-jdbc\4.2.6.RELEASE\spring-jdbc-4.2.6.RELEASE.jar
Total time: 62 ms
Jad reported messages/errors:
Exit status: 0
Caught exceptions:
*/