目录

spring boot 动态数据源配置

2018年04月14日 10:23 | 1382次浏览

上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库。那么这一节我们将要介绍怎么进行多数据源动态切换。添加本文实现的代码之后,只需要配置要数据源就可以直接通过注解使用,在实际使用的时候特别的简单。那么本章主要分以下几个步骤进行实战。

本章节案例步骤:

(1)新建maven java project;

(2)在pom.xml添加依赖包;

(3)编写启动类App.java

(4)编写配置文件application.properties;

(5)动态数据源路由类;

(6)注册多数据源;

(7)测试类测试;

接下来我们看看每一步具体的实现吧:

(1)新建maven java project;

    新建一个maven project,取名为:spring-boot-multi-ds

(2)在pom.xml添加依赖包;

   在pom.xml文件中加入依赖的库文件,主要是spring boot基本的,数据库驱动,spring-jpa支持即可,具体pom.xml文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.kfit</groupId>
  <artifactId>spring-boot-multids</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
 
  <name>spring-boot-multids</name>
  <url>http://maven.apache.org</url>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- jdk版本号,这里需要你本地进行的jdk进行修改,这里angel使用的是1.8的版本. -->
    <java.version>1.8</java.version>
  </properties>
 
 
   <!--
       spring boot 父节点依赖,
       引入这个之后相关的引入就不需要添加version配置,
       spring boot会自动选择最合适的版本进行添加。
       在这里使用的1.3.3版本,可能目前官方有最新的版本了,大家可以
       使用最新的版本。
     -->
    <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>1.3.3.RELEASE</version>
    </parent>
 
  <dependencies>
    <!-- 单元测试包,在这里没有使用到. -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>
   
    <!-- spring boot web支持:mvc,aop...
        这个是最基本的,基本每一个基本的demo都是需要引入的。
    -->
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
   
    <!-- mysql驱动.
       我们的demo是多数据源,在这里使用Mysql数据库.
    -->
    <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
    </dependency>
   
   
    <!-- spring jpa
       spring jpa中带有自带的tomcat数据连接池;
       在代码中我们也需要用到.
     -->
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
   
  </dependencies>
</project>

在上面的配置文件中都有相应的解释,大家可以自己解读下。

(3)编写启动类App.java

编写spring boot的启动类:

com.kfit.App:

package com.kfit;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
/**
 *
 * 
 * @version v.0.1
 */
@SpringBootApplication
public class App {
    public static void main(String[] args) {
       SpringApplication.run(App.class, args);
    }
}

(4)编写配置文件application.properties;

 在这里主要是多数据源和jpa的配置:

src/main/resources/application.properties:

########################################################
###配置文件包括1个主数据源和多个数据源,
###其中主数据源在Spring中的beanName默认为dataSource,
###另外几个数据源的beanName分包为:ds1、ds2、ds3
###其中datasource的type属性可以具体指定到我们需要的数据源上面,
###不指定情况下默认为:org.apache.tomcat.jdbc.pool.DataSource
###当然你也可以把这些数据源配置到主dataSource数据库中,然后读取数据库生成多数据源。当然这样做的必要性并不大,难不成数据源还会经常变吗。
########################################################
 
# 主数据源,默认的
#spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
 
 
# 更多数据源
custom.datasource.names=ds1,ds2,ds3
#custom.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds1.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1
custom.datasource.ds1.username=root
custom.datasource.ds1.password=root
 
#custom.datasource.ds2.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds2.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test
custom.datasource.ds2.username=root
custom.datasource.ds2.password=root
 
#custom.datasource.ds3.type=com.zaxxer.hikari.HikariDataSource
custom.datasource.ds3.driverClassName =com.mysql.jdbc.Driver
custom.datasource.ds3.url=jdbc:mysql://localhost:3306/test
custom.datasource.ds3.username=root
custom.datasource.ds3.password=root
 
 
# 下面为连接池的补充设置,应用到上面所有数据源中
spring.datasource.maximum-pool-size=100
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
spring.datasource.validation-query=SELECT 1
spring.datasource.test-on-borrow=false
spring.datasource.test-while-idle=true
spring.datasource.time-between-eviction-runs-millis=18800
 
 
 
########################################################
### Java Persistence Api
########################################################
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
#[org.hibernate.cfg.ImprovedNamingStrategy  #org.hibernate.cfg.DefaultNamingStrategy]
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.DefaultNamingStrategy
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

(5)动态数据源路由类;

   动态数据源能进行自动切换的核心就是spring底层提供了AbstractRoutingDataSource类进行数据源的路由的,我们主要继承这个类,实现里面的方法即可实现我们想要的,这里主要是实现方法:determineCurrentLookupKey(),而此方法只需要返回一个数据库的名称即可,所以我们核心的是有一个类来管理数据源的线程池,这个类才是动态数据源的核心处理类。还有另外就是我们使用aop技术在执行事务方法前进行数据源的切换。所以这里有几个需要编码的类,具体如下:

动态数据源上下文>com.kfit.config.datasource.DynamicDataSourceContextHolder:

package com.kfit.config.datasource.dynamic;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * 动态数据源上下文.
 *
 * 
 * @version v.0.1
 */
public class DynamicDataSourceContextHolder {
   
    /*
     * 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    /*
     * 管理所有的数据源id;
     * 主要是为了判断数据源是否存在;
     */
    public static List<String> dataSourceIds = new ArrayList<String>();
 
    /**
     * 使用setDataSourceType设置当前的
     * @param dataSourceType
     */
    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);
    }
}

数据源路由类>com.kfit.config.datasource.dynamic.DynamicDataSource:

package com.kfit.config.datasource.dynamic;
 
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
/**
 * 动态数据源.
 *
 * @version v.0.1
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
   
    /*
     * 代码中的determineCurrentLookupKey方法取得一个字符串,
     * 该字符串将与配置文件中的相应字符串进行匹配以定位数据源,配置文件,即applicationContext.xml文件中需要要如下代码:(non-Javadoc)
     * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()
     */
    @Override
    protected Object determineCurrentLookupKey() {
       /*
        * DynamicDataSourceContextHolder代码中使用setDataSourceType
        * 设置当前的数据源,在路由类中使用getDataSourceType进行获取,
        *  交给AbstractRoutingDataSource进行注入使用。
        */
       return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

指定数据源注解类>com.kfit.config.datasource.dynamic.TargetDataSource:

package com.kfit.config.datasource.dynamic;
 
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;
 
/**
 * 在方法上使用,用于指定使用哪个数据源
 * 
 * @version v.0.1
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
}

切换数据源Advice>com.kfit.config.datasource.dynamic.DynamicDataSourceAspect:

package com.kfit.config.datasource.dynamic;
 
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
 
/**
 * 切换数据源Advice
 * 
 * @version v.0.1
 */
@Aspect
@Order(-10)//保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {
   
    /*
     * @Before("@annotation(ds)")
     * 的意思是:
     *
     * @Before:在方法执行之前进行执行:
     * @annotation(targetDataSource):
     * 会拦截注解targetDataSource的方法,否则不拦截;
     */
    @Before("@annotation(targetDataSource)")
    public void changeDataSource(JoinPoint point, TargetDataSourcetargetDataSource) throws Throwable {
       //获取当前的指定的数据源;
        String dsId = targetDataSource.value();
        //如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
        if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
            System.err.println("数据源[{}]不存在,使用默认数据源 > {}"+targetDataSource.value()+point.getSignature());
        } else {
            System.out.println("Use DataSource : {} > {}"+targetDataSource.value()+point.getSignature());
            //找到的话,那么设置到动态数据源上下文中。
            DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
        }
    }
   
    @After("@annotation(targetDataSource)")
    public void restoreDataSource(JoinPoint point, TargetDataSourcetargetDataSource) {
       System.out.println("Revert DataSource : {} > {}"+targetDataSource.value()+point.getSignature());
       //方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
   
}

(6)注册多数据源;

   以上都是动态数据源在注入的时候使用的代码,其实很重要的一部分代码就是注册我们在application.properties配置的多数据源,这才是重点,这里我们使用

ImportBeanDefinitionRegistrar进行注册,具体的代码如下:

package com.kfit.config.datasource.dynamic;
 
import java.util.HashMap;
import java.util.Map;
 
import javax.sql.DataSource;
 
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;
 
/**
 * 动态数据源注册;
 *
 * @version v.0.1
 */
public class DynamicDataSourceRegister  implements ImportBeanDefinitionRegistrar, EnvironmentAware {
   
    //如配置文件中未指定数据源类型,使用该默认值
    private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
    private ConversionService conversionService = new DefaultConversionService();
    private PropertyValues dataSourcePropertyValues;
   
    // 默认数据源
    private DataSource defaultDataSource;
   
    private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
   
    /**
     * 加载多数据源配置
     */
    @Override
    public void setEnvironment(Environment environment) {
       System.out.println("DynamicDataSourceRegister.setEnvironment()");
       initDefaultDataSource(environment);
        initCustomDataSources(environment);
    }
   
    /**
     * 加载主数据源配置.
     * @param env
     */
    private void initDefaultDataSource(Environment env){
       // 读取主数据源
       RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
       Map<String, Object> dsMap = new HashMap<String, Object>();
       dsMap.put("type", propertyResolver.getProperty("type"));
        dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));
        dsMap.put("url", propertyResolver.getProperty("url"));
        dsMap.put("username", propertyResolver.getProperty("username"));
        dsMap.put("password", propertyResolver.getProperty("password"));
       
        //创建数据源;
        defaultDataSource = buildDataSource(dsMap);
        dataBinder(defaultDataSource, env);
    }
   
    /**
     * 初始化更多数据源
     *
     * @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);
        }
    }
   
    /**
     * 创建datasource.
     * @param dsMap
     * @return
     */
    @SuppressWarnings("unchecked")
    public DataSource buildDataSource(Map<String, Object> dsMap) {
       Object type = dsMap.get("type");
        if (type == null){
            type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
        }
        Class<? extends DataSource> dataSourceType;
       
       try {
           dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
           String driverClassName = dsMap.get("driverClassName").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);
            returnfactory.build();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
       returnnull;
    }
   
    /**
     * 为DataSource绑定更多数据
     * @param dataSource
     * @param env
     */
    private void dataBinder(DataSource dataSource, Environment env){
       RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
       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("driverClassName");
            values.remove("url");
            values.remove("username");
            values.remove("password");
            dataSourcePropertyValues = new MutablePropertyValues(values);
        }
        dataBinder.bind(dataSourcePropertyValues);
       
    }
   
 
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
       System.out.println("DynamicDataSourceRegister.registerBeanDefinitions()");
       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();
        //添加属性:AbstractRoutingDataSource.defaultTargetDataSource
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        registry.registerBeanDefinition("dataSource", beanDefinition);
    }
 
}

这里还有一个步骤很重要,由于我们是使用的ImportBeanDefinitionRegistrar的方式进行注册的,所以我们需要在App.java类中使用@Import进行注册,具体改造之后的App.java代码如下:

package com.kfit;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import com.kfit.config.datasource.dynamic.DynamicDataSourceRegister;
 
/**
 *
 * 
 * @version v.0.1
 */
@SpringBootApplication
//注册动态多数据源
@Import({DynamicDataSourceRegister.class})
public class App {
    public static void main(String[] args) {
       SpringApplication.run(App.class, args);
    }
}

(7)测试类测试;

 核心的代码都编写完毕了,当然我们肯定是需要编写代码进行测试下,我们才放心了。

com.kfit.demo.bean.Demo:

package com.kfit.demo.bean;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
 
/**
 * 测试demo类.
 * 
 * @version v.0.1
 */
@Entity
public class Demo {
    @Id @GeneratedValue
    private longid;
   
    private String name;
 
    public long getId() {
       return id;
    }
 
    public void setId(longid) {
       this.id = id;
    }
 
    public String getName() {
       return name;
    }
 
    publicvoid setName(String name) {
       this.name = name;
    }
 
    @Override
    public String toString() {
       return"Demo [id=" + id + ", name=" + name + "]";
    }
}

com.kfit.demo.dao.TestDao:

package com.kfit.demo.dao;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
 
import com.kfit.demo.bean.Demo;
 
@Service
public class TestDao {
   
    @Autowired
    private JdbcTemplate jdbcTemplate;
   
    /**
     * 不指定数据源使用默认数据源
     * @return
     */
    public List<Demo> getList(){
       String sql = "select *from Demo";
        return (List<Demo>) jdbcTemplate.query(sql, new RowMapper<Demo>(){
            @Override
            public Demo mapRow(ResultSet rs, introwNum) throws SQLException {
                Demo demo = new Demo();
                demo.setId(rs.getLong("id"));
                demo.setName(rs.getString("name"));;
                returndemo;
            }
        });
    }
   
     /**
     * 指定数据源
     * 在对应的service进行指定;
     * @return
     * @author SHANHY
     * @create  2016年1月24日
     */
    public List<Demo> getListByDs1(){
        /*
         * 这张表示复制的主库到ds1的,在ds中并没有此表.
         * 需要自己自己进行复制,不然会报错:Table 'test1.demo1' doesn't exist
         */
       String sql = "select *from Demo1";
        return (List<Demo>) jdbcTemplate.query(sql, new RowMapper<Demo>(){
 
            @Override
            public Demo mapRow(ResultSet rs, introwNum) throws SQLException {
                Demo demo = new Demo();
                demo.setId(rs.getLong("id"));
                demo.setName(rs.getString("name"));;
                returndemo;
            }
 
        });
    }
}

com.kfit.demo.service.TestService :

package com.kfit.demo.service;
 
import java.util.List;
 
import javax.annotation.Resource;
 
import org.springframework.stereotype.Service;
 
import com.kfit.config.datasource.dynamic.TargetDataSource;
import com.kfit.demo.bean.Demo;
import com.kfit.demo.dao.TestDao;
 
@Service
public class TestService {
   
    @Resource
    private TestDao testDao;
   
    /**
     * 不指定数据源使用默认数据源
     * @return
     */
    public List<Demo> getList(){
       returntestDao.getList();
    }
   
    /**
     * 指定数据源
     * @return
     */
    @TargetDataSource("ds1")
    public List<Demo> getListByDs1(){
        returntestDao.getListByDs1();
    }
}

com.kfit.demo.controller.TestController:

package com.kfit.demo.controller;
 
import javax.annotation.Resource;
 
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import com.kfit.demo.bean.Demo;
import com.kfit.demo.service.TestService;
 
@RestController
public class TestController {
   
    @Resource
    private TestService testService;
   
    @RequestMapping("/test1")
    public String test(){
//     for(Demo d:testService.getList()){
//         System.out.println(d);
//     }
       for(Demo d:testService.getListByDs1()){
           System.out.println(d);
       }
       return"ok";
    }
   
}

好了,测试代码就这么多了,运行App.java进行测试把,访问:

http://127.0.0.1:8080/test1 查看控制台的打印。



感觉本站内容不错,读后有收获?小额赞助,鼓励网站分享出更好的教程


上一篇:mongodb 更新文档 下一篇:mongodb 删除文档
^