应开发需求需要,根据项目业务,不同业务下的数据存入不同的数据库中,且须读写分离。目前常用的有两种方式:
第一种:定义多个个数据库连接,分别是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方法。及其他相关方法