springboot项目配置多数据源:配置+自定义注解+AOP

第一步.首先在配置文件中,将多数据源配置出来(这里你可以配置多个都无所谓):

##druid数据库连接配置
spring:
	datasource:
	    druid:
	      url: jdbc:mysql://110.40.172.238:3306/guns?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&nullCatalogMeansCurrent=true
	      username: root
	      password: aaaa
	      initial-size: 1
	      max-active: 100
	      min-idle: 10
	      max-wait: 60000
	      pool-prepared-statements: true
	      max-pool-prepared-statement-per-connection-size: 20
	      time-between-eviction-runs-millis: 60000
	      min-evictable-idle-time-millis: 300000
	      #Oracle需要打开注释
	      #validation-query: SELECT 1 FROM DUAL
	      test-while-idle: true
	      test-on-borrow: false
	      test-on-return: false
	      stat-view-servlet:
	        enabled: true
	        url-pattern: /druid/*
	        #login-username: admin
	        #login-password: admin
	      filter:
	        stat:
	          log-slow-sql: true
	          slow-sql-millis: 1000
	          merge-sql: false
	        wall:
	          config:
	            multi-statement-allow: true
##多数据源的配置
dynamic:
  datasource:
    first:
      url: jdbc:mysql://110.40.172.238:3306/guns?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&nullCatalogMeansCurrent=true
      username: root
      password: aaaa
      driver-class-name: com.mysql.cj.jdbc.Driver
    second:
      url: jdbc:mysql://127.0.0.1:3306/guns?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&nullCatalogMeansCurrent=true
      username: root
      password: aaaa
      driver-class-name: com.mysql.cj.jdbc.Driver

第二步,创建多数据源属性实体对象,用于接受配置文件中的数据源信息:

public class DataSourceProperties {
    private String driverClassName;
    private String url;
    private String username;
    private String password;

    /**
     * Druid默认参数
     */
    private int initialSize = 2;
    private int maxActive = 10;
    private int minIdle = -1;
    private long maxWait = 60 * 1000L;
    private long timeBetweenEvictionRunsMillis = 60 * 1000L;
    private long minEvictableIdleTimeMillis = 1000L * 60L * 30L;
    private long maxEvictableIdleTimeMillis = 1000L * 60L * 60L * 7;
    private String validationQuery = "select 1";
    private int validationQueryTimeout = -1;
    private boolean testOnBorrow = false;
    private boolean testOnReturn = false;
    private boolean testWhileIdle = true;
    private boolean poolPreparedStatements = false;
    private int maxOpenPreparedStatements = -1;
    private boolean sharePreparedStatements = false;
    private String filters = "stat,wall";
    public String getDriverClassName() {
        return driverClassName;
    }
    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public int getInitialSize() {
        return initialSize;
    }
    public void setInitialSize(int initialSize) {
        this.initialSize = initialSize;
    }
    public int getMaxActive() {
        return maxActive;
    }
    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
    }
    public int getMinIdle() {
        return minIdle;
    }
    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }
    public long getMaxWait() {
        return maxWait;
    }
    public void setMaxWait(long maxWait) {
        this.maxWait = maxWait;
    }
    public long getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }
    public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }
    public long getMinEvictableIdleTimeMillis() {
        return minEvictableIdleTimeMillis;
    }
    public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }
    public long getMaxEvictableIdleTimeMillis() {
        return maxEvictableIdleTimeMillis;
    }
    public void setMaxEvictableIdleTimeMillis(long maxEvictableIdleTimeMillis) {
        this.maxEvictableIdleTimeMillis = maxEvictableIdleTimeMillis;
    }
    public String getValidationQuery() {
        return validationQuery;
    }
    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }
    public int getValidationQueryTimeout() {
        return validationQueryTimeout;
    }
    public void setValidationQueryTimeout(int validationQueryTimeout) {
        this.validationQueryTimeout = validationQueryTimeout;
    }
    public boolean isTestOnBorrow() {
        return testOnBorrow;
    }
    public void setTestOnBorrow(boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }
    public boolean isTestOnReturn() {
        return testOnReturn;
    }
    public void setTestOnReturn(boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }
    public boolean isTestWhileIdle() {
        return testWhileIdle;
    }
    public void setTestWhileIdle(boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }
    public boolean isPoolPreparedStatements() {
        return poolPreparedStatements;
    }
    public void setPoolPreparedStatements(boolean poolPreparedStatements) {
        this.poolPreparedStatements = poolPreparedStatements;
    }
    public int getMaxOpenPreparedStatements() {
        return maxOpenPreparedStatements;
    }
    public void setMaxOpenPreparedStatements(int maxOpenPreparedStatements) {
        this.maxOpenPreparedStatements = maxOpenPreparedStatements;
    }
    public boolean isSharePreparedStatements() {
        return sharePreparedStatements;
    }
    public void setSharePreparedStatements(boolean sharePreparedStatements) {
        this.sharePreparedStatements = sharePreparedStatements;
    }
    public String getFilters() {
        return filters;
    }
    public void setFilters(String filters) {
        this.filters = filters;
    }
}

再创建一个Druid数据源工厂,作用是将上述数据源属性信息转换为DruidDataSource对象

public class DynamicDataSourceFactory {

    public static DruidDataSource buildDruidDataSource(DataSourceProperties properties) {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(properties.getDriverClassName());
        druidDataSource.setUrl(properties.getUrl());
        druidDataSource.setUsername(properties.getUsername());
        druidDataSource.setPassword(properties.getPassword());

        druidDataSource.setInitialSize(properties.getInitialSize());
        druidDataSource.setMaxActive(properties.getMaxActive());
        druidDataSource.setMinIdle(properties.getMinIdle());
        druidDataSource.setMaxWait(properties.getMaxWait());
        druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis());
        druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis());
        druidDataSource.setMaxEvictableIdleTimeMillis(properties.getMaxEvictableIdleTimeMillis());
        druidDataSource.setValidationQuery(properties.getValidationQuery());
        druidDataSource.setValidationQueryTimeout(properties.getValidationQueryTimeout());
        druidDataSource.setTestOnBorrow(properties.isTestOnBorrow());
        druidDataSource.setTestOnReturn(properties.isTestOnReturn());
        druidDataSource.setPoolPreparedStatements(properties.isPoolPreparedStatements());
        druidDataSource.setMaxOpenPreparedStatements(properties.getMaxOpenPreparedStatements());
        druidDataSource.setSharePreparedStatements(properties.isSharePreparedStatements());

        try {
            druidDataSource.setFilters(properties.getFilters());
            druidDataSource.init();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return druidDataSource;
    }
}

第三步.创建一个数据源管理对象,这里是可以切换当前线程的数据源 (ThreadLocal),后面的AOP就会通过自定义注解所表示的数据源调用这个API对当前线程的数据源进行切换。代码如下:

public class DynamicDataSourceHolder {
	
    private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<String>();

    /**
     * 设置线程持有的DataSource, 底层以map形式呈现, key为当前线程
     *
     * @param dataSource
     */
    public static void setDataSource(String dataSource) {
        THREAD_LOCAL.set(dataSource);
    }
    /**
     * 获取线程持有的当前数据源
     *
     * @return
     */
    public static String getDataSource() {
        return THREAD_LOCAL.get();
    }
    /**
     * 清除数据源
     */
    public static void clear() {
        THREAD_LOCAL.remove();
    }
}

第四步(核心).创建一个自定义的动态数据源对象,并且继承基类 AbstractRoutingDataSource,重写determineCurrentLookupKey方法,将第三步中的数据源返回给该对象(因为第三步是通过数据源的KEY来指定使用哪个数据源,第四步是将这个KEY返回给动态数据源管理对象,动态数据源管理对象会根据这个KEY找到我们的数据源,如果找不到就使用默认的数据源)

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDataSource();
    }
}

第五步.先创建一个读取多数据源配置信息的对象,这里要注意的是@ConfigurationProperties 这个注解,项目中要添加一个依赖:

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-configuration-processor</artifactId>
		<optional>true</optional>
	</dependency>

有了@ConfigurationProperties(prefix = “dynamic”),setDatasource方法会将前缀为“dynamic”的配置信息以key,value的方式读取到Map中,其中map的key就是每个数据源的名称,value是我们自定义的数据源属性的接收对象

@ConfigurationProperties(prefix = "dynamic")
public class DynamicDataSourceProperties {
    private Map<String, DataSourceProperties> datasource = new LinkedHashMap<>();

    public Map<String, DataSourceProperties> getDatasource() {
        return datasource;
    }
    
    public void setDatasource(Map<String, DataSourceProperties> datasource) {
        this.datasource = datasource;
    }
}

接下来就可以创建多数据源的配置对象了 注意的是需要@EnableConfigurationProperties(DynamicDataSourceProperties.class)
这个注解:开启DynamicDataSourceProperties对象读取配置文件的权限,否则上面的@ConfigurationProperties注解无法使用

/**
 * 配置多数据源
 *
 */
@Configuration
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
public class DynamicDataSourceConfig {
    @Autowired
    private DynamicDataSourceProperties properties;
	
	 /**
     * 读取配置文件中的默认数据源配置 即spring.datasource.druid下的数据库连接配置
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 获取动态数据源
     * @return
     */
    @Bean(name = "dynamicDataSource")
    public javax.sql.DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 获取默认数据源 (spring.datasource.druid的配置)
        DataSourceProperties dataSourceProperties = dataSourceProperties();
        //设置默认数据源 (将自定义的数据源你对象通过DynamicDataSourceFactory转换为druid数据源对象)
        dynamicDataSource.setDefaultTargetDataSource(DynamicDataSourceFactory.buildDruidDataSource(dataSourceProperties));
        // 配置多数据源,
        // 添加数据源标识和DataSource引用到目标源映射
        dynamicDataSource.setTargetDataSources(getDynamicDataSource());
        return dynamicDataSource;
    }
	
    private Map<Object, Object> getDynamicDataSource(){
        Map<String, DataSourceProperties> dataSourcePropertiesMap = properties.getDatasource();
        Map<Object, Object> targetDataSources = new HashMap<>(dataSourcePropertiesMap.size());
        dataSourcePropertiesMap.forEach((k, v) -> {
            DruidDataSource druidDataSource = DynamicDataSourceFactory.buildDruidDataSource(v);
            targetDataSources.put(k, druidDataSource);
        });
        return targetDataSources;
    }

    /**
     * 将数据源加入到事务管理中
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}    

以上的所有配置完成,项目启动后就可以将配置文件中的多个数据源信息存入动态数据源管理中
接下来就可以创建一个自定义注解:

@Documented //文档记录
@Retention(RetentionPolicy.RUNTIME) //生命周期
@Target({ElementType.TYPE,ElementType.METHOD}) //作用范围
@Inherited //是否可继承
public @interface MeDataSource {
    String value() default "";
}

再创建一个AOP 去拦截上面的注解
记得添加AOP依赖

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
	</dependency>
@Aspect
@Component
@Order(1)//优先执行此aop
@Slf4j
public class DataSourceAop {
    private final String DEFAULT_DATA_SOURCE = "first";
	
    @Before(value = "@annotation(com.example.picture.datasource.MeDataSource)")//你的自定义注解路径
    public void before(JoinPoint joinPoint){
        //获取到执行的方法
        Object target = joinPoint.getTarget();
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        // 执行方法名
        String methodName = methodSignature.getName();
        // 方法参数
        Class[] parameterTypes = methodSignature.getParameterTypes();
        try {
            // 获取方法, 直接getClass获取对象可能为代理对象
            Method method = target.getClass().getMethod(methodName, parameterTypes);
            // 添加默认数据源
            String dataSource = DEFAULT_DATA_SOURCE;
            if (null != method && method.isAnnotationPresent(MeDataSource.class)) {
                MeDataSource targetDataSource = method.getAnnotation(MeDataSource.class);
                dataSource = targetDataSource.value();
            }
            // 此处添加线程对应的数据源到上下文
            // 在AbstractRoutingDataSource子类中拿到数据源, 加载后进行配置
            DynamicDataSourceHolder.setDataSource(dataSource);
            log.info("generate data source : " + dataSource);
        } catch (Exception e) {
            log.info("error", e);
        }
    }

    /**
     * 清除数据源, 方法执行完成后, 清除数据源
     */
    @After("@annotation(com.example.picture.datasource.MeDataSource)")
    public void after(JoinPoint joinPoint) {
        DynamicDataSourceHolder.clear();
    }
}

DynamicDataSourceHolder.setDataSource(dataSource); 这个操作就是获取注解中的数据源标识传入数据源管理器中,管理器通过这个标识调用动态数据源对象获取对应的数据源信息

到此就可以启动项目进行测试了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值