应开发需求需要,根据项目业务,不同业务下的数据存入不同的数据库中,且须读写分离。目前常用的有两种方式:
第一种:定义多个个数据库连接,分别是AReadDataSource,AWriteDataSource,BReadDataSource,BWriteDataSource,CReadDataSource, CWriteDataSource来实现;
第二种:动态数据源切换,就是在程序运行时,把数据源动态织入到程序中,从而选择读取对应的数据库。主要使用的技术是:annotation,Spring AOP ,反射。具体实现方式如下:
先定义一个annotation的注解类:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataSourceNote {
String dbSource() default "";
}
添加一个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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- testA读库写库 start -->
<bean id="testAReadDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- 读库个性化配置 -->
<property name="driverClassName" value="${jdbc.testA_read.driver}"/>
<property name="url" value="${jdbc.testA_read.url}"/>
<property name="username" value="${jdbc.testA_read.username}"/>
<property name="password" value="${jdbc.testA_read.password}"/>
</bean>
<bean id="testAWriteDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- 写库个性化配置 -->
<property name="driverClassName" value="${jdbc.testA_write.driver}"/>
<property name="url" value="${jdbc.testA_write.url}"/>
<property name="username" value="${jdbc.testA_write.username}"/>
<property name="password" value="${jdbc.testA_write.password}"/>
</bean>
<!-- testA读库写库 end -->
<!-- testB读库写库 start -->
<bean id="testBReadDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- 读库个性化配置 -->
<property name="driverClassName" value="${jdbc.testB_read.driver}"/>
<property name="url" value="${jdbc.testB_read.url}"/>
<property name="username" value="${jdbc.testB_read.username}"/>
<property name="password" value="${jdbc.testB_read.password}"/>
</bean>
<bean id="testBWriteDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- 写库个性化配置 -->
<property name="driverClassName" value="${jdbc.testB_write.driver}"/>
<property name="url" value="${jdbc.testB_write.url}"/>
<property name="username" value="${jdbc.testB_write.username}"/>
<property name="password" value="${jdbc.testB_write.password}"/>
</bean>
<!-- testB读库写库 end -->
<!-- testC读库 写库 start -->
<bean id="testCReadDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- 读库个性化配置 -->
<property name="driverClassName" value="${jdbc.testC_read.driver}"/>
<property name="url" value="${jdbc.testC_read.url}"/>
<property name="username" value="${jdbc.testC_read.username}"/>
<property name="password" value="${jdbc.testC_read.password}"/>
</bean>
<bean id="testCWriteDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- 写库个性化配置 -->
<property name="driverClassName" value="${jdbc.testC_write.driver}"/>
<property name="url" value="${jdbc.testC_write.url}"/>
<property name="username" value="${jdbc.testC_write.username}"/>
<property name="password" value="${jdbc.testC_write.password}"/>
</bean>
<!-- testC读库 写库 end -->
<bean id="dataSource" class="com.test.dao.aspect.ChooseDataSource" lazy-init="true">
<description>数据源</description>
<property name="targetDataSources">
<map key-type="java.lang.String" value-type="javax.sql.DataSource">
<!-- write -->
<entry key="testAWrite" value-ref="testAWriteDataSource" />
<entry key="testBWrite" value-ref="testBWriteDataSource" />
<entry key="testCWrite" value-ref="testCWriteDataSource" />
<!-- read -->
<entry key="testARead" value-ref="testAReadDataSource" />
<entry key="testBRead" value-ref="testBReadDataSource" />
<entry key="testCRead" value-ref="testCReadDataSource" />
</map>
</property>
<property name="methodType">
<map key-type="java.lang.String">
<!-- read -->
<entry key="testARead" value=",get,select,count,list,query," />
<entry key="testBRead" value=",get,select,count,list,query," />
<entry key="testCRead" value=",get,select,count,list,query," />
<!-- write -->
<entry key="testAWrite" value=",add,insert,create,update,delete,remove," />
<entry key="testBWrite" value=",add,insert,create,update,delete,remove," />
<entry key="testCWrite" value=",add,insert,create,update,delete,remove," />
</map>
</property>
</bean>
<!-- 切面 -->
<bean class="com.test.dao.aspect.DataSourceAspect" />
<!-- jdbc模板 -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
</beans>
然后将dataSource.xml配置到spring-config.xml里面去,具体参考如下文件:
<import resource="classpath*:spring/dataSource.xml"/>
mybatis.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-4.0.xsd">
<!-- 开启注解配置 -->
<context:annotation-config />
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:sqlmaps/**/*.xml" />
</bean>
<!-- 事务管理 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
</beans>
现在再来看看,dataSource.xml里面相关的两个类的代码,第一个DataSourceAspect,用于动态切换数据库源:
@Aspect
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Log4j2
public class DataSourceAspect {
@Pointcut("execution(* com.test.dao.*..*Impl.*(..))")
public void aspect() {
}
/**
* 配置前置通知,使用在方法aspect()上注册的切入点
*/
@Before("aspect()")
public void before(JoinPoint point) {
String method = point.getSignature().getName();
Log4j2Util.printLog4j2(log,this, method, Level.INFO, "(" + StringUtils.join(point.getArgs(), ",") + ")");
//获取到对象的类
Object obj = point.getTarget();
Method methods[] = obj.getClass().getMethods();
for (int i = 0; i< methods.length; i++) {
if(method.equals(methods[i].getName())){
DataSourceNote note = methods[i].getAnnotation(DataSourceNote.class);
String dbSource = note.dbSource();
if(StringUtils.isNotEmpty(dbSource)){
Log4j2Util.printLog4j2(log,this, "before", Level.DEBUG, "---------数据库类型:"+dbSource);
HandleDataSource.putDataSource(dbSource);
}else{
Log4j2Util.printLog4j2(log,this, "before", Level.ERROR, "---------数据库类型:"+dbSource);
}
}
}
}
@After("aspect()")
public void after(JoinPoint point) {
HandleDataSource.clear();
}
}
下面再来看看ChooseDataSource类的处理,获取对应的dataSource里面的数据源:
public class ChooseDataSource extends AbstractRoutingDataSource {
public static Map<String, List<String>> METHODTYPE = new HashMap<String, List<String>>();
// 获取数据源名称
protected Object determineCurrentLookupKey() {
return HandleDataSource.getDataSource();
}
// 设置方法名前缀对应的数据源
public void setMethodType(Map<String, String> map) {
for (String key : map.keySet()) {
List<String> v = new ArrayList<String>();
String[] types = map.get(key).split(",");
for (String type : types) {
if (StringUtils.isNotBlank(type)) {
v.add(type);
}
}
METHODTYPE.put(key, v);
}
}
}
代码实际使用示例如下:
public class TestDataDaoImpl extends BaseDao implements TestDataDao{
private static final String NAME_SPACE = "com.test.dao.TestDataDao.";
@Override
@DataSourceNote(dbSource = "testAWrite")
public boolean addTestAUserMsg(List<UserDO> list)
throws DaoException {
Integer count = this.insert(NAME_SPACE + "addTestAUserMsg", list);
return (count == null || count == 0) ? false : true;
}
@Override
@DataSourceNote(dbSource = "testBWrite")
public boolean addTestBUserMsgLog(List<UserMsgLogDO> list)
throws DaoException {
Integer count = this.insert(NAME_SPACE + "addTestBUserMsgLog", list);
return (count == null || count == 0) ? false : true;
}
@Override
@DataSourceNote(dbSource = "testCRead")
public UserDetail queryTestCUserDetail(Map<String, String> param)
throws DaoException {
UserDetail temp = (UserDetail ) this.queryForObject(NAME_SPACE + "queryTestCUserDetail", param);
return temp;
}
}
按步骤依顺配置下来即可,不是很明白的地方可以去看看org.apache.commons.dbcp.BasicDataSource里的createConnectionFactory方法。及其他相关方法