1、定义读写枚举,注意这个枚举值跟配置文件里面的key值要一样
/** * @author liuqi * @Date 2017-10-24. * @describe 定义枚举类型,读写 */ public enum DynamicDataSourceGlobal { READ, WRITE }
2、自定义一个注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author liuqi * @Date 2017-10-24. * @describe 自定义注解 * 数据源 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DataSource { DynamicDataSourceGlobal value() default DynamicDataSourceGlobal.READ; }3、定义一个数据源key存储和读取类/** * @author liuqi * @Date 2017-10-24. * @describe 当前线程的数据源的key * 利用ThreadLocal 解决数据源设置 线程安全性问题 */ public class DynamicDataSourceHandle { private static final ThreadLocal<DynamicDataSourceGlobal> holder = new ThreadLocal<DynamicDataSourceGlobal>(); public static void putDataSource(DynamicDataSourceGlobal dataSource){ holder.set(dataSource); } public static DynamicDataSourceGlobal getDataSource(){ return holder.get(); } public static void clearDataSource() { holder.remove(); } }4、数据源读取子类
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * @author liuqi * @Date 2017-10-24. * @describe 动态数据源key获取 */ public class DynamicDataSource extends AbstractRoutingDataSource { /** * 获取数据源的key * @return */ @Override protected Object determineCurrentLookupKey() { DynamicDataSourceGlobal dynamicDataSourceGlobal = DynamicDataSourceHandle.getDataSource(); // 如果为空或写,就去写的数据源 if(dynamicDataSourceGlobal == null || dynamicDataSourceGlobal == DynamicDataSourceGlobal.WRITE ) { return DynamicDataSourceGlobal.WRITE.name(); } return DynamicDataSourceGlobal.READ.name(); } }5、定义一个面向切面的处理类
import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author liuqi * @Date 2017-10-24. * @describe 数据源选择切面类 * 数据源key赋值类 */ public class DataSourceAspect { private Logger logger = LoggerFactory.getLogger(getClass()); /** * 在dao层方法之前获取datasource对象之前在切面中指定当前线程数据源路由的key * @param point */ public void before(JoinPoint point) { Object target = point.getTarget(); String method = point.getSignature().getName(); logger.debug("数据源切面类目标对象:{},方法:{}",target,method); Class<?>[] classz = target.getClass().getInterfaces(); Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()) .getMethod().getParameterTypes(); try { Method m = classz[0].getMethod(method, parameterTypes); logger.debug("数据源切面类目标接口方法:{}",m); if (m != null && m.isAnnotationPresent(DataSource.class)) { DataSource data = m.getAnnotation(DataSource.class); logger.debug("数据源切面类--》程序调用数据源:{}",data.value()); DynamicDataSourceHandle.putDataSource(data.value()); }else{ DynamicDataSourceHandle.putDataSource(DynamicDataSourceGlobal.WRITE); logger.debug("数据源切面类--》程序调用默认数据源:{}",DynamicDataSourceGlobal.WRITE); } } catch (Exception e) { logger.debug("数据源切面类--》动态代理获取被代理类异常,详情:",e.getMessage()); throw new RuntimeException("动态获取数据源异常,详情:"+e.getMessage()); } } }
6、数据源配置
<!-- 写数据源配置, 使用 druid 数据库连接池 --> <bean id="writeDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass --> <property name="driverClassName" value="${jdbc.driver}"/> <!-- 基本属性 url、user、password --> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="${jdbc.pool.init}"/> <property name="minIdle" value="${jdbc.pool.minIdle}"/> <property name="maxActive" value="${jdbc.pool.maxActive}"/> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="60000"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="${jdbc.testSql}"/> <property name="testWhileIdle" value="true"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <!-- 配置监控统计拦截的filters --> <property name="proxyFilters"> <list> <ref bean="wall-filter"/> </list> </property> </bean> <!-- 写数据源配置, 使用 druid 数据库连接池 --> <bean id="readDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass --> <property name="driverClassName" value="${jdbc.driver}"/> <!-- 基本属性 url、user、password --> <property name="url" value="${jdbc.read.url}"/> <property name="username" value="${jdbc.read.username}"/> <property name="password" value="${jdbc.read.password}"/> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="${jdbc.pool.init}"/> <property name="minIdle" value="${jdbc.pool.minIdle}"/> <property name="maxActive" value="${jdbc.pool.maxActive}"/> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="60000"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="${jdbc.testSql}"/> <property name="testWhileIdle" value="true"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <!-- 配置监控统计拦截的filters --> <property name="proxyFilters"> <list> <ref bean="wall-filter"/> </list> </property> </bean> <!--重写数据源key方法--> <bean id="dataSource" class="com.xxx.dyndata.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- write --> <entry key="WRITE" value-ref="writeDataSource"/> <!-- read --> <entry key="READ" value-ref="readDataSource"/> </map> </property> <property name="defaultTargetDataSource" ref="readDataSource"/> </bean> <bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter"> <property name="config" ref="wall-config"/> </bean> <bean id="wall-config" class="com.alibaba.druid.wall.WallConfig"> <property name="multiStatementAllow" value="true"/> </bean> <!-- ============================mybatis SqlSession========================================================== --> <bean id="mybatisSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath*:com/xxx/**/**/*Dao.xml"/> </bean> <bean id="mybatisSqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="mybatisSqlSessionFactory"/> </bean> <!-- =============================mybatis SqlSession结束========================================================= --> <!-- 定义事务 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置 Annotation 驱动,扫描@Transactional注解的类定义事务 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- 为业务逻辑层的方法解析@DataSource注解 为当前线程的DynamicDataSourceHandle注入数据源key --> <bean id="dataSourceAspect" class="com.xxx.dyndata.DataSourceAspect" /> <aop:config proxy-target-class="true"> <aop:aspect id="dataSourceAspect" ref="dataSourceAspect" order="1"> <aop:pointcut id="tx" expression="execution(* com.xxx..*Service.*(..)) "/> <aop:before pointcut-ref="tx" method="before" /> </aop:aspect> </aop:config>
7、调用,在接口类添加注解
@DataSource(DynamicDataSourceGlobal.READ) List<TestVo> readList(TestVo vo);
8、事务说明
经测试,service类内部调用自身类的其他方法不会更换数据源,所以不存在分库导致的事务问题。