java-通过代码实现动态选择数据源
目录
java 通过代码实现动态选择数据源
类注解
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specify DataSource (指定数据源,用于执行事物切面时,指定其他数据源)<br>
* 注意:<br>
* {@code @SpecifyDS}放在类级别上等同于该类的每个公有方法都放上了{@code @SpecifyDS}<br>
* {@code @SpecifyDS}只对公有法有效(与Spring的{@code @Transactional}只对公有方法有效道理是一样的,因为都是Spring AOP代理)
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SpecifyDS {
String value();
}
线程独享的数据源对象
public class DataSourceHolder {
public static final ThreadLocal<String> holder = new ThreadLocal<String>();
public static void setDataSource(String name) {
holder.set(name);
}
public static String getDataSouce() {
return holder.get();
}
}
动态设置数据源
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 获取当前ThreadLocal设置的dataSource (动态设置的dataSource)
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSouce();
}
}
数据源自动选择的切面类
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.transaction.annotation.Transactional;
public class DataSourceAspect implements org.springframework.core.Ordered {
private static final String READ_ONLY_METHOD_PREFFIX = "select";
private static final String DEFAULT_MASTER_SUFFIX = "MasterDataSource";
private static final String DEFAULT_SLAVE_SUFFIX = "SlaveDataSource";
private static final int DEFAULT_ORDER = -1;
/**
* CUD【创建(Create)、更新(Update)和删除(Delete)】所使用的数据源
* <br>默认使用 {主库}
*/
private String cudDBSuffix = DEFAULT_MASTER_SUFFIX;
/**
* R【读取(Read)】所使用的数据源
* <br>默认使用 {从库}
*/
private String readDBSuffix = DEFAULT_SLAVE_SUFFIX;
/**
* Spring Aspect的顺序,如果一个方法上有多个切面,则值越小的先执行。
*/
private int order = DEFAULT_ORDER;
/**
* 执行事物切面时默认使用的数据源(通常配置为项目的主数据库的主库)
*/
private String defaultTransDb;
public void before(JoinPoint point) {
if (DataSourceHolder.getDataSouce() == null) {
// 根据包路径名 获取dataSource的key前缀,这样效率太低,改成根据Mapper前缀来获取
Class<?>[] classz = point.getTarget().getClass().getInterfaces();
String interfacePath = classz[0].getName();
// 获取dao和mapper之间的包名,这儿包名与xml的dataSource的key前缀相同
int daoIndex = interfacePath.indexOf("dao");
int mapperIndex = interfacePath.indexOf("mapper");
String packet = interfacePath.substring(daoIndex + 4, mapperIndex - 1);
if (point.getSignature().getName().indexOf(READ_ONLY_METHOD_PREFFIX) != -1) {
DataSourceHolder.setDataSource(packet + readDBSuffix);
} else {
DataSourceHolder.setDataSource(packet + cudDBSuffix);
}
}
}
public void beforeTrans(JoinPoint point) {
Signature signature = point.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
// 获取目标类
Class<?> target = point.getTarget().getClass();
Method targetMethod = null;
try {
targetMethod = target.getMethod(method.getName(), method.getParameterTypes());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
// 根据目标类方法中的注解,选择数据源
if (targetMethod != null) {
Transactional transactional = targetMethod.getAnnotation(Transactional.class);
if (transactional != null) {
SpecifyDS specifyDSCls = target.getAnnotation(SpecifyDS.class);
SpecifyDS specifyDS = targetMethod.getAnnotation(SpecifyDS.class);
if (specifyDS != null) {
DataSourceHolder.setDataSource(specifyDS.value());
} else if (specifyDSCls != null) {
DataSourceHolder.setDataSource(specifyDSCls.value());
} else {
DataSourceHolder.setDataSource(defaultTransDb);
}
}
}
}
public void after() {
DataSourceHolder.setDataSource(null);
}
@Override
public int getOrder() {
return order;
}
public void setOrder(int order){
this.order = order;
}
/**
* @return the cudDBSuffix
*/
public String getCudDBSuffix() {
return cudDBSuffix;
}
/**
* @param cudDBSuffix the cudDBSuffix to set
*/
public void setCudDBSuffix(String cudDBSuffix) {
this.cudDBSuffix = cudDBSuffix;
}
/**
* @return the readDBSuffix
*/
public String getReadDBSuffix() {
return readDBSuffix;
}
/**
* @param readDBSuffix the readDBSuffix to set
*/
public void setReadDBSuffix(String readDBSuffix) {
this.readDBSuffix = readDBSuffix;
}
/**
* @return the defaultTransDb
*/
public String getDefaultTransDb() {
return defaultTransDb;
}
/**
* @param defaultTransDb the defaultTransDb to set
*/
public void setDefaultTransDb(String defaultTransDb) {
this.defaultTransDb = defaultTransDb;
}
}
JDBC配置文件
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
default-autowire="byName">
<!--druid-->
<bean id="MasterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.map.master.url}"/>
<property name="username" value="${jdbc.map.master.username}"/>
<property name="password" value="${jdbc.map.master.password}"/>
<property name="initialSize" value="5"/>
<property name="minIdle" value="5"/>
<property name="maxActive" value="10"/>
<property name="timeBetweenEvictionRunsMillis" value="5000"/>
<property name="minEvictableIdleTimeMillis" value="60000"/>
<property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="removeAbandoned" value="true"/>
<property name="removeAbandonedTimeout" value="1800"/>
<property name="logAbandoned" value="true"/>
<property name="filters" value="stat"/>
<property name="connectionProperties" value="druid.stat.slowSqlMillis=100"/>
</bean>
<bean id="SlaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.map.slave.url}"/>
<property name="username" value="${jdbc.map.slave.username}"/>
<property name="password" value="${jdbc.map.slave.password}"/>
<property name="initialSize" value="5"/>
<property name="minIdle" value="5"/>
<property name="maxActive" value="10"/>
<property name="timeBetweenEvictionRunsMillis" value="5000"/>
<property name="minEvictableIdleTimeMillis" value="30000"/>
<property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="removeAbandoned" value="true"/>
<property name="removeAbandonedTimeout" value="1800"/>
<property name="logAbandoned" value="true"/>
<property name="filters" value="stat"/>
<property name="connectionProperties" value="druid.stat.slowSqlMillis=100"/>
</bean>
<!-- 配置DynamicDataSource代替普通dataSource,将实际数据源的DataSource赋值给它 -->
<bean id="dataSource" class="com.groupname.commons.db.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="MasterDataSource" value-ref="MasterDataSource"/>
<entry key="SlaveDataSource" value-ref="SlaveDataSource"/>
</map>
</property>
<!-- 默认主库 -->
<property name="defaultTargetDataSource" ref="MasterDataSource"/>
</bean>
<!-- 配置数据库注解aop,mapper层选择数据源 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="multiDataSourceAspect" class="com.groupname.commons.db.DataSourceAspect">
<!-- 执行事物切面时默认使用的数据源(通常配置为项目的主数据库的主库)。如果不配置,将默认使用上面的defaultTargetDataSource -->
<property name="defaultTransDb" value="mapMasterDataSource" />
<!-- 以下是默认配置,一般无需修改 -->
<!-- CUD【创建(Create)、更新(Update)和删除(Delete)】所使用的数据源。默认使用 {主库} -->
<!-- <property name="cudDBSuffix" value="MasterDataSource" /> -->
<!-- R【读取(Read)】所使用的数据源。默认使用 {从库} -->
<!-- <property name="readDBSuffix" value="SlaveDataSource" /> -->
</bean>
<aop:config>
<aop:aspect ref="multiDataSourceAspect" >
<!-- 为DAO层的Mapper自动选择数据源:默认规则是select开头的方法选择从库,其他像update、insert等方法选择主库。
可更改multiDataSourceAspect的slaveSuffix的值来改变默认策略 -->
<aop:pointcut id="daoDataSourceAutoSelect" expression="execution(* com.groupname.*.center.dao.*.mapper.*.*(..))"/>
<aop:before pointcut-ref="daoDataSourceAutoSelect" method="before"/>
<aop:after pointcut-ref="daoDataSourceAutoSelect" method="after"/>
<!-- 为事务控制自动选择数据源:扫描有@Transactional注解的service方法,默认切换到上面配置的defaultTransDb数据源去执行事务。
如果要切换其他数据源,可以在方法上再加一个@SpecifyDS({value}),在value中注明是哪个数据源ID -->
<aop:pointcut id="transDataSourceAutoSelect" expression="execution(* com.groupname.*.center.service.*.*(..))||execution(* com.groupname.*.manager.service.biz.*.*(..))"/>
<aop:before pointcut-ref="transDataSourceAutoSelect" method="beforeTrans"/>
<aop:after pointcut-ref="transDataSourceAutoSelect" method="after"/>
</aop:aspect>
</aop:config>
<!-- 定义事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 事务支持注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--编程式事务使用-->
<bean id="txDefinition" class="org.springframework.transaction.support.DefaultTransactionDefinition"></bean>
</beans>