spring 配置双数据源并读写分离
摘自 开源项目Ibase4j
关键思想在于AbstractRoutingSource 类 还有方法名称和切入点去控制使用哪个数据源
1.首先在配置文件配置多个数据源 并且交给继承自spring AbstractRoutingSource去管理
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"> <bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter" lazy-init="true"> <description>状态过滤器</description> <property name="slowSqlMillis" value="3000" /> <property name="logSlowSql" value="true" /> <property name="mergeSql" value="true" /> </bean> <bean id="readDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init" lazy-init="true"> <description>只读数据库连接</description> <property name="driverClassName" value="${db.driver}" /> <property name="url" value="${db.reader.url}" /> <property name="username" value="${db.reader.username}" /> <property name="password" value="${db.reader.password}" /> <!-- 初始化连接大小 --> <property name="initialSize" value="${db.initialSize}" /> <!-- 连接池最大数量 --> <property name="maxActive" value="${db.maxActive}" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="${db.minIdle}" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="${db.maxWait}" /> <!-- --> <property name="defaultReadOnly" value="true" /> <property name="proxyFilters"> <list> <ref bean="stat-filter" /> </list> </property> <property name="filters" value="${druid.filters}" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="timeBetweenLogStatsMillis" value="60000" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${db.timeBetweenEvictionRunsMillis}" /> </bean> <bean id="writeDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init" lazy-init="true"> <description>只写数据库连接</description> <property name="driverClassName" value="${db.driver}" /> <property name="url" value="${db.writer.url}" /> <property name="username" value="${db.writer.username}" /> <property name="password" value="${db.writer.password}" /> <property name="initialSize" value="${db.initialSize}" /> <property name="maxActive" value="${db.maxActive}" /> <property name="minIdle" value="${db.minIdle}" /> <property name="maxWait" value="${db.maxWait}" /> <property name="defaultReadOnly" value="false" /> <property name="proxyFilters"> <list> <ref bean="stat-filter" /> </list> </property> <property name="filters" value="${druid.filters}" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="timeBetweenLogStatsMillis" value="60000" /> <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}" /> <property name="timeBetweenEvictionRunsMillis" value="${db.timeBetweenEvictionRunsMillis}" /> </bean>
<!--注意这里的配置--> <bean id="dataSource" class="org.ibase4j.core.aspect.ChooseDataSource" lazy-init="true"> <description>数据源</description> <property name="targetDataSources"> <map key-type="java.lang.String" value-type="javax.sql.DataSource"> <!-- write --> <entry key="write" value-ref="writeDataSource" /> <!-- read --> <entry key="read" value-ref="readDataSource" /> </map> </property> <property name="defaultTargetDataSource" ref="writeDataSource" /> <property name="methodType"> <map key-type="java.lang.String"> <!-- read --> <entry key="read" value=",get,select,count,list,query," /> <!-- write --> <entry key="write" value=",add,insert,create,update,delete,remove," /> </map> </property> </bean>
<!-- 注意这个切面 --> <bean class="org.ibase4j.core.aspect.DataSourceAspect" /> <!-- --> <bean class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg ref="dataSource" /> </bean> </beans>
接下来事继承类
package org.ibase4j.core.aspect; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 获取数据源 * * @author ShenHuaJie * @version 2016年5月20日 下午3:17:16 */ 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); } } }
接下来切面实现类
package org.ibase4j.core.aspect; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * 切换数据源(不同方法调用不同数据源) * * @author ShenHuaJie * @version 2016年5月20日 下午3:17:52 */ @Aspect @EnableAspectJAutoProxy(proxyTargetClass = true) public class DataSourceAspect { private final Logger logger = LogManager.getLogger(); //配置切入点 @Pointcut("execution(* org.ibase4j.service..*.*(..))") public void aspect() { } /** * 配置前置通知,使用在方法aspect()上注册的切入点 */ @Before("aspect()") public void before(JoinPoint point) { String className = point.getTarget().getClass().getName(); String method = point.getSignature().getName(); logger.info(className + "." + method + "(" + StringUtils.join(point.getArgs(), ",") + ")"); try { L: for (String key : ChooseDataSource.METHODTYPE.keySet()) { for (String type : ChooseDataSource.METHODTYPE.get(key)) { if (method.startsWith(type)) { logger.info(key); HandleDataSource.putDataSource(key); break L; } } } } catch (Exception e) { logger.error(e); HandleDataSource.putDataSource("write"); } } @After("aspect()") public void after(JoinPoint point) { HandleDataSource.clear(); } }
还有一个帮助你拿到数据元的类
package org.ibase4j.core.aspect; /** * @author ShenHuaJie * @version 2016年5月20日 下午3:18:04 */ public class HandleDataSource { // 数据源名称线程池 private static final ThreadLocal<String> holder = new ThreadLocal<String>(); public static void putDataSource(String datasource) { holder.set(datasource); } public static String getDataSource() { return holder.get(); } public static void clear() { holder.remove(); } }