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的数据源标识从而获取对应标识的数据源返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值