MyBatis使用Zookeeper保存數(shù)據(jù)庫的配置可動(dòng)態(tài)刷新的實(shí)現(xiàn)代碼
核心關(guān)鍵點(diǎn): 封裝一個(gè)DataSource, 重寫 getConnection 就可以實(shí)現(xiàn)
我們一步一步來看.
環(huán)境: Spring Cloud + MyBatis
MyBatis常規(guī)方式下配置數(shù)據(jù)源: 使用Spring的Configuration
package com.cnscud.cavedemo.fundmain.config; import com.cnscud.xpower.dbn.SimpleDBNDataSourceFactory; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; /** * Database Config 多數(shù)據(jù)源配置: 主數(shù)據(jù)源. * * @author Felix Zhang 2021-08-02 17:30 * @version 1.0.0 */ @Configuration @MapperScan(basePackages = {"com.cnscud.cavedemo.fundmain.dao"}, sqlSessionFactoryRef = "sqlSessionFactoryMainDataSource") public class MainDataSourceConfig { //常規(guī)配置: 使用application.yml里面的配置. @Primary @Bean(name = "mainDataSource") @ConfigurationProperties("spring.datasource.main") public DataSource mainDataSource() throws Exception { return DataSourceBuilder.create().build(); } @Primary @Bean(name = "sqlSessionFactoryMainDataSource") public SqlSessionFactory sqlSessionFactoryMainDataSource(@Qualifier("mainDataSource") DataSource mainDataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); //org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); //configuration.setMapUnderscoreToCamelCase(true); //factoryBean.setConfiguration(configuration); factoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml")); // 使用mainDataSource數(shù)據(jù)源, 連接mainDataSource庫 factoryBean.setDataSource(mainDataSource); //下邊兩句僅僅用于*.xml文件,如果整個(gè)持久層操作不需要使用到xml文件的話(只用注解就可以搞定),則不加 //指定entity和mapper xml的路徑 //factoryBean.setTypeAliasesPackage("com.cnscud.cavedemo.fundmain.model"); factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/cnscud/cavedemo/fundmain/mapper/*.xml")); return factoryBean.getObject(); } @Primary @Bean public SqlSessionTemplate sqlSessionTemplateMainDataSource(@Qualifier("sqlSessionFactoryMainDataSource") SqlSessionFactory sqlSessionTemplateMainDataSource) throws Exception { //使用注解中配置的Factory return new SqlSessionTemplate(sqlSessionTemplateMainDataSource); } @Primary @Bean public PlatformTransactionManager mainTransactionManager(@Qualifier("mainDataSource") DataSource prodDataSource) { return new DataSourceTransactionManager(prodDataSource); } }
這里面獲取數(shù)據(jù)源的關(guān)鍵函數(shù)是 mainDataSource, 我們自己來實(shí)現(xiàn)就好了:
因?yàn)檫@個(gè)是一次性的工作, 所以我們無法修改DataSource的指向, 只能在DataSource內(nèi)部做文章, 所以我們需要自己實(shí)現(xiàn)一個(gè)DataSource.
其中的步驟比較多, 我們來看看最終結(jié)果:
最終的DataSourceWrapper
它完全封裝了一個(gè)DataSource, 自己并沒有任何DataSource的功能:
package com.cnscud.xpower.dbn; import javax.sql.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; /** * Datasource wrapper, 為了方便動(dòng)態(tài)創(chuàng)建DataSource. * * @author Felix Zhang 2021-08-05 14:14 * @version 1.0.0 */ public class DynamicByZookeeperDataSourceWrapper implements DataSource { protected SimpleDBNConnectionPool simpleDBNConnectionPool; protected String bizName; public DynamicByZookeeperDataSourceWrapper(SimpleDBNConnectionPool simpleDBNConnectionPool, String bizName) { this.simpleDBNConnectionPool = simpleDBNConnectionPool; this.bizName = bizName; } protected DataSource pickDataSource() throws SQLException{ return simpleDBNConnectionPool.getDataSource(bizName); } @Override public Connection getConnection() throws SQLException { return pickDataSource().getConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return pickDataSource().getConnection(username, password); } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return pickDataSource().unwrap(iface); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return pickDataSource().isWrapperFor(iface); } @Override public PrintWriter getLogWriter() throws SQLException { return pickDataSource().getLogWriter(); } @Override public void setLogWriter(PrintWriter out) throws SQLException { pickDataSource().setLogWriter(out); } @Override public void setLoginTimeout(int seconds) throws SQLException { pickDataSource().setLoginTimeout(seconds); } @Override public int getLoginTimeout() throws SQLException { return pickDataSource().getLoginTimeout(); } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException(); } }
SimpleDBNConnectionPool
支持多個(gè)數(shù)據(jù)源的暫存池, 可以根據(jù)name獲取不同的數(shù)據(jù)庫DataSource實(shí)例:
這個(gè)類負(fù)責(zé)創(chuàng)建DataSource, 保存在Map里. 并且能監(jiān)聽Zookeeper的變化, 一旦偵聽到變化, 就close現(xiàn)有的DataSource.
package com.cnscud.xpower.dbn; import com.github.zkclient.IZkDataListener; import com.zaxxer.hikari.HikariDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import static java.lang.String.format; /** * The simple datasource pool. * * 根據(jù)名字存放多個(gè)數(shù)據(jù)庫的DataSource, 并且會(huì)監(jiān)聽Zookeeper配置, 動(dòng)態(tài)重建. * * @author adyliu (imxylz@gmail.com) * @since 2011-7-27 */ public class SimpleDBNConnectionPool { final Logger logger = LoggerFactory.getLogger(getClass()); private Map<String, DataSource> instances = new ConcurrentHashMap<>(); private final Set<String> watcherSchema = new HashSet<String>(); public DataSource getInstance(String bizName) { try { return findDbInstance(bizName); } catch (SQLException e) { e.printStackTrace(); } return null; } public Connection getConnection(String bizName) throws SQLException { DataSource ds = getDataSource(bizName); return ds.getConnection(); } public DataSource getDataSource(String bizName) throws SQLException { return findDbInstance(bizName); } protected void destroyInstance(final String bizName) { synchronized (instances) { DataSource oldInstanceIf = instances.remove(bizName); logger.warn(format("destoryInstance %s and %s", bizName, oldInstanceIf != null ? "close datasource" : "do nothing")); if (oldInstanceIf != null) { closeDataSource(oldInstanceIf); } } } protected void closeDataSource(DataSource ds) { if (ds instanceof HikariDataSource) { try { ((HikariDataSource) ds).close(); } catch (Exception e) { logger.error("Close datasource failed. ", e); } } } private DataSource createInstance(Map<String, String> dbcfg) { return new SimpleDataSourceBuilder().buildDataSource(dbcfg); } private DataSource findDbInstance(final String bizName) throws SQLException { DataSource ins = instances.get(bizName); if (ins != null) { return ins; } synchronized (instances) {// 同步操作 ins = instances.get(bizName); if (ins != null) { return ins; } boolean success = false; try { Map<String, String> dbcfg = SchemeNodeHelper.getInstance(bizName); if (dbcfg == null) { throw new SQLException("No such datasouce: " + bizName); } ins = createInstance(dbcfg); //log.warn("ins put "+ins); instances.put(bizName, ins); if (watcherSchema.add(bizName)) { SchemeNodeHelper.watchInstance(bizName, new IZkDataListener() { public void handleDataDeleted(String dataPath) throws Exception { logger.warn(dataPath + " was deleted, so destroy the bizName " + bizName); destroyInstance(bizName); } public void handleDataChange(String dataPath, byte[] data) throws Exception { logger.warn(dataPath + " was changed, so destroy the bizName " + bizName); destroyInstance(bizName); } }); } success = true; } catch (SQLException e) { throw e; } catch (Throwable t) { throw new SQLException("cannot build datasource for bizName: " + bizName, t); } finally { if (!success) { instances.remove(bizName); } } } return ins; } }
真正創(chuàng)建DataSource的代碼:
package com.cnscud.xpower.dbn; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import org.apache.commons.lang.StringUtils; import java.util.Map; /** * Hikari DataSource. * * 思考: 可以根據(jù)參數(shù)里面的類型來使用不同的庫創(chuàng)建DataSource, 例如Druid. (默認(rèn)為HikariDataSource) * * * @author Felix Zhang 2021-08-05 11:14 * @version 1.0.0 */ public class SimpleDataSourceBuilder { public HikariDataSource buildDataSource(Map<String, String> args) { HikariConfig config = new HikariConfig(); config.setJdbcUrl(getUrl(args)); config.setUsername(args.get("username")); config.setPassword(args.get("password")); config.setDriverClassName(getDriverClassName(args)); String maximumPoolSizeKey = "maximum-pool-size"; int maximumPoolSize = 30; if(StringUtils.isNotEmpty(args.get(maximumPoolSizeKey))){ maximumPoolSize = Integer.parseInt(args.get(maximumPoolSizeKey)); } config.addDataSourceProperty("cachePrepStmts", "true"); //是否自定義配置,為true時(shí)下面兩個(gè)參數(shù)才生效 config.addDataSourceProperty("prepStmtCacheSize", maximumPoolSize); //連接池大小默認(rèn)25,官方推薦250-500 config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); //單條語句最大長(zhǎng)度默認(rèn)256,官方推薦2048 config.addDataSourceProperty("useServerPrepStmts", "true"); //新版本MySQL支持服務(wù)器端準(zhǔn)備,開啟能夠得到顯著性能提升 config.addDataSourceProperty("useLocalSessionState", "true"); config.addDataSourceProperty("useLocalTransactionState", "true"); config.addDataSourceProperty("rewriteBatchedStatements", "true"); config.addDataSourceProperty("cacheResultSetMetadata", "true"); config.addDataSourceProperty("cacheServerConfiguration", "true"); config.addDataSourceProperty("elideSetAutoCommits", "true"); config.addDataSourceProperty("maintainTimeStats", "false"); config.setMaximumPoolSize(maximumPoolSize); // config.setMinimumIdle(10);//最小閑置連接數(shù),默認(rèn)為0 config.setMaxLifetime(600000);//最大生存時(shí)間 config.setConnectionTimeout(30000);//超時(shí)時(shí)間30秒 config.setIdleTimeout(60000); config.setConnectionTestQuery("select 1"); return new HikariDataSource(config); } private String getDriverClassName(Map<String, String> args) { return args.get("driver-class-name"); } private String getUrl(Map<String, String> args) { return args.get("jdbc-url") == null ? args.get("url"): args.get("jdbc-url"); } }
為了方便讀取Zookeeper節(jié)點(diǎn), 還有個(gè)SchemeNodeHelper:
支持兩種配置文件的方式 json或者Properties格式:
package com.cnscud.xpower.dbn; import com.cnscud.xpower.configcenter.ConfigCenter; import com.cnscud.xpower.utils.Jsons; import com.github.zkclient.IZkDataListener; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.StringReader; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 從Zookeeper的 /xpower/dbn節(jié)點(diǎn)下讀取數(shù)據(jù)庫配置. * 內(nèi)容支持兩種格式: json或者properties格式. * * JSON格式如下: * { * "jdbc-url": "jdbc:mysql://127.0.0.1:3306/cavedemo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC", * "username": "dbuser", * "password": "yourpassword", * "driver-class-name": "com.mysql.cj.jdbc.Driver" * } * * Properties格式如下: * jdbc-url: jdbc:mysql://127.0.0.1:3306/cavedemo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC * username: dbuser * password: password * driver-class-name: com.mysql.cj.jdbc.Driver * * @author Felix Zhang * @since 2021-8-5 */ public class SchemeNodeHelper { static final Logger logger = LoggerFactory.getLogger(SchemeNodeHelper.class); //支持兩種格式: json, properties public static Map<String, String> getInstance(final String instanceName) throws Exception { String data = ConfigCenter.getInstance().getDataAsString("/xpower/dbn/" + instanceName); if(StringUtils.isEmpty(data)){ return null; } data = data.trim(); if (data.startsWith("{")) { //as json Map<String, String> swap = Jsons.fromJson(data, Map.class); Map<String, String> result = new HashMap<>(); if (swap != null) { for (String name : swap.keySet()) { result.put(name.toLowerCase(), swap.get(name)); } } return result; } else { //as properties Properties props = new Properties(); try { props.load(new StringReader(data)); } catch (IOException e) { logger.error("loading global config failed", e); } Map<String, String> result = new HashMap<>(); for (String name : props.stringPropertyNames()) { result.put(name.toLowerCase(), props.getProperty(name)); } return result; } } public static void watchInstance(final String bizName, final IZkDataListener listener) { final String path = "/xpower/dbn/" + bizName; ConfigCenter.getInstance().subscribeDataChanges(path, listener); } }
實(shí)際應(yīng)用
最后在MyBatis項(xiàng)目中, 替換原有MainDataSource代碼為:
/** * 添加@Primary注解,設(shè)置默認(rèn)數(shù)據(jù)源,事務(wù)管理器. * 此處使用了一個(gè)可以動(dòng)態(tài)重建的DataSource, 如果Zookeeper配置改變,會(huì)動(dòng)態(tài)重建. */ @Primary @Bean(name = "mainDataSource") public DataSource mainDataSource() throws Exception { return SimpleDBNDataSourceFactory.getInstance().getDataSource("cavedemo"); }
運(yùn)行項(xiàng)目, 發(fā)現(xiàn)可以連上數(shù)據(jù)庫, 并且不重啟項(xiàng)目的情況下, 動(dòng)態(tài)修改數(shù)據(jù)庫配置, 能自動(dòng)重連.
項(xiàng)目代碼:
https://github.com/cnscud/xpower/tree/main/xpower-main/src/main/java/com/cnscud/xpower/dbn
其中用到的 ConfigCenter 也在這個(gè)項(xiàng)目里, 也可以自己實(shí)現(xiàn), 就可以脫離本項(xiàng)目了.
本文來自博客園,作者:飛云~風(fēng)之谷,轉(zhuǎn)載請(qǐng)注明原文鏈接:https://www.cnblogs.com/cnscud/p/15103859.html
到此這篇關(guān)于MyBatis使用Zookeeper保存數(shù)據(jù)庫的配置,可動(dòng)態(tài)刷新的文章就介紹到這了,更多相關(guān)MyBatis使用Zookeeper保存數(shù)據(jù)庫的配置,可動(dòng)態(tài)刷新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
盤點(diǎn)總結(jié)SpringBoot自帶工具類使用提升開發(fā)效率
這篇文章主要為大家介紹了盤點(diǎn)總結(jié)SpringBoot自帶工具類使用提升開發(fā)效率,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Java中的List和MySQL中的varchar相互轉(zhuǎn)換的解決方案
實(shí)體類中有一個(gè) List<String> 類型的屬性,對(duì)應(yīng)于 MySQL 表里的 varchar 字段,使用 MyBatis 添加或查詢時(shí)能互相轉(zhuǎn)換,本文給大家介紹Java中的List和MySQL中的varchar相互轉(zhuǎn)換的解決方案,需要的朋友可以參考下2024-06-06Java動(dòng)態(tài)獲取實(shí)現(xiàn)某個(gè)接口下所有的實(shí)現(xiàn)類對(duì)象集合
今天小編就為大家分享一篇關(guān)于Java動(dòng)態(tài)獲取實(shí)現(xiàn)某個(gè)接口下所有的實(shí)現(xiàn)類對(duì)象集合,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12Maven打包SpringBoot工程的實(shí)現(xiàn)示例
在使用Spring Boot和Maven的項(xiàng)目中,你可以使用Maven來打包你的項(xiàng)目,本文主要介紹了Maven打包SpringBoot工程的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-05-05Spring切面優(yōu)先級(jí)與基于xml的AOP實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Spring切面的優(yōu)先級(jí)與基于xml的AOP的詳細(xì)步驟,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-11-11Java編程實(shí)現(xiàn)swing圓形按鈕實(shí)例代碼
這篇文章主要介紹了Java編程實(shí)現(xiàn)swing圓形按鈕實(shí)例代碼,涉及兩個(gè)簡(jiǎn)單的Java實(shí)現(xiàn)按鈕的代碼,其中一個(gè)具有偵測(cè)點(diǎn)擊事件的簡(jiǎn)單功能,具有一定借鑒價(jià)值,需要的朋友可以參考。2017-11-11SpringMVC轉(zhuǎn)發(fā)與重定向參數(shù)傳遞的實(shí)現(xiàn)詳解
這篇文章主要介紹了SpringMVC轉(zhuǎn)發(fā)與重定向參數(shù)傳遞,對(duì)于重定向,可以通過FlashMap或RedirectAttributes來在請(qǐng)求間傳遞數(shù)據(jù),因?yàn)橹囟ㄏ蛏婕皟蓚€(gè)獨(dú)立的HTTP請(qǐng)求,而轉(zhuǎn)發(fā)則在同一請(qǐng)求內(nèi)進(jìn)行,數(shù)據(jù)可以直接通過HttpServletRequest共享,需要的朋友可以參考下2022-07-07