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

作者:catoop 时间:2021-07-03 04:27:02 

本文实现案例场景:

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

为了在开发中以最简单的方法使用,本文基于注解和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>

来源:http://blog.csdn.net/catoop/article/details/50575038

标签:spring,boot,数据源
0
投稿

猜你喜欢

  • Android从源码的角度彻底理解事件分发机制的解析(下)

    2023-09-21 11:23:59
  • Android studio设置文件头定制代码注释的方法

    2021-07-23 16:52:41
  • 在 Ubuntu Linux 上安装 Oracle Java 14的方法

    2022-01-01 15:13:52
  • Android 短信验证码自动填写

    2022-06-15 08:22:56
  • android开发教程之自定义属性用法详解

    2022-09-22 20:26:17
  • c# 获得当前绝对路径的方法(超简单)

    2021-06-18 05:23:31
  • Java Exception 捕获和显示实例详解

    2022-07-05 22:41:09
  • 浅谈为什么要使用mybatis的@param

    2023-07-01 20:12:39
  • C#实现Array,List,Dictionary相互转换

    2022-10-09 13:51:39
  • IDEA-Maven项目的jdk版本设置方法

    2022-07-21 16:04:17
  • 详解java中String、StringBuilder、StringBuffer的区别

    2023-06-17 06:03:23
  • Spring Cloud Alibaba Nacos Config加载配置详解流程

    2022-03-24 23:06:01
  • C#泛型约束的深入理解

    2023-02-21 09:32:19
  • SpringCloud Config统一配置中心问题分析解决与客户端动态刷新实现

    2023-08-17 17:19:48
  • Android应用中使用Fragment组件的一些问题及解决方案总结

    2022-09-12 09:22:27
  • 基于springboot实现redis分布式锁的方法

    2023-06-16 01:36:56
  • mybatis注解与xml常用语句汇总

    2022-05-17 18:39:47
  • Java的运算符和程序逻辑控制你了解吗

    2023-01-19 10:01:59
  • Android使用Item Swipemenulistview实现仿QQ侧滑删除功能

    2021-08-16 19:47:35
  • Maven工程打包jar的多种方式

    2022-12-15 06:54:46
  • asp之家 软件编程 m.aspxhome.com