当前位置: 移动技术网 > IT编程>开发语言>Java > Spring Boot 动态数据源示例(多数据源自动切换)

Spring Boot 动态数据源示例(多数据源自动切换)

2019年07月22日  | 移动技术网IT编程  | 我要评论

本文实现案例场景:

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

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

一配置二使用

1. 启动类注册动态数据源

2. 配置文件中配置多个数据源

3. 在需要的方法上使用注解指定数据源

1、在启动类添加 @import({dynamicdatasourceregister.class, mproxytransactionmanagementconfiguration.class})

@springbootapplication
@import({dynamicdatasourceregister.class}) // 注册动态多数据源
public class springbootsampleapplication {

  // 省略其他代码
}

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>(){

      @override
      public student maprow(resultset rs, int rownum) throws sqlexception {
        student stu = new student();
        stu.setid(rs.getint("id"));
        stu.setage(rs.getint("age"));
        stu.setname(rs.getstring("name"));
        stu.setsumscore(rs.getstring("score_sum"));
        stu.setavgscore(rs.getstring("score_avg"));
        return stu;
      }

    });
  }

  /**
   * 指定数据源
   *
   * @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>(){

      @override
      public student maprow(resultset rs, int rownum) throws sqlexception {
        student stu = new student();
        stu.setid(rs.getint("id"));
        stu.setage(rs.getint("age"));
        stu.setname(rs.getstring("name"));
        stu.setsumscore(rs.getstring("score_sum"));
        stu.setavgscore(rs.getstring("score_avg"));
        return stu;
      }

    });
  }

  /**
   * 指定数据源
   *
   * @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>(){

      @override
      public student maprow(resultset rs, int rownum) throws sqlexception {
        student stu = new student();
        stu.setid(rs.getint("id"));
        stu.setage(rs.getint("age"));
        stu.setname(rs.getstring("name"));
        stu.setsumscore(rs.getstring("score_sum"));
        stu.setavgscore(rs.getstring("score_avg"));
        return stu;
      }

    });
  }
}

要注意的是,在使用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

package org.springboot.sample.datasource;

import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource;

/**
 * 动态数据源
 *
 * @author  单红宇(365384722)
 * @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)
 * @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)
 * @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)
 * @create  2016年1月23日
 */
@target({ elementtype.method, elementtype.type })
@retention(retentionpolicy.runtime)
@documented
public @interface targetdatasource {
  string name();
}

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

spring.datasource.maximum-pool-size=80

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

补充:

如果你使用的是springmvc,并集成了shiro,一般按网上的配置你可能是:

 <bean class="org.springframework.aop.framework.autoproxy.defaultadvisorautoproxycreator" depends-on="lifecyclebeanpostprocessor">
    <property name="proxytargetclass" value="true" />
  </bean>

  <bean class="org.apache.shiro.spring.security.interceptor.authorizationattributesourceadvisor">
    <property name="securitymanager" ref="securitymanager"/>
  </bean>

那么你请不要这样做,请按下面方法配置:

 <!-- aop式方法级权限检查 -->
  <!-- 不要使用 defaultadvisorautoproxycreator 会出现二次代理的问题,这里不详述。 mark by shanhy 2016-05-15 -->
  <aop:config proxy-target-class="true"/>
  <!-- 或者你使用了 <aop:aspectj-autoproxy proxy-target-class="true" /> 也可以。 -->

  <bean class="org.apache.shiro.spring.security.interceptor.authorizationattributesourceadvisor">
    <property name="securitymanager" ref="securitymanager"/>
  </bean>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网