非侵入式mybatis多數據庫切換實踐

環境:springboot+mybatis

數據庫:mysql+oracle

需求:一個程序中,一部分數據從mysql中查,一部分數據從oracle中查。使用AOP來實現數據庫動態切換

參考網上的mybatis動態數據源,並額外擴展使用AOP

步驟:

1.先定義不同的數據庫連接類型,用來表示mysql數據庫和oracle數據庫,如果還有多個數據庫,繼續定義 (這裏cms表示oracle數據庫,hms表示mysql數據庫)

public enum  DataSourceTypeEnum {
    cms,hms
}

 2.繼承AbstractRoutingDataSource,實現我們自己的動態數據源類型

/**
 * 動態數據源
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final  ThreadLocal<DataSourceTypeEnum> contextHolder = new ThreadLocal<>();

    public static void setDataSourceType(DataSourceTypeEnum type){
        contextHolder.set(type);
    }

    public static  DataSourceTypeEnum getDataSourceType(){
        return contextHolder.get();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSourceType();
    }
}

3.定義數據源配置類,在裏面定義數據庫連接1(cms),數據庫連接2(hms),外加主數據源。

數據庫連接1/2有兩組實現,分別使用Druid連接池和Pooled連接池實現,由配置文件指定使用哪種。

主數據源 傳入 數據庫連接1和2 來完成構造,並設置默認數據庫連接

/**
 * 數據庫定義,通過配置項datasource.type,來切換數據庫連接池
 */
@Configuration
public class DataSourceConfig {
    private static int initTimes = 0;

    @Autowired
    Environment environment;

    /**
     * 動態數據源,注入cms的數據庫連接和hms的數據庫連接
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("cmsDataSource") DataSource cmsDataSource,
                                        @Qualifier("hmsDataSource") DataSource hmsDataSource) {
        assert initTimes==0;    //數據源只能初始化一次
        initTimes++;
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceTypeEnum.cms, cmsDataSource);
        targetDataSources.put(DataSourceTypeEnum.hms, hmsDataSource);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);// 該方法是AbstractRoutingDataSource的方法
        dataSource.setDefaultTargetDataSource(cmsDataSource);// 默認的datasource設置爲myTestDbDataSource

        return dataSource;
    }

    @Bean("cmsDataSource")
    @ConditionalOnProperty(value = "datasource.type", havingValue = "druid")
    public DataSource cmsDataSourceDruid() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", environment.getProperty("cms.db.driverClass"));
        props.put("url", environment.getProperty("cms.db.url"));
        props.put("username", environment.getProperty("cms.db.username"));
        props.put("password", environment.getProperty("cms.db.password"));
        props.put("initialPoolSize", environment.getProperty("cms.db.initialPoolSize"));
        props.put("initialMaxPoolSize", environment.getProperty("cms.db.initialMaxPoolSize"));
        props.put("initialMinPoolSize", environment.getProperty("cms.db.initialMinPoolSize"));
        return DruidDataSourceFactory.createDataSource(props);
    }
    @Bean("hmsDataSource")
    @ConditionalOnProperty(value = "datasource.type", havingValue = "druid")
    public DataSource hmsDataSourceDruid() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", environment.getProperty("hms.db.driverClass"));
        props.put("url", environment.getProperty("hms.db.url"));
        props.put("username", environment.getProperty("hms.db.username"));
        props.put("password", environment.getProperty("hms.db.password"));
        props.put("initialPoolSize", environment.getProperty("hms.db.initialPoolSize"));
        props.put("initialMaxPoolSize", environment.getProperty("hms.db.initialMaxPoolSize"));
        props.put("initialMinPoolSize", environment.getProperty("hms.db.initialMinPoolSize"));
        return DruidDataSourceFactory.createDataSource(props);
    }

    @Bean("cmsDataSource")
    @ConditionalOnProperty(value = "datasource.type", havingValue = "jdbc")
    public DataSource cmsDataSourceJdbc() throws Exception {
        PooledDataSource pooledDataSource = new PooledDataSource();
        pooledDataSource.setDriver(environment.getProperty("cms.db.driverClass"));
        pooledDataSource.setUrl(environment.getProperty("cms.db.url"));
        pooledDataSource.setUsername(environment.getProperty("cms.db.username"));
        pooledDataSource.setPassword(environment.getProperty("cms.db.password"));
        return pooledDataSource;
    }
    @Bean("hmsDataSource")
    @ConditionalOnProperty(value = "datasource.type", havingValue = "jdbc")
    public DataSource hmsDataSourceJdbc() throws Exception {
        PooledDataSource pooledDataSource = new PooledDataSource();
        pooledDataSource.setDriver(environment.getProperty("hms.db.driverClass"));
        pooledDataSource.setUrl(environment.getProperty("hms.db.url"));
        pooledDataSource.setUsername(environment.getProperty("hms.db.username"));
        pooledDataSource.setPassword(environment.getProperty("hms.db.password"));
        return pooledDataSource;
    }
}

4.編寫mybatis配置類,傳入我們定義的動態數據庫

@Configuration
@MapperScan("demo.dao")
public class MybatisConfig {

    @Autowired
    DataSourceConfig dataSourceConfig;

    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("cmsDataSource") DataSource cmsDataSource,
                                               @Qualifier("hmsDataSource") DataSource hmsDataSource)
            throws Exception{
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(dataSourceConfig.dataSource(cmsDataSource,hmsDataSource));//多次調用dataSource,返回同一個bean
        fb.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:demo/dao/*.xml"));
        fb.setDatabaseIdProvider(getDatabaseIdProvider());
        return fb.getObject();
    }

    public DatabaseIdProvider getDatabaseIdProvider(){
        DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
        Properties properties = new Properties();
        properties.setProperty("Oracle","oracle");
        properties.setProperty("Mysql","mysql");
        properties.setProperty("Microsoft SQL Server","sqlserver");
        databaseIdProvider.setProperties(properties);
        return databaseIdProvider;
    }


}

5.到這裏就已經可以切換數據庫,正常使用了,例如:

@Service
public class IntegerateHelper {
    @Autowired
    IntegrateInfoMapper cmsMapper;    //mysql對應的sql語句
    @Autowired
    CtrlIntegrationInfoMapper hmsMapper;    //oracle對應的sql語句

    //默認的,這裏是查詢mysql庫
    public IntegrateInfo queryIntegrateInfo(Short integrateId){
        IntegrateInfo integrateInfo = cmsMapper.selectByPrimaryKey(integrateId);
        return integrateInfo;
    }

    //進行數據庫切換,查詢oracle數據庫,查詢完,再切換回mysql數據庫
    public CtrlIntegrationInfo queryCtrlVendorInfo(String integrateName){
        DynamicDataSource.setDataSourceType(DataSourceTypeEnum.hms);    
        CtrlIntegrationInfo integrationInfo = hmsMapper.selectByName(integrateName);
        DynamicDataSource.setDataSourceType(DataSourceTypeEnum.cms);    
        return integrationInfo;
    }
}

6.每次需要數據庫切換,都要手動在代碼前後添加兩行代碼,可以使用AOP來幫助我們完成切換

定義一個註解(注意:因爲數據庫切換不支持事務,所以需要聲明成Propagation.NOT_SUPPORTED。如果不聲明,在事務中進行數據庫切換,會切換不成功,還是在默認數據庫中執行

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public @interface ChooseHmsDatabase {
}

再將這個註解作爲切面,定義一個around的AOP

@Aspect
@Component
public class ChangeDbAop {
    @Pointcut("@annotation(ChooseHmsDatabase)")
    public void pointcut(){}

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        DynamicDataSource.setDataSourceType(DataSourceTypeEnum.hms);    //切換到hms的庫
        try {
            return joinPoint.proceed();
        }finally {
            DynamicDataSource.setDataSourceType(DataSourceTypeEnum.cms);    //切換回cms的庫
        }
    }
}

7.在需要數據庫切換到oracle庫的地方,註解上@ChooseHmsDatabase就行了

@Service
public class IntegerateHelper {
    @Autowired
    IntegrateInfoMapper cmsMapper;

    @Autowired
    CtrlIntegrationInfoMapper hmsMapper;

    public IntegrateInfo queryIntegrateInfo(Short integrateId){
        IntegrateInfo integrateInfo = cmsMapper.selectByPrimaryKey(integrateId);
        return integrateInfo;
    }

    //使用註解,就會自動在方法調用時切換到另一個數據庫,方法調用後,自動切換會原來的數據庫
    @ChooseHmsDatabase
    public CtrlIntegrationInfo queryCtrlVendorInfo(String integrateName){
        CtrlIntegrationInfo integrationInfo = hmsMapper.selectByName(integrateName);
        return integrationInfo;
    }

}

最後,附上gradle架包引用和數據庫的配置

    compile("org.springframework.boot:spring-boot-starter")
    compile("org.springframework.boot:spring-boot-starter-aop")

    //mybatis相關架包
    compile("com.baomidou:mybatis-plus-boot-starter:2.3")
    compile group: 'org.mybatis.generator', name: 'mybatis-generator-core', version: '1.4.0'
    compile group: 'com.alibaba', name: 'druid', version: '1.1.22'
    compile group: 'com.github.pagehelper', name: 'pagehelper', version: '5.1.11'


    compile("com.microsoft.sqlserver:mssql-jdbc:6.4.0.jre8")
    compile group: 'com.oracle.ojdbc', name: 'ojdbc8', version: '19.3.0.0'
    compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.18'
################## 選擇數據庫連接池實現
datasource.type=druid

#############   oracle的數據庫連接配置     #############
cms.db.url=jdbc:oracle:thin:@127.0.0.1:1521:xe
cms.db.driverClass=oracle.jdbc.driver.OracleDriver
cms.db.username=user
cms.db.password=123456
cms.db.initialPoolSize=5
cms.db.initialMaxPoolSize=20
cms.db.initialMinPoolSize=5

##############   mysql的數據庫連接配置   #####################
hms.db.url=jdbc:mysql://10.10.213.168:3306/test
hms.db.driverClass=com.mysql.jdbc.Driver
hms.db.username=root
hms.db.password=123456
hms.db.initialPoolSize=5
hms.db.initialMaxPoolSize=20
hms.db.initialMinPoolSize=5

 

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