本文实现案例场景:
某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库。
为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在spring boot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便。
配置文件配置内容为:
(不包括项目中的其他配置,这里只是数据源相关的)
# 主数据源,默认的
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456# 更多数据源
custom.datasource.names=ds1,ds2
custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1
custom.datasource.ds1.username=root
custom.datasource.ds1.password=123456custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2
custom.datasource.ds2.username=root
custom.datasource.ds2.password=123456
注册数据源 :
- /**
- * 自定义注解
- * @author meng
- *
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target({
- ElementType.METHOD
- })
- public @interface DS {
- DataSourceType value() default DataSourceContextHolder.DataSourceType.base;
- }
- /**
- *初始化主数据源
- * @author meng
- *
- */
- @Configuration
- public class DataSourceConfiguration {
- @Bean(name = "base")
- @Primary
- @ConfigurationProperties(prefix = "spring.datasource")
- public DataSource dataSource1() {
- return DataSourceBuilder.create().build();
- }
- @Bean(name = "jdbc2")
- @ConfigurationProperties(prefix = "custom.datasource.ds1")
- public DataSource dataSource2() {
- return DataSourceBuilder.create().build();
- }
- @Bean(name = "hive")
- @ConfigurationProperties(prefix = "custom.datasource.ds2")
- public DataSource dataSource2hive() {
- return DataSourceBuilder.create().build();
- }
- }
创建一个线程保存当前线程使用的Database标识:
- /**
- * 保存一个线程安全的DataBaseType容器
- * @author meng
- *
- */
- public class DataSourceContextHolder {
- /** 使用枚举标识数据源 */
- public enum DataSourceType{
- base,jdbc2,hive
- }
- /** 创建线程保存数据源 */
- private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<DataSourceType>();
- /**
- * 设置数据源
- * @param dbType
- */
- public static void setDataSourceType(DataSourceType dataSourceType){
- // 如果数据源为空,抛异常
- if(dataSourceType == null)throw new NullPointerException();
- contextHolder.set(dataSourceType);
- }
- /**
- * 获得数据源
- * @return
- */
- public static DataSourceType getDataSourceType(){
- return contextHolder.get()==null? DataSourceType.base:contextHolder.get();
- }
- /**
- * 清空数据源
- */
- public static void clearDataSourceType(){
- contextHolder.remove();
- }
- }
获取当前线程的Database标识
- /**
- * 动态获取数据源(需要继承AbstractRoutingDataSource)
- * @author meng
- *
- */
- public class DynamicDataSource extends AbstractRoutingDataSource{
- @Override
- protected Object determineCurrentLookupKey() {
- // 使用DatabaseContextHolder获取当前线程的DatabaseType
- return DataSourceContextHolder.getDataSourceType();
- }
- }
使用aop动态切换数据源:
- /**
- * 使用aop动态切换数据源
- * @author meng
- *
- */
- @Aspect
- @Component
- public class DynamicDataSourceAspect{
- public static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
- @SuppressWarnings("rawtypes")
- @Before("@annotation(DS)")
- public void beforeSwitchDS(JoinPoint point){
- //获得当前访问的class
- Class<?> className = point.getTarget().getClass();
- //获得访问的方法名
- String methodName = point.getSignature().getName();
- MethodSignature methodSignature = (MethodSignature) point.getSignature();
- //得到方法的参数的类型
- Class[] argClass = methodSignature.getParameterTypes();
- DataSourceType dataSource = DataSourceContextHolder.DataSourceType.base;
- try {
- // 得到访问的方法对象
- Method method = className.getMethod(methodName, argClass);
- // 判断是否存在@DS注解
- if (method.isAnnotationPresent(DS.class)) {
- DS annotation = method.getAnnotation(DS.class);
- // 取出注解中的数据源名
- dataSource = annotation.value();
- }
- } catch (Exception e) {
- e.printStackTrace();
- logger.error("数据源切换异常",e.getMessage());
- }
- // 切换数据源
- DataSourceContextHolder.setDataSourceType(dataSource);
- logger.info("切换到数据源:"+dataSource);
- }
- /**
- * 清空数据源
- * @param point
- */
- @After("@annotation(DS)")
- public void afterSwitchDS(JoinPoint point){
- DataSourceContextHolder.clearDataSourceType();
- logger.info("执行完成,数据源清空");
- }
- }
动态切换数据源并注册事务:
- /**
- * 数据源切换
- * @author meng
- *
- */
- @Configuration
- public class MybatisConfiguration {
- /** 注入数据源 */
- @Resource(name = "base")
- private DataSource base;
- @Resource(name = "jdbc2")
- private DataSource jdbc2;
- @Resource(name = "hive")
- private DataSource hive;
- /**
- * 将动态数据源添加到SqlSessionFactory中
- * @return
- * @throws Exception
- */
- @Bean(name="sqlSessionFactoryBean")
- public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSouceProxy")
- DataSource dataSource) throws Exception {
- SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
- sqlSessionFactoryBean.setDataSource(dataSource);
- sqlSessionFactoryBean.setTypeAliasesPackage(Constants.TYPE_ALIASES_PACKAGE);
- // 添加XML目录
- ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
- org.springframework.core.io.Resource[] resources = resolver.getResources(Constants.MAPPER_XML_LOCATION);
- org.springframework.core.io.Resource[] re = resolver.getResources(Constants.MAPPER_XML_LOCATION_OTHER);
- sqlSessionFactoryBean.setMapperLocations(resources);
- sqlSessionFactoryBean.setMapperLocations(re);
- // 分页插件
- PageHelper pageHelper = new PageHelper();
- Properties properties = new Properties();
- // reasonable:分页合理化参数,默认值为false。
- // 当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
- properties.setProperty("reasonable", "true");
- // supportMethodsArguments:默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。
- properties.setProperty("supportMethodsArguments", "true");
- properties.setProperty("returnPageInfo", "check");
- // params:用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值,
- // 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
- properties.setProperty("params", "count=countSql");
- pageHelper.setProperties(properties);
- sqlSessionFactoryBean.setPlugins(new Interceptor[]{pageHelper});
- return sqlSessionFactoryBean.getObject();
- }
- @Bean(name="dataSouceProxy")
- public AbstractRoutingDataSource dataSouceProxy(){
- // 动态数据源对象
- DynamicDataSource proxy = new DynamicDataSource();
- Map<Object,Object> targetDataResources = new HashMap<>();
- targetDataResources.put(DataSourceContextHolder.DataSourceType.base,base);
- targetDataResources.put(DataSourceContextHolder.DataSourceType.jdbc2,jdbc2);
- targetDataResources.put(DataSourceContextHolder.DataSourceType.hive,hive);
- // 默认数据源
- proxy.setDefaultTargetDataSource(base);
- proxy.setTargetDataSources(targetDataResources);
- return proxy;
- }
- @Bean
- public PlatformTransactionManager baseTransactionManager(
- @Qualifier("dataSouceProxy") DataSource dataSource) {
- return new DataSourceTransactionManager(dataSource);
- }
- }