最近的项目中在数据库优化的时候需要用到读写分离,由于代码已经写好了,所以最优的方式就是在代码端不做任何修改,由于mybatis的灵活性,所以考虑用mybatis执行的时候通过插件的形式进行动态设置数据源。又由于spring支持数据源的懒加载和路由数据源的功能,所以最终解决方案是mybatis+spring数据源懒加载+spring动态数据源
话不多说,直接上代码
首先根据写一个动态数据源类去继承spring的AbstractRoutingDataSource类,重写他的determineCurrentLookupKey方法,从AbstractRoutingDataSource类中的
/** * Retrieve the current target DataSource. Determines the * {@link #determineCurrentLookupKey() current lookup key}, performs * a lookup in the {@link #setTargetDataSources targetDataSources} map, * falls back to the specified * {@link #setDefaultTargetDataSource default target DataSource} if necessary. * @see #determineCurrentLookupKey() */ protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; }
所以这个方法主要是获取数据源的key
public class DynamicDataSource extends AbstractRoutingDataSource { /* * (non-Javadoc) * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey() */ @Override protected Object determineCurrentLookupKey() { return DBContextHolder.getDbType(); } }
public class DBContextHolder { private static Logger logger = Logger.getLogger(DBContextHolder.class); /** * 线程ThreadLocal */ private static ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static final String DB_TYPE_WR = "dbTypeWR"; public static final String DB_TYPE_RD = "dbTypeRD"; public static String getDbType() { String db = contextHolder.get(); if(db == null) { db = DB_TYPE_WR;// 默认是读写库 } return db; } /** * @Title: setDbType * @Description: 设置本线程的dbtype * @author YiXiaoGang * @date 2015年12月25日 下午3:04:49 * @param str */ public static void setDbType(String str) { logger.debug("所使用数据源为:" + str); contextHolder.set(str); } /** * @Title: clearDBType * @Description: 清理连接类型 * @author YiXiaoGang * @date 2015年12月25日 下午3:05:00 */ public static void clearDBType() { contextHolder.remove(); } }
@Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})} ) public class DatasouceInterceptor implements Interceptor { /** * 插件方法更新 */ private static final String UPDATE = "update"; /** * 插件方法查询 */ private static final String QUERY = "query"; Logger logger = Logger.getLogger(PagingInterceptor.class); /** * SQL方言 可选MySql,Oracle */ private String diacect; /** * mybatis插件拦截方法,用于分页,加解密 */ @Override public Object intercept(Invocation invocation) throws Throwable { String methodName = invocation.getMethod().getName(); MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0]; if(QUERY.equals(methodName)) { DBContextHolder.setDbType(DBContextHolder.DB_TYPE_RD); return doQuery(invocation, fields, coditions); }else if(UPDATE.equals(methodName)) { DBContextHolder.setDbType(DBContextHolder.DB_TYPE_WR); return doUpdate(invocation, fields); } return null; }
最后配置多数据源spring数据源部分如下
<!-- 配置主数据源(可读写)--> <bean id="dataSourceWR" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" /> <property name="username" value="test1" /> <property name="password" value="test1" /> <!-- 初始化连接数 --> <property name="initialSize" value="1" /> <!-- 最大活动连接数 <property name="maxActive" value="50" /> --> <!-- 最大空闲连接数 --> <property name="maxIdle" value="10" /> <!-- 最小空闲连接数 --> <property name="minIdle" value="1" /> <!-- 获取连接超时等待时间(毫秒) <property name="maxWait" value="10000" /> --> <!-- 空闲池空闲连接激活线程的运行间隔时间(毫秒) --> <property name="timeBetweenEvictionRunsMillis" value="60000" /> <!-- 空闲池中空闲连接能够被激活前的最小空闲时间(毫秒) --> <property name="minEvictableIdleTimeMillis" value="10000" /> </bean> <!--配置从数据源,只用于读 --> <bean id="dataSourceRD" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcal" /> <property name="username" value="test2" /> <property name="password" value="test2" /> <!-- 初始化连接数 --> <property name="initialSize" value="1" /> <!-- 最大活动连接数 <property name="maxActive" value="50" /> --> <!-- 最大空闲连接数 --> <property name="maxIdle" value="10" /> <!-- 最小空闲连接数 --> <property name="minIdle" value="1" /> <!-- 获取连接超时等待时间(毫秒) <property name="maxWait" value="10000" /> --> <!-- 空闲池空闲连接激活线程的运行间隔时间(毫秒) --> <property name="timeBetweenEvictionRunsMillis" value="60000" /> <!-- 空闲池中空闲连接能够被激活前的最小空闲时间(毫秒) --> <property name="minEvictableIdleTimeMillis" value="10000" /> </bean> <!-- 定义sqlSessionFactory 并自动注册实体别名 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath*:mapping/*/*.xml" /> <property name="plugins"> <ref bean="pagingInterceptor" /> </property> </bean> <!--配置动态数据源,这儿targetDataSources就是路由数据源所对应的名称--> <bean id="dynamicDataSource" class="com.test.DynamicDataSource"> <!-- 通过key-value关联数据源 --> <property name="targetDataSources"> <map> <entry value-ref="dataSourceWR" key="dbTypeWR"></entry> <entry value-ref="dataSourceRD" key="dbTypeRD"></entry> </map> </property> </bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy"> <property name="targetDataSource"> <ref local="dynamicDataSource"/> </property> </bean>