动态数据源
多数据源问题很常见,例如读写分离数据库配置。
我们很多项目中业务都需要涉及到多个数据源,最简单的做法就是直接在Java代码里面lookup需要的数据源,但是这样的做法很明显耦合度太高了,
而且当逻辑流程不够严谨的时候就会出现各种大家不愿意看到的问题,由于我们现在的大多项目已经离不开spring了,spring也提供各种强大的功能,
很明显这种动态数据源功能也包括在内,具体实现类请看
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
1)首先配置多个datasource和AOP
datasource.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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd" default-autowire="byName">
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="DS" />
<property name="typeAliasesPackage" value="com.letv.ofc.domain" />
<property name="configLocation" value="classpath:mybatis-config.xml" />
<!-- mapper and resultmap path -->
<property name="mapperLocations">
<list>
<value>classpath:sqlmap/mysql/*/*.xml</value>
</list>
</property>
</bean>
<bean id="DS" class="DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="ofc" key="dataSourceOfc"/>
<entry value-ref="order" key="dataSourceOrder"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="ofc"/>
</bean>
<bean id="ofc" class="org.apache.commons.dbcp.BasicDataSource" >
<property name="driverClassName" value="${mysql.db.driverClassName}" />
<property name="defaultAutoCommit" value="true" />
<property name="url" value="${ofc.db.url}"/>
<property name="username" value="${ofc.db.username}" />
<property name="password" value="${ofc.db.password}" />
<property name="initialSize" value="${ofc.db.initialSize}" />
<property name="maxActive" value="${ofc.db.maxActive}" />
<property name="maxIdle" value="${ofc.db.maxIdle}" />
<property name="maxWait" value="${ofc.db.maxWait}" />
<property name="minIdle" value="${ofc.db.minIdle}" />
<property name="timeBetweenEvictionRunsMillis" value="${ofc.db.timeBetweenEvictionRunsMillis}" />
<property name="numTestsPerEvictionRun" value="${ofc.db.numTestsPerEvictionRun}" />
<property name="minEvictableIdleTimeMillis" value="${ofc.db.minEvictableIdleTimeMillis}" />
</bean>
<bean id="order" class="org.apache.commons.dbcp.BasicDataSource" >
<property name="driverClassName" value="${mysql.db.driverClassName}" />
<property name="defaultAutoCommit" value="true" />
<property name="url" value="${order.db.url}"/>
<property name="username" value="${order.db.username}" />
<property name="password" value="${order.db.password}" />
<property name="initialSize" value="${order.db.initialSize}" />
<property name="maxActive" value="${order.db.maxActive}" />
<property name="maxIdle" value="${order.db.maxIdle}" />
<property name="maxWait" value="${order.db.maxWait}" />
<property name="minIdle" value="${order.db.minIdle}" />
<property name="timeBetweenEvictionRunsMillis" value="${order.db.timeBetweenEvictionRunsMillis}" />
<property name="numTestsPerEvictionRun" value="${order.db.numTestsPerEvictionRun}" />
<property name="minEvictableIdleTimeMillis" value="${order.db.minEvictableIdleTimeMillis}" />
</bean>
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
<aop:pointcut id="ofcAspect" expression="execution(* ofc.manager..*.*(..))" />>
<aop:pointcut id="orderAspect" expression="execution(* order.manager..*.*(..))" />
<aop:before pointcut-ref="ofcAspect" method="setDataSourceOfc" />
<aop:before pointcut-ref="orderAspect" method="setDataSourceOrder" />
</aop:aspect>
</aop:config>
2)类的实现
本例子是通过Spring AOP来实现动态datasource的设置的。具体的类如下:
2.1 DataSourceInterceptor类的实现。它出 Spring AOP调用方法的实现。
该类的方法就是在AOP拦截时候,会被spring AOP调用,动态地根据被执行的方法来设置对应的DS
2.2 DynamicDataSource extends AbstractRoutingDataSource,这个是实现spring 动态数据源必须要做的。
spring 规定用户必须继承
AbstractRoutingDataSource并实现:AbstractRoutingDataSource的determineCurrentLookupKey。
另外考虑到多线程环境,所以一般要使用ThreadLocal类来包装线程特有的变量,如下文的DatabaseContextHolder:contextHolder变量。
sqlSessionFactory的dataSource属性设置为DynamicDataSource 后,当要访问mysql时候,spring会调用
DynamicDataSource.determineCurrentLookupKey()来获得key,然后根据下面的xml配置来得到targetDataSources,根据key,比如"dataSourceOfc",就得到了对应的value,从而得到了 数据库访问的名字和属性,比如"
ofc"。
<bean id="dataSource" class="com.letv.ofc.service.base.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="ofc" key="dataSourceOfc"/>
<entry value-ref="order" key="dataSourceOrder"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="ofc"/>
</bean>
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="ofc" key="dataSourceOfc"/>
<entry value-ref="order" key="dataSourceOrder"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="ofc"/>
</bean>
这里关键是在调用不同的类的方法时候,要利用AOP来设置不同的key,比如
"dataSourceOfc",或者"dataSourceOrder",而这个则是通过调用对象DataSourceInterceptor的不同方法来完成的。
该类通过 spring AOP (如下的xml的配置方式)来实现的。
<aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
<aop:pointcut id="ofcAspect" expression="execution(* com.letv.ofc.manager..*.*(..))" />
<aop:pointcut id="orderAspect" expression="execution(* com.letv.order.manager..*.*(..))" />
<aop:before pointcut-ref="ofcAspect" method="setDataSourceOfc" />
<aop:before pointcut-ref="orderAspect" method="setDataSourceOrder" />
</aop:aspect>
</aop:config>
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
<aop:pointcut id="ofcAspect" expression="execution(* com.letv.ofc.manager..*.*(..))" />
<aop:pointcut id="orderAspect" expression="execution(* com.letv.order.manager..*.*(..))" />
<aop:before pointcut-ref="ofcAspect" method="setDataSourceOfc" />
<aop:before pointcut-ref="orderAspect" method="setDataSourceOrder" />
</aop:aspect>
</aop:config>
public class DatabaseContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
return contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
public class DataSourceInterceptor {
public void setDataSourceOfc(JoinPoint jp) {
DatabaseContextHolder.setCustomerType("dataSourceOfc");
}
public void setDataSourceOrder(JoinPoint jp) {
DatabaseContextHolder.setCustomerType("dataSourceOrder");
}
}
DynamicDataSource extends AbstractRoutingDataSource {
//@Override
protected Object determineCurrentLookupKey() {
return DatabaseContextHolder.getCustomerType();
}
}