記一次Spring Boot 配置多ElasticSearch-sql 數據源,按照參數動態切換


最近公司項目中 有需要用ElasticSearch (后續簡稱ES) 集成 SQL 查詢功能,並可以按照請求參數動態切換目標數據源,同事找到我幫忙實現該功能,以前沒做過,只好趕鴨子上架,
網上很多資料不全,瞎琢磨半天終於完成,記錄了一些實現過程中踩過的坑,便於大家借鑒。

我們測試環境部署的是 ElasticSearch6.8.2 ,對應需要使用的jar需要是同版本的x-pack-sql-jdbc.jar 否則會報版本不一致錯誤.
不過該功能的開通需要鉑金會員或者自己破解,具體的破解方案可以看看其他文章。以下介紹代碼的具體實現.

切換數據源部分有參考下方鏈接代碼,
https://blog.csdn.net/hekf2010/article/details/81155778

1. application.properties配置

server.port=6666
#主數據源
spring.datasource.url=jdbc:es://http://10.0.75.20:9200/
#es 從數據源 es1,es2
slave.datasource.names=es1,es2 
#es1
slave.datasource.es1.url=jdbc:es://http://10.0.75.21:9200/
#es2
slave.datasource.es2.url=jdbc:es://http://10.0.75.22:9200/
#mapper.xml文件
mybatis.mapper-locations=classpath:mapper/*.xml 
#實體類包
mybatis.type-aliases-package=com.kunlun.es.vo  

2. 注冊動態數據源.
PS:這個地方一開始以為要添加ES db驅動的,后面查看源碼之后發現,這貨壓根就不需要添加EsDriver

import org.apache.log4j.Logger;
import org.elasticsearch.xpack.sql.jdbc.EsDataSource;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;



/**
 * @Author zhaozhiguo
 * @Date 2020-11-04
 * @Description 注冊動態數據源
 */
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private Logger logger = Logger.getLogger(DynamicDataSourceRegister.class);

    /***默認數據源***/
    private DataSource deftDataSource;
    /***自定義數據源***/
    private Map<String, DataSource> slaveDataSources = new ConcurrentHashMap<>();

    @Override
    public void setEnvironment(Environment environment) {
        initDefaultDataSource(environment);
        initslaveDataSources(environment);
    }

    private void initDefaultDataSource(Environment env) {
        // 讀取主數據源
        Properties properties = new Properties();
        EsDataSource esDataSource = new EsDataSource();
        esDataSource.setUrl( env.getProperty("spring.datasource.url"));
        esDataSource.setProperties(properties);
        deftDataSource = esDataSource;
    }


    private void initslaveDataSources(Environment env) {
        // 讀取配置文件獲取更多數據源
        String dsPrefixs = env.getProperty("slave.datasource.names");
        for (String dsPrefix : dsPrefixs.split(",")) {
            // 多個數據源
            Properties properties = new Properties();
            EsDataSource esDataSource = new EsDataSource();
            esDataSource.setUrl(env.getProperty("slave.datasource." + dsPrefix + ".url"));
            esDataSource.setProperties(properties);
            slaveDataSources.put(dsPrefix, esDataSource);
        }
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        //添加默認數據源
        targetDataSources.put("dataSource", this.deftDataSource);
        DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
        //添加其他數據源
        targetDataSources.putAll(slaveDataSources);
        for (String key : slaveDataSources.keySet()) {
            DynamicDataSourceContextHolder.dataSourceIds.add(key);
        }

        //創建DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("defaultTargetDataSource", deftDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        //注冊 - BeanDefinitionRegistry
        beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);

        logger.info("Dynamic DataSource Registry");
    }

3. 自定義注解,用於攔截 mapper 執行sql 時切換數據源

import java.lang.annotation.*;

/**
 * @Author zhaozhiguo
 * @Date 2020-11-04
 * @Description 需要切換數據源注解
 */

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {

}

4. 請求參數

/**
 * @Author zhaozg
 * @Date 2020-11-04
 * @Description SelectParam 查詢參數
 */
public class SelectParam {

    /**需要執行的SQL*/
    private String sql;

    /**執行SQL的數據源名稱,需要和properties slave.datasource.names 匹配*/
    private String dcName;

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public String getDcName() {
        return dcName;
    }

    public void setDcName(String dcName) {
        this.dcName = dcName;
    }
}

5. AOP 監聽動態切換數據源

import com.kunlun.es.vo.SelectParam;
import org.apache.log4j.Logger;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @Author zhaozhiguo
 * @Date 2020-11-04
 * @Description 動態數據源通知
 */

@Aspect
@Order(-1)
@Component
public class DynamicDattaSourceAspect {

    private Logger logger = Logger.getLogger(DynamicDattaSourceAspect.class);

    //改變數據源
    @Before("@annotation(targetDataSource)")
    public void changeDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
        Object[] str= joinPoint.getArgs();
        SelectParam selectParams = (SelectParam) str[0];
        if (!DynamicDataSourceContextHolder.isContainsDataSource(selectParams.getDcName())) {
            logger.error("數據源 " + selectParams.getDcName() + " 不存在使用默認的數據源 -> " + joinPoint.getSignature());
        } else {
            logger.debug("使用數據源:" + selectParams.getDcName());
            DynamicDataSourceContextHolder.setDataSourceType(selectParams.getDcName());
        }
    }

    @After("@annotation(targetDataSource)")
    public void clearDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
        Object[] str= joinPoint.getArgs();
        SelectParam selectParams = (SelectParam) str[0];
        logger.debug("清除數據源 " + selectParams.getDcName()+ " !");
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}

6. Mapper下方法 添加 TargetDataSource 注解

import com.kunlun.es.config.TargetDataSource;
import com.kunlun.es.vo.SelectParam;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;
import java.util.Map;

/**
 * @Author zhaozhiguo
 * @Date 2020-11-04
 * @Description 動態數據源通知
 */

@Mapper
public interface SelectObjMapper {

    @TargetDataSource
    @Select("${selectParam.sql}")
    List<Map> selectObj(@Param("selectParam") SelectParam selectParam);
}

7. 啟動類,需要添加@Import(DynamicDataSourceRegister.class)

import com.kunlun.es.config.DynamicDataSourceRegister;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

/**
 * @Author zhaozhiguo
 * @Date 2020-11-03
 * @Description 啟動類
 */
@SpringBootApplication
@Import(DynamicDataSourceRegister.class)
public class EsSelectApplication {

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

8. 查詢 接口暴露

import com.kunlun.es.service.SelectObjService;
import com.kunlun.es.vo.SelectParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

/**
 * @Author zhaozhiguo
 * @Date 2020-11-04
 * @Description 查詢接口
 */
@RestController
public class SelectObjController {

    @Autowired
    private SelectObjService selectObjService;


    @PostMapping("/selectObj")
    public List<Map> selectObj(@RequestBody SelectParam selectParam) {
        return selectObjService.selectObj(selectParam);
    }
}

9. 調用接口,大工告成!

源碼就不上傳了,整體實現思路還是比較清楚的,jar包是淘寶花了0.56 塊大洋代下的 (此處吐槽CSDN 這個包要46積分)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM