zoukankan      html  css  js  c++  java
  • 四、Spring Boot 多数据源 自动切换

    实现案例场景:
    某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库。
    为了在开发中以最简单的方法使用,本文基于注解和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
    *
    */
    @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);
    }

    /**
    * 不指定数据源使用默认数据源
    *
    */
    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
    */
    @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;
    }

    });
    }

    /**
    * 指定数据源
    *
    */
    @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语句的接口,无逻辑实现
    *
    */
    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;

    /**
    * 动态数据源
    *
    */
    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
    *
    */
    @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)
    *
    */
    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
    */
    @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);
    }

    /**
    * 初始化主数据源
    *
    */
    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
    */
    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);
    }

    /**
    * 初始化更多数据源
    *
    */
    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;

    /**
    * 在方法上使用,用于指定使用哪个数据源
    *
    */
    @Target({ ElementType.METHOD, ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TargetDataSource {
    String name();
    }
    本文代码博主是经过测试后没有问题才发出来共享给大家的。对于连接池参数配置会应用到所有数据源上。
    比如配置一个:
    spring.datasource.maximum-pool-size=80
    1

    1
    那么我们所有的数据源都会自动应用上。
    补充:
    如果你使用的是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>

  • 相关阅读:
    Selenium简单测试页面加载速度的性能(Page loading performance)
    Selenium Page object Pattern usage
    Selenium如何支持测试Windows application
    UI Automation的两个成熟的框架(QTP 和Selenium)
    分享自己针对Automation做的两个成熟的框架(QTP 和Selenium)
    敏捷开发中的测试金字塔(转)
    Selenium 的基础框架类
    selenium2 run in Jenkins GUI testing not visible or browser not open but run in background浏览器后台运行不可见
    eclipse与SVN 结合(删除SVN中已经上传的问题)
    配置Jenkins的slave节点的详细步骤适合windows等其他平台
  • 原文地址:https://www.cnblogs.com/chenliangcl/p/7346020.html
Copyright © 2011-2022 走看看