亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁技術(shù)文章
文章詳情頁

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

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

通常業(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í)也足夠簡(jiǎn)單了,但是如果我們的數(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());}//方法上存在注解,使用方法定義的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; }}

既然在執(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')@Datapublic 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)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Spring
相關(guān)文章:
主站蜘蛛池模板: 停停五月天 | 久久综合给合久久狠狠狠色97 | 樱花aⅴ一区二区三区四区 樱花草在线社区www韩国 | 亚洲欧美久久精品一区 | 黄色片视频国产 | 欧美一级α片毛片免费观看 | 久视频在线 | 天堂亚洲国产日韩在线看 | 国产亚洲精品一区二区 | 国产精品久久久影院 | 亚洲日比视频 | 免费一级特黄特色大片在线观看看 | 亚洲欧美91| 国产精品嫩模在线播放 | 欧美一级毛片高清视频 | 国产精品久久久久久久久夜色 | 久久久91精品国产一区二区 | 日本老年人精品久久中文字幕 | 亚洲精品一区二区三区不卡 | 色拍拍在线精品视频 | 亚洲国产成人精品区 | 国产交换精品一区二区三区 | 91精品日本久久久久久牛牛 | 一级特黄女人生活片 | 亚洲国产精品综合欧美 | 美国特级成人毛片 | 在线观看国产一区二区三区99 | 手机看片自拍自拍自拍 | 国产精品一区二区三区四区五区 | 日韩精品a在线视频 | 国产精品不卡视频 | 亚洲国产一区在线观看 | 亚洲图片偷拍自拍 | 日韩亚洲欧美性感视频影片免费看 | 6080欧美一区二区三区四区 | 正在播放国产精品放孕妇 | 久久国产乱子伦精品免 | 免费看污的网站 | 国产精品性 | 欧美毛片 | 午夜视频在线观看国产www |