Mybatis實用功能——動態數據源

一、動態數據源

動態數據源是一個很實用的功能,能夠在運行時切換數據源執行不同的數據庫操作,下文將通過spring整合Mybatis手寫一個動態數據源,主要使用到的技術有AOP、靜態代理模式,本文主要內容是動態數據源,對於其它邊邊角角的內容不多闡述。

 

二、步驟

spring boot配置文件:

logging:
  level:
    com.my.dynamicdatasource: debug
    org.springframework: warn
spring:
  datasource:
    druid:
      default:
        type: com.alibaba.druid.pool.DruidDataSource
        url: jdbc:mysql://localhost:3306/mybatis_study?serverTimezone=GMT%2B8
        username: root
        password: 123456
        driverClassName: com.mysql.cj.jdbc.Driver
      slave:
        type: com.alibaba.druid.pool.DruidDataSource
        enabled: false
        url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
        username: root
        password: 123456
        driverClassName: com.mysql.cj.jdbc.Driver

# MyBatis
mybatis:
  mapperLocations: classpath:mappers/*Mapper.xml
  configLocation: classpath:mybatis-config.xml

說明:在spring boot中註冊需要使用到的多個數據源,關於druid的設置不是本文重點,所以就沒有配置。

DataSourceKey枚舉類:

public enum DataSourceKey {
  
  DEFAULT,SLAVE

}

說明;:該類用來標識切換數據源,實際用多少個數據源就需要創建對應數量的枚舉類。

DataSource註解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

  DataSourceKey value() default DataSourceKey.DEFAULT;

}

說明:用來標記在Service或Mapper上,用於輔助AOP切換數據源操作。

UserServiceImpl服務類:

@Service
public class UserServiceImpl implements UserService{

  @Autowired
  private UserMapper userMapper;

  @Override
  @DataSource
  public User selectById(Integer id) {
    return userMapper.selectById(id);
  }

  @Override
  @DataSource(value = DataSourceKey.SLAVE)
  public User selectByIdSlave(Integer id) {
    return userMapper.selectById(id);
  }
}

說明:service層實現類。

UserMapper類:

public interface UserMapper {

    User selectById(Integer id);

}

DataSourceConfig數據源配置類:

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.default")
    public DataSource dataSourceDefault() {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.slave")
    public DataSource dataSourceSlave() {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }

    /**
     * 動態數據源
     * @param dataSourceDefault 默認數據源
     * @param dataSourceSlave 從數據源
     * @return
     */
    @Bean
    @Primary
    public DataSource dataSource(DataSource dataSourceDefault,DataSource dataSourceSlave) {
        DynamicDataSource dataSource = new DynamicDataSource(dataSourceDefault);
        dataSource.putDateSource(DataSourceKey.DEFAULT,dataSourceDefault);
        dataSource.putDateSource(DataSourceKey.SLAVE,dataSourceSlave);
        return dataSource;
    }

}

說明:創建數據源,對於動態數據源一定要加上 @Primary註解。

DataSourceAspect切面類:

@Aspect
@Component
public class DataSourceAspect {

  private static final Logger logger  = LoggerFactory.getLogger(DataSourceAspect.class);

  //切點
  @Pointcut("@annotation(com.my.dynamicdatasource.DataSource)")
  public void pointCut() {

  }


  @Around(value = "pointCut()")
  public Object aroud(ProceedingJoinPoint joinPoint) throws Throwable {
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();
    DataSource dataSource = method.getAnnotation(DataSource.class);
    DataSourceKey dataSourceKey = dataSource.value();
    if(dataSourceKey == null) {
      dataSourceKey = DataSourceKey.DEFAULT;
    }
    logger.info("本次使用的數據源是:"+dataSourceKey.toString());
    DynamicDataSource.DynamicDataSourceContextHolder.putDataSourceKey(dataSourceKey);
    try {
      return joinPoint.proceed();
    }finally {
      DynamicDataSource.DynamicDataSourceContextHolder.clearDateSource();
    }
  }

}

說明:AOP切面類,在service或mapper調用之前進行數據源的切換操作。

DynamicDataSource動態數據源類:

public class DynamicDataSource implements DataSource {

  public DynamicDataSource(DataSource defaultDataSource) {
    this.defaultDataSource = defaultDataSource;
  }

  private Map<DataSourceKey,DataSource> dataSourceMap = new ConcurrentHashMap();

  private DataSource defaultDataSource;

  private DataSource getDateSource(){
    DataSourceKey dataSourceKey = DynamicDataSourceContextHolder.getDataSourceKey();
    return dataSourceMap.get(dataSourceKey);
  }

  @Override
  public Connection getConnection() throws SQLException {
    return getDateSource().getConnection();
  }

  @Override
  public Connection getConnection(String username, String password) throws SQLException {
    return getDateSource().getConnection(username,password);
  }


  @Override
  public <T> T unwrap(Class<T> iface) throws SQLException {
    return getDateSource().unwrap(iface);
  }

  @Override
  public boolean isWrapperFor(Class<?> iface) throws SQLException {
    return getDateSource().isWrapperFor(iface);
  }

  @Override
  public PrintWriter getLogWriter() throws SQLException {
    return getDateSource().getLogWriter();
  }

  @Override
  public void setLogWriter(PrintWriter out) throws SQLException {
    getDateSource().setLogWriter(out);
  }

  @Override
  public void setLoginTimeout(int seconds) throws SQLException {
    getDateSource().setLoginTimeout(seconds);
  }

  @Override
  public int getLoginTimeout() throws SQLException {
    return getDateSource().getLoginTimeout();
  }

  @Override
  public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    return getDateSource().getParentLogger();
  }

  public void putDateSource(DataSourceKey dataSourceKey,DataSource dataSource) {
    dataSourceMap.put(dataSourceKey,dataSource);
  }

  /**
   *  靜態內部類,用於從線程中獲取當前要使用的數據源
   */
  public static class DynamicDataSourceContextHolder {

    private static ThreadLocal<DataSourceKey> context = new ThreadLocal();

    private static DataSourceKey getDataSourceKey() {
        return context.get();
    }

    public static void putDataSourceKey(DataSourceKey dataSourceKey) {
      if(dataSourceKey == null) {
          dataSourceKey = DataSourceKey.DEFAULT;
      }
        context.set(dataSourceKey);
    }

    public static void clearDateSource() {
      context.remove();
    }
  }
}

說明:對所有數據源的代理,主要是對獲取數據源操作進行增強,通過靜態內部類的ThreadLocal,根據當前線程存儲的@DataSource上的DataSourceKey去進行數據源的修改。

DynamicDatasourceApplication主程序類:

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@MapperScan("com.my.dynamicdatasource.mapper")
public class DynamicDatasourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(DynamicDatasourceApplication.class, args);
    }

}

說明:主程序類,注意必須添加排出掉DataSourceAutoConfiguration.class,否則spring boot會自動加載配置文件中配置的第一個數據源,導致衝突。

測試類:

@SpringBootTest
class DynamicDatasourceApplicationTests {


    @Autowired
    private UserService userService;

    @Test
    public void test() {
        User user = userService.selectById(1);
        System.out.println(user);
        User user1 = userService.selectByIdSlave(2);
    }

}

 

三、原理說明

1.使用AOP切面對標記了@DataSource註解的方法進行攔截,然後將其要使用的對應的數據源標識存放到ThreadLocal中,等到獲取數據源時獲取。

2.創建一個靜態代理對象,把其設置爲Mybatis的數據源獲取類,攔截了所有實際數據源的獲取,當Mybatis需要一個數據源時,調用的是該代理對象,該代理對象再獲取當前線程中存放在ThreadLocal的數據源標識從而獲取對應標識的數據源返回。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章