欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于注解的springboot+mybatis的多數據源組件的實現代碼

 更新時間:2021年04月16日 08:36:41   作者:jy的blog  
這篇文章主要介紹了基于注解的springboot+mybatis的多數據源組件的實現,會使用到多個數據源,文中通過代碼講解的非常詳細,需要的朋友可以參考下

通常業(yè)務開發(fā)中,我們會使用到多個數據源,比如,部分數據存在mysql實例中,部分數據是在oracle數據庫中,那這時候,項目基于springboot和mybatis,其實只需要配置兩個數據源即可,只需要按照

dataSource -SqlSessionFactory - SqlSessionTemplate配置好就可以了。

如下代碼,首先我們配置一個主數據源,通過@Primary注解標識為一個默認數據源,通過配置文件中的spring.datasource作為數據源配置,生成SqlSessionFactoryBean,最終,配置一個SqlSessionTemplate。

@Configuration
@MapperScan(basePackages = "com.xxx.mysql.mapper", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {

    @Bean(name = "primaryDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druid() {
        return new DruidDataSource();
    }

    @Bean(name = "primarySqlSessionFactory")
    @Primary
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
        return bean.getObject();
    }

    @Bean("primarySqlSessionTemplate")
    @Primary
    public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sessionFactory) {
        return new SqlSessionTemplate(sessionFactory);
    }
}

然后,按照相同的流程配置一個基于oracle的數據源,通過注解配置basePackages掃描對應的包,實現特定的包下的mapper接口,使用特定的數據源。

@Configuration
@MapperScan(basePackages = "com.nbclass.oracle.mapper", sqlSessionFactoryRef = "oracleSqlSessionFactory")
public class OracleDataSourceConfig {

    @Bean(name = "oracleDataSource")
    @ConfigurationProperties(prefix = "spring.secondary")
    public DataSource oracleDruid(){
        return new DruidDataSource();
    }

    @Bean(name = "oracleSqlSessionFactory")
    public SqlSessionFactory oracleSqlSessionFactory(@Qualifier("oracleDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:oracle/mapper/*.xml"));
        return bean.getObject();
    }

    @Bean("oracleSqlSessionTemplate")
    public SqlSessionTemplate oracleSqlSessionTemplate(@Qualifier("oracleSqlSessionFactory") SqlSessionFactory sessionFactory) {
        return new SqlSessionTemplate(sessionFactory);
    }
}

這樣,就實現了一個工程下使用多個數據源的功能,對于這種實現方式,其實也足夠簡單了,但是如果我們的數據庫實例有很多,并且每個實例都主從配置,那這里維護起來難免會導致包名過多,不夠靈活。

現在考慮實現一種對業(yè)務侵入足夠小,并且能夠在mapper方法粒度上去支持指定數據源的方案,那自然而然想到了可以通過注解來實現,首先,自定義一個注解@DBKey:

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

    String DEFAULT = "default"; // 默認數據庫節(jié)點

    String value() default DEFAULT;
}

思路和上面基于springboot原生的配置的類似,首先定義一個默認的數據庫節(jié)點,當mapper接口方法/類沒有指定任何注解的時候,默認走這個節(jié)點,注解支持傳入value參數表示選擇的數據源節(jié)點名稱。至于注解的實現邏輯,可以通過反射來獲取mapper接口方法/類的注解值,然后指定特定的數據源。

那在什么時候執(zhí)行這個操作獲取呢?可以考慮使用spring AOP織入mapper層,在切入點執(zhí)行具體mapper方法之前,將對應的數據源配置放入threaLocal中,有了這個邏輯,立即動手實現:

首先,定義一個db配置的上下文對象。維護所有的數據源key實例,以及當前線程使用的數據源key:

public class DBContextHolder {

    private static final ThreadLocal<String> DB_KEY_CONTEXT = new ThreadLocal<>();

    //在app啟動時就加載全部數據源,不需要考慮并發(fā)
    private static Set<String> allDBKeys = new HashSet<>();

    public static String getDBKey() {
        return DB_KEY_CONTEXT.get();
    }

    public static void setDBKey(String dbKey) {
        //key必須在配置中
        if (containKey(dbKey)) {
            DB_KEY_CONTEXT.set(dbKey);
        } else {
            throw new KeyNotFoundException("datasource[" + dbKey + "] not found!");
        }
    }

    public static void addDBKey(String dbKey) {
        allDBKeys.add(dbKey);
    }

    public static boolean containKey(String dbKey) {
        return allDBKeys.contains(dbKey);
    }

    public static void clear() {
        DB_KEY_CONTEXT.remove();
    }
}

然后,定義切點,在切點before方法中,根據當前mapper接口的@@DBKey注解來選取對應的數據源key:

@Aspect
@Order(Ordered.LOWEST_PRECEDENCE - 1)
public class DSAdvice implements BeforeAdvice {

    @Pointcut("execution(* com.xxx..*.repository.*.*(..))")
    public void daoMethod() {
    }

    @Before("daoMethod()")
    public void beforeDao(JoinPoint point) {
        try {
            innerBefore(point, false);
        } catch (Exception e) {
            logger.error("DefaultDSAdviceException",
                    "Failed to set database key,please resolve it as soon as possible!", e);
        }
    }

    /**
     * @param isClass 攔截類還是接口
     */
    public void innerBefore(JoinPoint point, boolean isClass) {
        String methodName = point.getSignature().getName();

        Class<?> clazz = getClass(point, isClass);
        //使用默認數據源
        String dbKey = DBKey.DEFAULT;
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
        Method method = null;
        try {
            method = clazz.getMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("can't find " + methodName + " in " + clazz.toString());
        }
        //方法上存在注解,使用方法定義的datasource
        if (method.isAnnotationPresent(DBKey.class)) {
            DBKey key = method.getAnnotation(DBKey.class);
            dbKey = key.value();
        } else {
            //方法上不存在注解,使用類上定義的注解
            clazz = method.getDeclaringClass();
            if (clazz.isAnnotationPresent(DBKey.class)) {
                DBKey key = clazz.getAnnotation(DBKey.class);
                dbKey = key.value();
            }
        }
        DBContextHolder.setDBKey(dbKey);
    }


    private Class<?> getClass(JoinPoint point, boolean isClass) {
        Object target = point.getTarget();
        String methodName = point.getSignature().getName();

        Class<?> clazz = target.getClass();
        if (!isClass) {
            Class<?>[] clazzList = target.getClass().getInterfaces();

            if (clazzList == null || clazzList.length == 0) {
                throw new MutiDBException("找不到mapper class,methodName =" + methodName);
            }
            clazz = clazzList[0];
        }

        return clazz;
    }
}

既然在執(zhí)行mapper之前,該mapper接口最終使用的數據源已經被放入threadLocal中,那么,只需要重寫新的路由數據源接口邏輯即可:

public class RoutingDatasource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        String dbKey = DBContextHolder.getDBKey();
        return dbKey;
    }

    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        for (Object key : targetDataSources.keySet()) {
            DBContextHolder.addDBKey(String.valueOf(key));
        }
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }
}

另外,我們在服務啟動,配置mybatis的時候,將所有的db配置加載:

@Bean
    @ConditionalOnMissingBean(DataSource.class)
    @Autowired
    public DataSource dataSource(MybatisProperties mybatisProperties) {
        Map<Object, Object> dsMap = new HashMap<>(mybatisProperties.getNodes().size());
        for (String nodeName : mybatisProperties.getNodes().keySet()) {
            dsMap.put(nodeName, buildDataSource(nodeName, mybatisProperties));
            DBContextHolder.addDBKey(nodeName);
        }
        RoutingDatasource dataSource = new RoutingDatasource();
        dataSource.setTargetDataSources(dsMap);
        if (null == dsMap.get(DBKey.DEFAULT)) {
            throw new RuntimeException(
                    String.format("Default DataSource [%s] not exists", DBKey.DEFAULT));
        }
        dataSource.setDefaultTargetDataSource(dsMap.get(DBKey.DEFAULT));
        return dataSource;
    }



@ConfigurationProperties(prefix = "mybatis")
@Data
public class MybatisProperties {

    private Map<String, String> params;

    private Map<String, Object> nodes;

    /**
     * mapper文件路徑:多個location以,分隔
     */
    private String mapperLocations = "classpath*:com/iqiyi/xiu/**/mapper/*.xml";

    /**
     * Mapper類所在的base package
     */
    private String basePackage = "com.iqiyi.xiu.**.repository";

    /**
     * mybatis配置文件路徑
     */
    private String configLocation = "classpath:mybatis-config.xml";
}

那threadLocal中的key什么時候進行銷毀呢,其實可以自定義一個基于mybatis的攔截器,在攔截器中主動調DBContextHolder.clear()方法銷毀這個key。具體代碼就不貼了。這樣一來,我們就完成了一個基于注解的支持多數據源切換的中間件。

那有沒有可以優(yōu)化的點呢?其實,可以發(fā)現,在獲取mapper接口/所在類的注解的時候,使用了反射來獲取的,那我們知道一般反射調用是比較耗性能的,所以可以考慮在這里加個本地緩存來優(yōu)化下性能:

private final static Map<String, String> METHOD_CACHE = new ConcurrentHashMap<>();
//....
public void innerBefore(JoinPoint point, boolean isClass) {
        String methodName = point.getSignature().getName();

        Class<?> clazz = getClass(point, isClass);
        //key為類名+方法名
        String keyString = clazz.toString() + methodName;
        //使用默認數據源
        String dbKey = DBKey.DEFAULT;
        //如果緩存中已經有這個mapper方法對應的數據源的key,那直接設置
        if (METHOD_CACHE.containsKey(keyString)) {
            dbKey = METHOD_CACHE.get(keyString);
        } else {
            Class<?>[] parameterTypes =
                    ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
            Method method = null;

            try {
                method = clazz.getMethod(methodName, parameterTypes);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("can't find " + methodName + " in " + clazz.toString());
            }
             //方法上存在注解,使用方法定義的datasource
            if (method.isAnnotationPresent(DBKey.class)) {
                DBKey key = method.getAnnotation(DBKey.class);
                dbKey = key.value();
            } else {
                clazz = method.getDeclaringClass();
                //使用類上定義的注解
                if (clazz.isAnnotationPresent(DBKey.class)) {
                    DBKey key = clazz.getAnnotation(DBKey.class);
                    dbKey = key.value();
                }
            }
           //先放本地緩存
            METHOD_CACHE.put(keyString, dbKey);
        }
        DBContextHolder.setDBKey(dbKey);
    }

這樣一來,只有在第一次調用這個mapper接口的時候,才會走反射調用的邏輯去獲取對應的數據源,后續(xù),都會走本地緩存,提升了性能。

到此這篇關于基于注解的springboot+mybatis的多數據源組件的實現代碼的文章就介紹到這了,更多相關springboot mybatis多數據源組件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • java實現簡單五子棋小游戲(2)

    java實現簡單五子棋小游戲(2)

    這篇文章主要為大家詳細介紹了java實現簡單五子棋小游戲的第二部分,添加游戲結束條件,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Java方法遞歸調用實例解析

    Java方法遞歸調用實例解析

    這篇文章主要介紹了Java方法遞歸調用實例解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-02-02
  • java中staticclass靜態(tài)類詳解

    java中staticclass靜態(tài)類詳解

    這篇文章主要介紹了java中staticclass靜態(tài)類詳解,具有一定借鑒價值,需要的朋友可以了解下。
    2017-12-12
  • SpringBoot同一接口多個實現類配置的實例詳解

    SpringBoot同一接口多個實現類配置的實例詳解

    這篇文章主要介紹了SpringBoot同一接口多個實現類配置的實例詳解,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • java秒殺之redis限流操作詳解

    java秒殺之redis限流操作詳解

    這篇文章主要為大家詳細介紹了java秒殺之redis限流操作,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • Java實現API sign簽名校驗的方法詳解

    Java實現API sign簽名校驗的方法詳解

    為了防止中間人攻擊,有時我們需要進行API sign 簽名校驗。本文將用Java語言實現API sign 簽名校驗,感興趣的小伙伴可以嘗試一下
    2022-07-07
  • 簡單了解4種分布式session解決方案

    簡單了解4種分布式session解決方案

    這篇文章主要介紹了簡單了解4種分布式session解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-04-04
  • springboot中使用groovy的示例代碼

    springboot中使用groovy的示例代碼

    Groovy就是一種繼承了動態(tài)語言的優(yōu)良特性并運行在JVM上的編程語言,Groovy支持動態(tài)輸入,閉包,元編程,運算符重載等等語法,這篇文章主要介紹了springboot中使用groovy的相關知識,需要的朋友可以參考下
    2022-09-09
  • Java 數組聲明、創(chuàng)建、初始化詳解

    Java 數組聲明、創(chuàng)建、初始化詳解

    本文主要介紹Java 數組聲明、創(chuàng)建、初始化的資料,這里整理相關知識,及簡單實現代碼,幫助大家學習,有興趣的小伙伴可以參考下
    2016-09-09
  • Java數組,去掉重復值、增加、刪除數組元素的實現方法

    Java數組,去掉重復值、增加、刪除數組元素的實現方法

    下面小編就為大家?guī)硪黄狫ava數組,去掉重復值、增加、刪除數組元素的實現方法。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-08-08

最新評論