参考文章:blog.csdn.net/guying4875/…
监于原文章格式比较乱,并且文章不太好找,所以参考文章重新排版,方便自己加深印象。
说明:文中代码做了部分删减,关键信息做了擦除
原理讲解
实现思路:在spring中配置多个数据源,然后在service层通过注解方式标明方法所要使用的数据源,利用springAOP在service方法执行前根据方法上的注解明确所要使用的数据源。如下图
以上分析可得出,需要抽象出一个DynamicDataSource数据源,在spring中关于数据源的配置都是基于DynamicDataSource,在运行时根据service上的注解从DynamicDataSource选取需要的数据源进行实际的持久化操作;首先,介绍spring 的AbstractRoutingDataSource 类
AbstractRoutingDataSource这个类 是spring2.0以后增加的
我们先来看下AbstractRoutingDataSource的定义(下面开始上代码,只展示了关键的一些代码):
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
private Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource;
......
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
......
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
// 一个后门,留给子类用来重写。获取xml文件中配置的key
Object lookupKey = determineCurrentLookupKey();
// resolvedDataSources 就是解析的xml配置文件,解析的键值对存入一个map中,
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;*
}
protected abstract Object determineCurrentLookupKey();
}
AbstractRoutingDataSource继承了AbstractDataSource ,而AbstractDataSource 又是DataSource 的子类。
DataSource 是javax.sql 的数据源接口,定义如下:
package javax.sql;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Wrapper;
public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
}
DataSource 接口定义了2个方法,都是获取数据库连接。我们在看下AbstractRoutingDataSource 如何实现了DataSource接口,
我们主要关心两句话
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
determineCurrentLookupKey方法返回lookupKey,resolvedDataSources方法就是根据lookupKey从Map中获得数据源。
resolvedDataSources 和determineCurrentLookupKey定义如下:
// 键值对
private Map<Object, DataSource> resolvedDataSources;
// 抽象方法,留个子类实现
protected abstract Object determineCurrentLookupKey()
看到以上定义,我们是不是有点思路了,resolvedDataSources是Map类型,我们可以把MasterDataSource和SlaveDataSource存到Map中,如下:
| key | value |
|---|---|
| master | MasterDataSource |
| slave | SlaveDataSource |
我们在写一个类DynamicDataSource 继承AbstractRoutingDataSource,实现其 determineCurrentLookupKey() 方法,该方法返回Map的key,master或slave。这样就达到了切换数据库的目的。
介绍实现方式:
1. 创建注解类,用来标记service方法所要使用的数据源key
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceSeparate {
DataSourceTypeEnum value() default DataSourceTypeEnum.MASTER;
}
2. 创建DynamicDataSource类,该类继承自AbstractRoutingDataSource
注: DynamicDataSourceHolder类可以省略,跟DynamicDataSource合并成一个类。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDbType();
}
}
// @Description DynamicDataSourceHolder 此类主要就是为了构建一个 ThreadLocal。对当前线程存储一个全局的对象。 然后通过Aop的切面,进行动态的更改 这个全局的 ThreadLocal对象值。
public abstract class {
private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static String getDbType() {
String db = contextHolder.get();
if (db == null) {
// 默认为主库
db = "master";
}
return db;
}
public static void setDbType(String str) {
contextHolder.set(str);
}
// 清理连接类型
public static void clearDBType() {
contextHolder.remove();
}
}
方法中实现了determineCurrentLookupKey()方法,该方法会从ThreadLocal对象中获取到一个key来表明所要使用的数据源
3. 实现springAop来根据service中方法上的注解设置ThreadLocal对象
package
import com.fengjr.datasource.DynamicDataSourceHolder;
import com.fengjr.p2p.asset.common.annotations.DataSourceSeparate;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
// @Description 默认使用主库,根据切面选择从库
@Slf4j
@Order(1)
@Aspect
public class DynamicDataSourceAspect {
@Pointcut("execution(* com..service..*.*(..)) && @annotation(com.annotations.DataSourceSeparate)")
private void anyMethod() {}//声明一个切入点
//在service的所有方法执行前,并且带有注解 DataSourceSeparate
@Before("anyMethod()")
public void beforeDynamicSource(JoinPoint joinPoint){
//获取方法,此处可将signature强转为MethodSignature
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
DataSourceSeparate annotation = method.getAnnotation(DataSourceSeparate.class);
String key = annotation.value().getKey();
System.out.println(key);
try {
DynamicDataSourceHolder.setDbType(key);
} catch (Throwable t) {
log.info(t.getMessage(), t);
}
}
@After("anyMethod()")
public void afterDynamicSource(){
try {
DynamicDataSourceHolder.clearDBType();
} catch (Throwable t) {
log.info(t.getMessage(), t);
}
}
}
4. 开始配置spring数据源 application-jdbc.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: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.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 基础 数据源 -->
<bean id="abstractDataSource" abstract="true" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
<property name="minPoolSize" value="${jdbc.minPoolSize}"/>
<property name="acquireRetryAttempts" value="${jdbc.acquireRetryAttempts}"/>
<property name="testConnectionOnCheckout" value="${jdbc.testConnectionOnCheckout}"/>
<property name="breakAfterAcquireFailure" value="${jdbc.breakAfterAcquireFailure}"/>
<property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}"/>
<property name="maxStatements" value="${jdbc.maxStatements}"/>
<property name="maxIdleTime" value="${jdbc.maxIdleTime}"/>
<property name="driverClass" value="${jdbc.driver}"/>
</bean>
<bean id="dataSource_master_p2p_asset" parent="abstractDataSource">
<property name="jdbcUrl" value="${mysql.pro}"/>
<property name="user" value="${mysql.pro.w}"/>
<property name="password" value="${mysql.pro.pwd.rw}"/>
</bean>
<!-- 从数据库 主要负责读数据 -->
<bean id="dataSource_slave" parent="abstractDataSource">
<property name="jdbcUrl" value="${mysql.pro.url.read}"/>
<property name="user" value="${mysql.pro.user.read}" />
<property name="password" value="${mysql.pro.pwd.read}" />
</bean>
<!-- 配置动态数据源 -->
<bean id="dataSource" class="com.fengjr.datasource.DynamicDataSource" lazy-init="true">
<property name="targetDataSources">
<!-- 此处是关键,返回的对象是DataSource, 这是 java.sql包下的一个类。 -->
<map key-type="java.lang.String" value-type="javax.sql.DataSource">
<entry value-ref="dataSource_master" key="master"></entry>
<entry value-ref="dataSource_slave" key="slave"></entry>
</map>
</property>
<!-- 设置默认的目标数据源 -->
<property name="defaultTargetDataSource" ref="dataSource_master" />
</bean>
</beans>
5. 实例使用方法
public interface Userservic {
//@DataSource("master") 默认查找主库
public void add(User user);
@DataSourceSeparate(value = DataSourceTypeEnum.SLAVE)
public void selectByCondition(User user);
}
读写分析方法还有很多,这只是其中一种,首次写文,不足指出还请多多见谅。