最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于注解的springboot+mybatis的多數(shù)據(jù)源組件的實(shí)現(xiàn)代碼

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

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

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

如下代碼,首先我們配置一個(gè)主數(shù)據(jù)源,通過@Primary注解標(biāo)識(shí)為一個(gè)默認(rèn)數(shù)據(jù)源,通過配置文件中的spring.datasource作為數(shù)據(jù)源配置,生成SqlSessionFactoryBean,最終,配置一個(gè)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);
    }
}

然后,按照相同的流程配置一個(gè)基于oracle的數(shù)據(jù)源,通過注解配置basePackages掃描對(duì)應(yīng)的包,實(shí)現(xiàn)特定的包下的mapper接口,使用特定的數(shù)據(jù)源。

@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);
    }
}

這樣,就實(shí)現(xiàn)了一個(gè)工程下使用多個(gè)數(shù)據(jù)源的功能,對(duì)于這種實(shí)現(xiàn)方式,其實(shí)也足夠簡單了,但是如果我們的數(shù)據(jù)庫實(shí)例有很多,并且每個(gè)實(shí)例都主從配置,那這里維護(hù)起來難免會(huì)導(dǎo)致包名過多,不夠靈活。

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

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

    String DEFAULT = "default"; // 默認(rèn)數(shù)據(jù)庫節(jié)點(diǎn)

    String value() default DEFAULT;
}

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

那在什么時(shí)候執(zhí)行這個(gè)操作獲取呢?可以考慮使用spring AOP織入mapper層,在切入點(diǎn)執(zhí)行具體mapper方法之前,將對(duì)應(yīng)的數(shù)據(jù)源配置放入threaLocal中,有了這個(gè)邏輯,立即動(dòng)手實(shí)現(xiàn):

首先,定義一個(gè)db配置的上下文對(duì)象。維護(hù)所有的數(shù)據(jù)源key實(shí)例,以及當(dāng)前線程使用的數(shù)據(jù)源key:

public class DBContextHolder {

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

    //在app啟動(dòng)時(shí)就加載全部數(shù)據(jù)源,不需要考慮并發(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();
    }
}

然后,定義切點(diǎn),在切點(diǎn)before方法中,根據(jù)當(dāng)前mapper接口的@@DBKey注解來選取對(duì)應(yīng)的數(shù)據(jù)源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);
        //使用默認(rèn)數(shù)據(jù)源
        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接口最終使用的數(shù)據(jù)源已經(jīng)被放入threadLocal中,那么,只需要重寫新的路由數(shù)據(jù)源接口邏輯即可:

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();
    }
}

另外,我們?cè)诜?wù)啟動(dòng),配置mybatis的時(shí)候,將所有的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文件路徑:多個(gè)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什么時(shí)候進(jìn)行銷毀呢,其實(shí)可以自定義一個(gè)基于mybatis的攔截器,在攔截器中主動(dòng)調(diào)DBContextHolder.clear()方法銷毀這個(gè)key。具體代碼就不貼了。這樣一來,我們就完成了一個(gè)基于注解的支持多數(shù)據(jù)源切換的中間件。

那有沒有可以優(yōu)化的點(diǎn)呢?其實(shí),可以發(fā)現(xiàn),在獲取mapper接口/所在類的注解的時(shí)候,使用了反射來獲取的,那我們知道一般反射調(diào)用是比較耗性能的,所以可以考慮在這里加個(gè)本地緩存來優(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;
        //使用默認(rèn)數(shù)據(jù)源
        String dbKey = DBKey.DEFAULT;
        //如果緩存中已經(jīng)有這個(gè)mapper方法對(duì)應(yīng)的數(shù)據(jù)源的key,那直接設(shè)置
        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);
    }

這樣一來,只有在第一次調(diào)用這個(gè)mapper接口的時(shí)候,才會(huì)走反射調(diào)用的邏輯去獲取對(duì)應(yīng)的數(shù)據(jù)源,后續(xù),都會(huì)走本地緩存,提升了性能。

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

相關(guān)文章

  • Spring自帶的校驗(yàn)框架Validation的使用實(shí)例

    Spring自帶的校驗(yàn)框架Validation的使用實(shí)例

    今天小編就為大家分享一篇關(guān)于Spring自帶的校驗(yàn)框架Validation的使用實(shí)例,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • spring.datasource.schema配置詳解

    spring.datasource.schema配置詳解

    本文主要介紹了spring.datasource.schema配置,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • Spring Cloud項(xiàng)目前后端分離跨域的操作

    Spring Cloud項(xiàng)目前后端分離跨域的操作

    這篇文章主要介紹了Spring Cloud項(xiàng)目前后端分離跨域的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • java實(shí)現(xiàn)將字符串中首字母轉(zhuǎn)換成大寫,其它全部轉(zhuǎn)換成小寫的方法示例

    java實(shí)現(xiàn)將字符串中首字母轉(zhuǎn)換成大寫,其它全部轉(zhuǎn)換成小寫的方法示例

    這篇文章主要介紹了java實(shí)現(xiàn)將字符串中首字母轉(zhuǎn)換成大寫,其它全部轉(zhuǎn)換成小寫的方法,涉及java字符串遍歷、轉(zhuǎn)換、拼接等相關(guān)操作技巧,需要的朋友可以參考下
    2019-06-06
  • springboot的@Value中#和$區(qū)別詳解

    springboot的@Value中#和$區(qū)別詳解

    這篇文章主要介紹了springboot的@Value中#和$區(qū)別詳解,@Value注解的作用主要可以給屬性直接賦值、也可以讀取配置文件中的值給屬性賦值,需要的朋友可以參考下
    2023-11-11
  • java語言實(shí)現(xiàn)猜數(shù)字游戲

    java語言實(shí)現(xiàn)猜數(shù)字游戲

    這篇文章主要為大家詳細(xì)介紹了java語言實(shí)現(xiàn)猜數(shù)字游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • Java面向?qū)ο蟮姆庋b你了解嗎

    Java面向?qū)ο蟮姆庋b你了解嗎

    這篇文章主要為大家詳細(xì)介紹了Java面向?qū)ο蟮姆庋b,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • Springboot @Validated和@Valid的區(qū)別及使用詳解

    Springboot @Validated和@Valid的區(qū)別及使用詳解

    這篇文章主要介紹了Springboot @Validated和@Valid的區(qū)別及使用詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • 通過Java壓縮JavaScript代碼實(shí)例分享

    通過Java壓縮JavaScript代碼實(shí)例分享

    這篇文章主要介紹了通過Java壓縮JavaScript代碼實(shí)例分享,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-12-12
  • Springboot集成ProtoBuf的實(shí)例

    Springboot集成ProtoBuf的實(shí)例

    這篇文章主要介紹了Springboot集成ProtoBuf的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03

最新評(píng)論

彝良县| 陆川县| 彭山县| 浦北县| 扬中市| 盐亭县| 海盐县| 岳西县| 资讯 | 永靖县| 苗栗市| 衡南县| 平遥县| 德江县| 娄烦县| 湘潭市| 灵武市| 安化县| 高淳县| 五寨县| 南乐县| 黄大仙区| 平乡县| 海林市| 神农架林区| 襄垣县| 长治县| 吴忠市| 清镇市| 康马县| 永川市| 永嘉县| 绍兴县| 桃源县| 达拉特旗| 明溪县| 建德市| 蓝田县| 雅江县| 沭阳县| 蓬莱市|