本文实现案例场景:
某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库。

为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在spring boot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便。

一配置二使用
1. 启动类注册动态数据源
2. 配置文件中配置多个数据源
3. 在需要的方法上使用注解指定数据源

1、在启动类添加 @Import({DynamicDataSourceRegister.class, MProxyTransactionManagementConfiguration.class})

@SpringBootApplication
@Import({DynamicDataSourceRegister.class}) // 注册动态多数据源
public class SpringBootSampleApplication {
<span class="hljs-comment">// 省略其他代码</span>

}

2、配置文件配置内容为:
(不包括项目中的其他配置,这里只是数据源相关的)

# 主数据源,默认的

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=123456
custom.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

3、使用方法

package org.springboot.sample.service;
import java.sql.ResultSet;


import java.sql.SQLException;


import java.util.List;
import org.springboot.sample.datasource.TargetDataSource;


import org.springboot.sample.entity.Student;


import org.springboot.sample.mapper.StudentMapper;


import org.springframework.beans.factory.annotation.Autowired;


import org.springframework.jdbc.core.JdbcTemplate;


import org.springframework.jdbc.core.RowMapper;


import org.springframework.stereotype.Service;
/**
  • Student Service
  • @author 单红宇(365384722)
  • @myblog http://blog.csdn.net/catoop/
  • @create 2016年1月12日


    */


    @Service


    public class StudentService {
    @Autowired


    private JdbcTemplate jdbcTemplate;
    // MyBatis的Mapper方法定义接口


    @Autowired


    private StudentMapper studentMapper;
    @TargetDataSource(name="ds2")


    public List<Student> likeName(String name){


    return studentMapper.likeName(name);


    } public List<Student> likeNameByDefaultDataSource(String name){


    return studentMapper.likeName(name);


    }
    /**
    • 不指定数据源使用默认数据源
    • @return
    • @author SHANHY
    • @create 2016年1月24日


      */


      public List<Student> getList(){


      String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT";


      return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){
       <span class="hljs-meta">@Override</span></br>
      <span class="hljs-function"><span class="hljs-keyword">public</span> Student <span class="hljs-title">mapRow</span><span class="hljs-params">(ResultSet rs, <span class="hljs-keyword">int</span> rowNum)</span> <span class="hljs-keyword">throws</span> SQLException </span>{</br>
      Student stu = <span class="hljs-keyword">new</span> Student();</br>
      stu.setId(rs.getInt(<span class="hljs-string">"ID"</span>));</br>
      stu.setAge(rs.getInt(<span class="hljs-string">"AGE"</span>));</br>
      stu.setName(rs.getString(<span class="hljs-string">"NAME"</span>));</br>
      stu.setSumScore(rs.getString(<span class="hljs-string">"SCORE_SUM"</span>));</br>
      stu.setAvgScore(rs.getString(<span class="hljs-string">"SCORE_AVG"</span>));</br>
      <span class="hljs-keyword">return</span> stu;</br>
      }

      });

      }


    /**

    • 指定数据源

    • @return

    • @author SHANHY

    • @create 2016年1月24日


      */

      @TargetDataSource(name="ds1")


      public List<Student> getListByDs1(){


      String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT";


      return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){

       <span class="hljs-meta">@Override</span></br>
      <span class="hljs-function"><span class="hljs-keyword">public</span> Student <span class="hljs-title">mapRow</span><span class="hljs-params">(ResultSet rs, <span class="hljs-keyword">int</span> rowNum)</span> <span class="hljs-keyword">throws</span> SQLException </span>{</br>
      Student stu = <span class="hljs-keyword">new</span> Student();</br>
      stu.setId(rs.getInt(<span class="hljs-string">"ID"</span>));</br>
      stu.setAge(rs.getInt(<span class="hljs-string">"AGE"</span>));</br>
      stu.setName(rs.getString(<span class="hljs-string">"NAME"</span>));</br>
      stu.setSumScore(rs.getString(<span class="hljs-string">"SCORE_SUM"</span>));</br>
      stu.setAvgScore(rs.getString(<span class="hljs-string">"SCORE_AVG"</span>));</br>
      <span class="hljs-keyword">return</span> stu;</br>
      }

      });

      }


    /**

    • 指定数据源

    • @return

    • @author SHANHY

    • @create 2016年1月24日


      */


      @TargetDataSource(name="ds2")


      public List<Student> getListByDs2(){


      String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT";


      return (List<Student>) jdbcTemplate.query(sql, new RowMapper<Student>(){

       <span class="hljs-meta">@Override</span></br>
      <span class="hljs-function"><span class="hljs-keyword">public</span> Student <span class="hljs-title">mapRow</span><span class="hljs-params">(ResultSet rs, <span class="hljs-keyword">int</span> rowNum)</span> <span class="hljs-keyword">throws</span> SQLException </span>{</br>
      Student stu = <span class="hljs-keyword">new</span> Student();</br>
      stu.setId(rs.getInt(<span class="hljs-string">"ID"</span>));</br>
      stu.setAge(rs.getInt(<span class="hljs-string">"AGE"</span>));</br>
      stu.setName(rs.getString(<span class="hljs-string">"NAME"</span>));</br>
      stu.setSumScore(rs.getString(<span class="hljs-string">"SCORE_SUM"</span>));</br>
      stu.setAvgScore(rs.getString(<span class="hljs-string">"SCORE_AVG"</span>));</br>
      <span class="hljs-keyword">return</span> stu;</br>
      }

      });

      }

      }

要注意的是,在使用MyBatis时,注解@TargetDataSource 不能直接在接口类Mapper上使用。
按上面的代码中StudentMapper为接口,代码如下:

package org.springboot.sample.mapper;
import java.util.List;
import org.springboot.sample.entity.Student;
/**
  • StudentMapper,映射SQL语句的接口,无逻辑实现
  • @author 单红宇(365384722)
  • @myblog http://blog.csdn.net/catoop/
  • @create 2016年1月20日


    */

    public interface StudentMapper {
    // 注解 @TargetDataSource 不可以在这里使用


    List<Student> likeName(String name);
    Student getById(int id);
    String getNameById(int id);
}



请将下面几个类放到Spring Boot项目中。
DynamicDataSource.java
DynamicDataSourceAspect.java
DynamicDataSourceContextHolder.java
DynamicDataSourceRegister.java
TargetDataSource.java
MProxyTransactionManagementConfiguration.java
MTransactionInterceptor.java

package org.springboot.sample.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
  • 动态数据源
  • @author 单红宇(365384722)
  • @myblog http://blog.csdn.net/catoop/
  • @create 2016年1月22日

    */

    public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override

    protected Object determineCurrentLookupKey() {


    return DynamicDataSourceContextHolder.getDataSourceType();


    }
}

package org.springboot.sample.datasource;
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.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Component;

/**
* 切换数据源Advice

*
* @author 单红宇(365384722)

* @myblog http://blog.csdn.net/catoop/

* @create 2016年1月23日

*/
@Aspect

@Order(-1)// 保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(ds)")
public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable {

String dsId = ds.name();

if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {

logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());

} else {

logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature());

DynamicDataSourceContextHolder.setDataSourceType(ds.name());

}
} @After("@annotation(ds)")

public void restoreDataSource(JoinPoint point, TargetDataSource ds) {

logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());

DynamicDataSourceContextHolder.clearDataSourceType();

} }
package org.springboot.sample.datasource;
import java.util.ArrayList;

import java.util.List;
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

public static List<String> dataSourceIds = new ArrayList<>();


public static void setDataSourceType(String dataSourceType) {

contextHolder.set(dataSourceType);

}
public static String getDataSourceType() {

return contextHolder.get();

} public static void clearDataSourceType() {

contextHolder.remove();

} /**
* 判断指定DataSrouce当前是否存在

*
* @param dataSourceId

* @return

* @author SHANHY

* @create 2016年1月24日

*/
public static boolean containsDataSource(String dataSourceId){

return dataSourceIds.contains(dataSourceId);

}
}
package org.springboot.sample.datasource;
import java.util.HashMap;

import java.util.Map;
import javax.sql.DataSource;
import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.MutablePropertyValues;

import org.springframework.beans.PropertyValues;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;

import org.springframework.beans.factory.support.GenericBeanDefinition;

import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;

import org.springframework.boot.bind.RelaxedDataBinder;

import org.springframework.boot.bind.RelaxedPropertyResolver;

import org.springframework.context.EnvironmentAware;

import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;

import org.springframework.core.convert.ConversionService;

import org.springframework.core.convert.support.DefaultConversionService;

import org.springframework.core.env.Environment;

import org.springframework.core.type.AnnotationMetadata;
/**
* 动态数据源注册<br/>

* 启动动态数据源请在启动类中(如SpringBootSampleApplication)

* 添加 @Import(DynamicDataSourceRegister.class)

*
* @author 单红宇(365384722)

* @myblog http://blog.csdn.net/catoop/

* @create 2016年1月24日

*/
public class DynamicDataSourceRegister

implements ImportBeanDefinitionRegistrar, EnvironmentAware {


private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
private ConversionService conversionService = new DefaultConversionService();

private PropertyValues dataSourcePropertyValues;
// 如配置文件中未指定数据源类型,使用该默认值

private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
// private static final Object DATASOURCE_TYPE_DEFAULT =

// "com.zaxxer.hikari.HikariDataSource";
// 数据源

private DataSource defaultDataSource;

private Map<String, DataSource> customDataSources = new HashMap<>();
@Override

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
// 将主数据源添加到更多数据源中

targetDataSources.put("dataSource", defaultDataSource);

DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");

// 添加更多数据源

targetDataSources.putAll(customDataSources);

for (String key : customDataSources.keySet()) {

DynamicDataSourceContextHolder.dataSourceIds.add(key);

} // 创建DynamicDataSource

GenericBeanDefinition beanDefinition = new GenericBeanDefinition();

beanDefinition.setBeanClass(DynamicDataSource.class);

beanDefinition.setSynthetic(true);

MutablePropertyValues mpv = beanDefinition.getPropertyValues();

mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);

mpv.addPropertyValue("targetDataSources", targetDataSources);

registry.registerBeanDefinition("dataSource", beanDefinition);
logger.info("Dynamic DataSource Registry");

} /**
* 创建DataSource

*
* @param type

* @param driverClassName

* @param url

* @param username

* @param password

* @return

* @author SHANHY

* @create 2016年1月24日

*/
@SuppressWarnings("unchecked")

public DataSource buildDataSource(Map<String, Object> dsMap) {

try {

Object type = dsMap.get("type");

if (type == null)

type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
Class<? extends DataSource> dataSourceType;

dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
String driverClassName = dsMap.get("driver-class-name").toString();

String url = dsMap.get("url").toString();

String username = dsMap.get("username").toString();

String password = dsMap.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)

.username(username).password(password).type(dataSourceType);

return factory.build();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

return null;

} /**

* 加载多数据源配置
*/
@Override
public void setEnvironment(Environment env) {

initDefaultDataSource(env);

initCustomDataSources(env);

} /**
* 初始化主数据源
*
* @author SHANHY
* @create 2016年1月24日
*/
private void initDefaultDataSource(Environment env) {

// 读取主数据源

RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");

Map<String, Object> dsMap = new HashMap<>();

dsMap.put("type", propertyResolver.getProperty("type"));

dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name"));

dsMap.put("url", propertyResolver.getProperty("url"));

dsMap.put("username", propertyResolver.getProperty("username"));

dsMap.put("password", propertyResolver.getProperty("password"));
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource, env);

} /**
* 为DataSource绑定更多数据

*
* @param dataSource

* @param env

* @author SHANHY

* @create 2016年1月25日

*/

private void dataBinder(DataSource dataSource, Environment env){

RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);

//dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext));

dataBinder.setConversionService(conversionService);

dataBinder.setIgnoreNestedProperties(false);//false

dataBinder.setIgnoreInvalidFields(false);//false

dataBinder.setIgnoreUnknownFields(true);//true

if(dataSourcePropertyValues == null){

Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");

Map<String, Object> values = new HashMap<>(rpr);

// 排除已经设置的属性

values.remove("type");

values.remove("driver-class-name");

values.remove("url");

values.remove("username");

values.remove("password");

dataSourcePropertyValues = new MutablePropertyValues(values);

}

dataBinder.bind(dataSourcePropertyValues);

} /**
* 初始化更多数据源
*
* @author SHANHY
* @create 2016年1月24日
*/
private void initCustomDataSources(Environment env) {

// 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源

RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");

String dsPrefixs = propertyResolver.getProperty("names");

for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源

Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");

DataSource ds = buildDataSource(dsMap);

customDataSources.put(dsPrefix, ds);

dataBinder(ds, env);

}

}
}
package org.springboot.sample.datasource;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 在方法上使用,用于指定使用哪个数据源
*
* @author 单红宇(365384722)

* @myblog http://blog.csdn.net/catoop/

* @create 2016年1月23日
*/
@Target({ ElementType.METHOD, ElementType.TYPE })

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface TargetDataSource {

String name();

}

本文代码博主是经过测试后没有问题才发出来共享给大家的。对于连接池参数配置会应用到所有数据源上。
比如配置一个:

spring.datasource.maximum-pool-size=80

那么我们所有的数据源都会自动应用上。

<!-- 登录查看 begin -->
<!-- 登录查看 end -->

Spring Boot 动态数据源(Spring 注解数据源)的更多相关文章

  1. 43. Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

    [视频&交流平台] àSpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008&utm ...

  2. Spring Boot HikariCP 一 ——集成多数据源

    其实这里介绍的东西主要是参考的另外一篇文章,数据库读写分离的. 参考文章就把链接贴出来,里面有那位的代码,简单明了https://gitee.com/comven/dynamic-datasource ...

  3. spring boot + druid + mybatis + atomikos 多数据源配置 并支持分布式事务

    文章目录 一.综述 1.1 项目说明 1.2 项目结构 二.配置多数据源并支持分布式事务 2.1 导入基本依赖 2.2 在yml中配置多数据源信息 2.3 进行多数据源的配置 三.整合结果测试 3.1 ...

  4. Spring boot配置多个Redis数据源操作实例

    原文:https://www.jianshu.com/p/c79b65b253fa Spring boot配置多个Redis数据源操作实例 在SpringBoot是项目中整合了两个Redis的操作实例 ...

  5. Spring Boot 2.x Redis多数据源配置(jedis,lettuce)

    Spring Boot 2.x Redis多数据源配置(jedis,lettuce) 96 不敢预言的预言家 0.1 2018.11.13 14:22* 字数 65 阅读 727评论 0喜欢 2 多数 ...

  6. 14、Spring Boot 2.x 集成 Druid 数据源

    14.Spring Boot 2.x 集成 Druid 数据源 完整源码: Spring-Boot-Demos

  7. Spring Boot的27个注解【核心】

    导读[约定大于配置] Spring Boot方式的项目开发已经逐步成为Java应用开发领域的主流框架,它不仅可以方便地创建生产级的Spring应用程序,还能轻松地通过一些注解配置与目前比较火热的微服务 ...

  8. Spring Boot 实战 —— MyBatis(注解版)使用方法

    原文链接: Spring Boot 实战 -- MyBatis(注解版)使用方法 简介 MyBatis 官网 是这么介绍它自己的: MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过 ...

  9. Spring Boot动态注入删除bean

    Spring Boot动态注入删除bean 概述 因为如果采用配置文件或者注解,我们要加入对象的话,还要重启服务,如果我们想要避免这一情况就得采用动态处理bean,包括:动态注入,动态删除. 动态注入 ...

  10. Spring Boot整合MyBatis(非注解版)

    Spring Boot整合MyBatis(非注解版),开发时采用的时IDEA,JDK1.8 直接上图: 文件夹不存在,创建一个新的路径文件夹 创建完成目录结构如下: 本人第一步习惯先把需要的包结构创建 ...

随机推荐

  1. 利用HTTP代理录制Jmeter脚本

    1 測试计划中加入一个线程组1 2在"工作台"-非測试元件-加入"HTTP代理server" port: 代理server的port,默认8080,可自行改动, ...

  2. 深入浅出WPF 第一部分(3)

    3.2.3 属性元素 <Grid HorizontalAlignment="Center" VerticalAlignment="Center"> ...

  3. iOS使用push隐藏子页面底部bottom TabBar

    下面两种情况是我在开发过程中遇到的,一种是代码使用pushViewController,还有一种是storyboard直接使用push.之前也查阅了非常多关于隐藏底部tabbar的资料.可是要么使用起 ...

  4. android 图片特效处理之光晕效果

    这篇将讲到图片特效处理的图片光晕效果.跟前面一样是对像素点进行处理,本篇实现的思路可参见android图像处理系列之九--图片特效处理之二-模糊效果和android图像处理系列之十三--图片特效处理之 ...

  5. java接口理解(转载)

    今天和同事好好的讨论了java接口的原理和作用,发现原来自己的对接口的理解仅仅是局限在概念的高度抽象上,觉得好像理解了但是不会变化应用其实和没有理解差不多.以前看一个帖子说学习一个东西不管什么时候都要 ...

  6. Codefroces Educational Round 27 845G Shortest Path Problem?

    Shortest Path Problem? You are given an undirected graph with weighted edges. The length of some pat ...

  7. 18/9/22NOIP模拟考

    18/9/22NOIP模拟考 其实本来是有多组数据的,出题人忘记在题面上加了   斜眼笑 期望得分:100:实际得分:100 由于种种原因,拿到题的时候已经过去了0.5h+... 然后因为这道题数据范 ...

  8. QTemporaryDir及QTemporaryFile建立临时目录及文件夹(创建一个随机名称的目录或文件,两者均能保证不会覆盖已有文件)

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址:本文标题:QTemporaryDir及QTemporaryFile建立临时目录及文件夹     本文地址: ...

  9. Android学习笔记技巧之给文本加边框

    BorderTextViews.Java package xiaosi.BorderTextView; import android.content.Context; import android.g ...

  10. 基于WebSphere与Domino的电子商务网站构架分析

    650) this.width=650;" border="0" alt="174812596.jpg" src="http://img1. ...