成人在线亚洲_国产日韩视频一区二区三区_久久久国产精品_99国内精品久久久久久久

您的位置:首頁技術文章
文章詳情頁

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

瀏覽:130日期:2023-03-17 16:03:08

通常業務開發中,我們會使用到多個數據源,比如,部分數據存在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); }}

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

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

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})public @interface DBKey { String DEFAULT = 'default'; // 默認數據庫節點 String value() default DEFAULT;}

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

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

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

public class DBContextHolder { private static final ThreadLocal<String> DB_KEY_CONTEXT = new ThreadLocal<>(); //在app啟動時就加載全部數據源,不需要考慮并發 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());}//方法上存在注解,使用方法定義的datasourceif (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; }}

既然在執行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')@Datapublic 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。具體代碼就不貼了。這樣一來,我們就完成了一個基于注解的支持多數據源切換的中間件。

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

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接口的時候,才會走反射調用的邏輯去獲取對應的數據源,后續,都會走本地緩存,提升了性能。

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

標簽: Spring
相關文章:
成人在线亚洲_国产日韩视频一区二区三区_久久久国产精品_99国内精品久久久久久久
亚洲另类在线一区| 精品国产百合女同互慰| 日韩一区国产二区欧美三区| 午夜视频在线观看一区二区| 狠狠久久婷婷| 国产日韩精品一区二区三区在线| 国产一区亚洲一区| 日本高清不卡视频| 夜夜嗨av一区二区三区中文字幕| 精品成人国产| 国产精品欧美极品| 欧美激情无毛| 国产日本欧洲亚洲| 成人性色生活片免费看爆迷你毛片| 欧美三级中文字幕在线观看| 秋霞午夜鲁丝一区二区老狼| 色综合久久88色综合天天 | 色婷婷激情综合| 午夜视频在线观看一区二区 | 韩国av一区二区三区在线观看| 久久激情综合| 亚洲成a人在线观看| 国产精品免费一区二区三区在线观看| 国产精品久久国产精麻豆99网站| 国产精品网站一区| 在线观看www91| 狠久久av成人天堂| 欧美日韩影院| 国产亚洲美州欧州综合国| 一区二区三区免费看视频| 久久久综合网| 日本不卡一二三| 亚洲人成高清| thepron国产精品| 国产精品久久久久久久午夜片 | 欧美精品乱码久久久久久按摩| 国产成人av电影| 日韩午夜电影av| 亚洲性感激情| 天天亚洲美女在线视频| 亚洲一区二区三区影院| 国产精品久久久久久久免费软件 | 91丨九色porny丨蝌蚪| 久久亚区不卡日本| 91啪在线观看| 久久午夜老司机| 亚洲图色在线| 一区二区三区不卡在线观看| 久久精品五月婷婷| 免费在线看一区| 91精品国产综合久久精品app| 成人在线综合网站| 国产亚洲综合色| 亚洲精品孕妇| 亚洲女与黑人做爰| 国产精品老牛| 久久国内精品视频| 欧美美女bb生活片| 风间由美中文字幕在线看视频国产欧美| 久久综合九色欧美综合狠狠| 欧美激情五月| 亚洲综合自拍偷拍| 91行情网站电视在线观看高清版| 国产精品一区二区在线看| 国产午夜精品久久久久久久 | 欧美日韩一卡二卡三卡 | 日韩制服丝袜先锋影音| 8x8x8国产精品| 成人黄动漫网站免费app| 亚洲国产精品成人久久综合一区| 99精品福利视频| 日本aⅴ免费视频一区二区三区| 欧美一区二区三区白人| 欧美日韩免费观看一区=区三区| 樱花影视一区二区| 欧美午夜一区二区三区| 波多野结衣亚洲| 亚洲精品国产成人久久av盗摄| 欧美亚洲动漫精品| 成人高清免费在线播放| 亚洲欧美一区二区在线观看| 久久激情婷婷| 国产成人一级电影| 亚洲国产成人午夜在线一区| 噜噜噜91成人网| 国产麻豆成人传媒免费观看| 国产日韩欧美亚洲| 麻豆精品91| 成人午夜av在线| 一区二区三区成人在线视频| 欧美一区二区在线播放| 狠狠色综合一区二区| 蜜臀av在线播放一区二区三区| 欧美成人一级视频| 99国内精品久久久久久久软件| 久久精品72免费观看| 国产三级精品三级在线专区| 久久午夜精品| 91在线国产福利| 亚洲v中文字幕| 欧美va在线播放| 国产亚洲精品自拍| 丁香天五香天堂综合| 一区二区三区成人| 精品成人在线观看| 久久国产精品亚洲va麻豆| www.欧美日韩国产在线| 亚洲va在线va天堂| 久久久国产精品不卡| 欧美视频完全免费看| 国产精品mm| 国产美女视频91| 亚洲精品美国一| 日韩一级黄色大片| 香蕉视频成人在线观看 | 国产a久久麻豆| 无码av免费一区二区三区试看 | 欧美日韩亚洲一区三区| 国产尤物一区二区| 亚洲国产欧美日韩另类综合| 久久久不卡影院| 欧美午夜精品理论片a级按摩| 激情欧美丁香| 国产精品66部| 午夜一区二区三区视频| 国产片一区二区| 欧美一区二区网站| 欧美中文字幕亚洲一区二区va在线| 欧美日韩国产精品一卡| 国产一区二区在线电影| 亚洲高清视频的网址| 久久久不卡影院| 51精品久久久久久久蜜臀| 一区二区三区欧美成人| 99精品欧美一区二区三区综合在线| 蜜臀久久99精品久久久画质超高清| 亚洲特级片在线| 久久婷婷国产综合国色天香| 欧美亚洲综合在线| 中文亚洲欧美| 亚洲欧美综合一区| 懂色av一区二区夜夜嗨| 蜜臀久久99精品久久久久宅男 | 免费欧美日韩国产三级电影| 一区二区三区日韩精品| 日本一区二区三区dvd视频在线| 欧美一级欧美三级在线观看| 欧美亚洲免费在线一区| 免费亚洲一区| 一区在线视频| 欧美精品综合| 欧美在线三区| 不卡影院免费观看| 国产乱码精品一区二区三区忘忧草 | 亚洲视频在线二区| 欧美一区二区在线| 成人看片黄a免费看在线| 黑人精品欧美一区二区蜜桃| 午夜视频久久久久久| 亚洲精品伦理在线| 国产精品久99| 中文字幕精品三区| 久久―日本道色综合久久| 日韩一级完整毛片| 91精品国产福利| 欧美丰满一区二区免费视频| 欧美亚洲国产一卡| 久久久久国产精品一区二区| 亚洲三级观看| 在线观看欧美亚洲| 欧美女人交a| 欧美不卡福利| 女人天堂亚洲aⅴ在线观看| 成人美女视频在线观看| 成人精品视频一区二区三区尤物| 国产精品99久久久久久久vr| 国产乱妇无码大片在线观看| 国内精品伊人久久久久av影院| 久久99在线观看| 精品一区二区三区在线播放 | 欧美另类亚洲| 欧美日韩精品免费观看视一区二区| 91一区在线观看| 99久久精品国产观看| 91亚洲永久精品| 欧美精品国产| 激情久久久久| 影音先锋亚洲电影| 一本色道久久综合亚洲精品婷婷| 99成人免费视频| 先锋影音一区二区三区| 色妞www精品视频| 欧美影院一区二区| 欧美日韩另类一区| 欧美一级在线免费| 久久综合久久久久88| 欧美国产精品一区二区| 亚洲色图欧美激情| 亚洲一区二区中文在线| 日本最新不卡在线|