首页 > 开发 > Java > 正文

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

2024-07-13 10:04:38
字体:
来源:转载
供稿:网友

本文实现案例场景:

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

为了在开发中以最简单的方法使用,本文基于注解和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.Driverspring.datasource.url=jdbc:mysql://localhost:3306/testspring.datasource.username=rootspring.datasource.password=123456# 更多数据源custom.datasource.names=ds1,ds2custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Drivercustom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1custom.datasource.ds1.username=rootcustom.datasource.ds1.password=123456custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Drivercustom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2custom.datasource.ds2.username=rootcustom.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日 */@Servicepublic 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之前执行@Componentpublic 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)@Documentedpublic @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>

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


注:相关教程知识阅读请移步到JAVA教程频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表