datagridview绑定数据源不显示_SpringBoot+mybatis+Druid 实现运行时数据源的动态创建管理...

我这里是为了实现一个业务场景。Spring如何配置多数据源并整合Druid的方式网上已经一大片了。

但是那种方式适应场景是采用主从模式的数据库。

(做法)我们需要事先在配置文件配置好服务需要用到的多个数据源信息,并创建自定义注解,这里注解一般声明在Service层的方法上较为合理。然后不同的Service根据注解的value属性区分方法所用到的数据源

这里,我实现的是在项目运行时,根据参数传递或其他途径获取到数据库连接配置信息,进行动态的连接创建,切换,和销毁

pom依赖:

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

Druid 数据源配置:

server:
  port: 8080

spring:
  profiles:
    active: dev
  datasource:
    druid:
      name: druid-default-datasource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/qb?characterEncoding=utf8&serverTimezone=GMT%2B8
      username: root
      password: root
      initial-size: 5
      max-active: 30
      min-idle: 5
      filter:
        stat:
          enabled: true
          log-slow-sql: true
          merge-sql: true
          slow-sql-millis: 30000 #单位毫秒
          connection-stack-trace-enable: true
        config:
          enabled: true
        slf4j:
          enabled: true
      query-timeout: 1800
      max-wait: 60000
      validation-query: select 1 from dual
      pool-prepared-statements: true #mysql5.5以上或者oracle建议开启
      testWhileIdle: true
      filters: stat,wall,config,slf4j
      break-after-acquire-failure: true
      time-between-eviction-runs-millis: 300000
      connection-error-retry-attempts: 3
      stat-view-servlet:
        enabled: true #默认为true,可以不显示设置
        url-pattern: /druid/*
        reset-enable: false
        login-username: admin
        login-password: 123
        deny: 192.168.0.191
      web-stat-filter:
        enabled: true #默认为true,可以不显示设置
        url-pattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
        session-stat-enable: 'true'
        profile-enable: 'true'
        #-1标识采用数据库默认事务隔离级别
      default-transaction-isolation: 2
      aop-patterns: com.landa.service.*
  aop:
    proxy-target-class: true
    #proxy-target-class属性值决定是基于接口的还是基于类的代理被创建。如果proxy-target-class 属性值被设置为true,那么基于类的代理将起作用(这时需要cglib库)。如果proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理将起作用。
    #即使你未声明 proxy-target-class="true" ,但运行类没有继承接口,spring也会自动使用CGLIB代理。
    #高版本spring自动根据运行类选择 JDK 或 CGLIB 代理


#mybatis配置
mybatis:
  configuration:
    lazy-loading-enabled: true
    aggressive-lazy-loading: true
    cache-enabled: true
    call-setters-on-nulls: true
    jdbc-type-for-null: NULL
  mapper-locations: class:mybatis/mapper/**/*.xml
  
  
#PageHelper分页插件配置
pagehelper:
  helper-dialect: mysql
  reasonable: 'true'
  # 用于控制默认不带 count 查询的方法中,是否执行 count 查询,这里设置为true后,total会为-1
  auto-dialect: 'true'
  page-size-zero: 'true'
  default-count: 'false'
  close-conn: 'true'
  auto-runtime-dialect: 'false'
  offset-as-page-num: 'false'
  row-bounds-with-count: 'true'

上面是我写demo时yml的完整配置。
yml中的框架插件配置都可以用java配置来替代的,这里为了直观便将所有配置放在一起方便大家理解。

这时候,基础的东西已经配置好了

声明自定义注解:

package com.landa.annotation;

import java.lang.annotation.*;

/**
 * @Author: save
 * @Date: 2020/5/27 10:16
 * @Version: 1.0
 **/
@Documented
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
}

数据源初始化配置

package com.landa.common;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;


/**
 * @Author: save
 * @Date: 2020/5/27 12:02
 */
@Configuration
public class DynamicDataSourceConfig {

    /**
     * @Author: save
     * @Description: 构建默认数据源
     * @Date: 2020/5/27 14:22
     * @return: javax.sql.DataSource
     **/
    @Bean(name = "defaultDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource defaultDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 核心动态数据源
     * @return 数据源实例
     */
    @Bean
    public DataSource dynamicDataSource() {
        DynamicDataSourceHandler dataSource = new DynamicDataSourceHandler();
        //dataSource.setDefaultTargetDataSource(defaultDataSource());
        dataSource.setTargetDataSources(new HashMap<>());
        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        //此处设置为了解决找不到mapper文件的问题
        //sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().
         //       getResources("classpath:mybatis/mapper/**/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    /**
     * 事务管理
     * @return 事务管理实例
     */
    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

创建动态数据源管理类(继承AbstractRoutingDataSource数据源扩展类)

package com.landa.common;

import com.landa.util.SpringUtil;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;

/**
 * 继承数据源扩展类 
 * @author Administrator
 *
 */
public class DynamicDataSourceHandler extends AbstractRoutingDataSource {
	//初始化主数据源到ThreadLocal中
    private static final ThreadLocal<DataSource> DATASOURCE = ThreadLocal.withInitial(() -> (DataSource) SpringUtil.getBean("defaultDataSource"));

    @Override
    protected Object determineCurrentLookupKey() {
        return null;
    }

    @Override
    protected DataSource determineTargetDataSource() {
        return getDataSource();
    }


    public static DataSource getDataSource() {
        return DynamicDataSourceHandler.DATASOURCE.get();
    }

    public static void setDataSource(DataSource dataSource) {
        DynamicDataSourceHandler.DATASOURCE.set(dataSource);
    }

    public static void clear() {
        DynamicDataSourceHandler.DATASOURCE.remove();
    }
}

自定义切面类

package com.landa.aspect;

import com.landa.annotation.DataSource;
import com.landa.common.DynamicDataSourceHandler;
import com.landa.util.DruidUtil;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * @Author: save
 * @Date: 2020/5/27 10:19
 * @Version: 1.0
 **/
@Aspect
@Order(-1)
@Component
public class DataSourceAspect {
    /**
     * @Aspect:作用是把当前类标识为一个切面供容器读取
     * @Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
     * @Around:环绕增强,相当于MethodInterceptor
     * @AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
     * @Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
     * @AfterThrowing:异常抛出增强,相当于ThrowsAdvice
     * @After: final增强,不管是抛出异常或者正常退出都会执行
     **/

    @Autowired
    private DruidUtil druidUtil;

    @Before("execution(* com.landa.service.*.*(..))")
    public void beforeServiceMethodExecute(JoinPoint point){
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method targetMethod = methodSignature.getMethod();
        Parameter[] parameters= targetMethod.getParameters();
        Object[] args=point.getArgs();
        if(null==parameters||parameters.length==0){
            return;
        }
        for (int i = 0; i < parameters.length; i++) {
            if(parameters[i].isAnnotationPresent(DataSource.class)){
                String dataSourceId=args[i].toString();
                if(null==dataSourceId||dataSourceId.length()==0){
                    throw new RuntimeException("数据源切换失败");
                }
                druidUtil.switchDB(dataSourceId);
            }
        }
    }

    /**
     * @Author: save
     * @Description: 方法执行完成,清除数据源
     * @Date: 2020/5/27 11:13
     * @Param: point【接入点】
     * @return: void
     **/
    @After("execution(* com.landa.service.*.*(..))")
    public void afterServiceMethodExecute(JoinPoint point){
        DynamicDataSourceHandler.clear();
    }
}

注意:我这里的@DataSource注解设置的作用域是在方法参数上。因为实际业务中数据源Id由其他服务发送接口请求时传过来,然后数据源服务定义切面切入需要切库的Service(这里切入点可以视自己的实际业务而定)。切面中切入到目标类的方法,判断方法参数中是否存在由@DataSource注解标注的参数。如果存在,则拿到该参数值(也就是数据源id)去数据源主库查询数据源配置信息(如连接数据库需要的url,driver,username,password等等),

下面假设DataSourceModel为获取到的数据库配置信息

package com.landa.model;

import lombok.Data;

/**
 * @Author: save
 * @Date: 2020/5/27 11:38
 * @Version: 1.0
 **/
@Data
public class DataSourceModel {
    private String username;
    private String password;
    private String url;
    private String driverClassName;

    public DataSourceModel(String username, String password, String url, String driverClassName) {
        this.username = username;
        this.password = password;
        this.url = url;
        this.driverClassName = driverClassName;
    }
}

创建数据源的简单demo

package com.landa.util;

import com.alibaba.druid.pool.DruidDataSource;
import com.landa.common.DynamicDataSourceHandler;
import com.landa.model.DataSourceModel;
import org.springframework.stereotype.Component;

/**
 * @Description:
 * @Author: save
 * @Date: 2020/5/27 14:05
 * @Version: 1.0
 **/
@Component
public class DruidUtil {

    public void switchDB(String dataSourceId){
    	//该Model的数据实际中应该是传参过来或去数据库查表
        DataSourceModel dataSourceModel=new DataSourceModel("root","root","jdbc:mysql://localhost:3306/qb_qbbs?characterEncoding=utf8&serverTimezone=GMT%2B8","com.mysql.cj.jdbc.Driver");
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(dataSourceModel.getDriverClassName());
        dataSource.setUrl(dataSourceModel.getUrl());
        dataSource.setUsername(dataSourceModel.getUsername());
        dataSource.setPassword(dataSourceModel.getPassword());
        DynamicDataSourceHandler.setDataSource(dataSource);
    }
}

再回到切面类DataSourceAspect中看Before,After

1fec1ed16577dfc92b04f7f31d143cdf.png

druidUtil.switchDB(datasourceId)方法根据切点拿到的数据源id,创建数据库连接,并通过

8d67531c8c090ae5ec8846bda67640f1.png

进行切库操作。

然后在方法执行完成

7e5fb98a05a540a80ea565e8b71bef18.png

after中执行数据源清除操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值