目录

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>