Core key points: Encapsulate a DataSource and rewrite getConnection to achieve it
Let's take a look step by step.
Environment: Spring Cloud + MyBatis
Configuring data sources under MyBatis regular mode: Use Spring Configuration
package ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; /** * Database Config Multi-data source configuration: primary data source. * * @author Felix Zhang 2021-08-02 17:30 * @version 1.0.0 */ @Configuration @MapperScan(basePackages = {""}, sqlSessionFactoryRef = "sqlSessionFactoryMainDataSource") public class MainDataSourceConfig { //General configuration: Use the configuration inside. @Primary @Bean(name = "mainDataSource") @ConfigurationProperties("") public DataSource mainDataSource() throws Exception { return ().build(); } @Primary @Bean(name = "sqlSessionFactoryMainDataSource") public SqlSessionFactory sqlSessionFactoryMainDataSource(@Qualifier("mainDataSource") DataSource mainDataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); // configuration = new (); //(true); //(configuration); (new PathMatchingResourcePatternResolver().getResource("classpath:")); // Use mainDataSource data source to connect to mainDataSource library (mainDataSource); //The following two sentences are only used for *.xml files. If the entire persistence layer operation does not require the use of the xml file (it can be done with only annotations), then no additional //Specify the path of entity and mapper xml //(""); (new PathMatchingResourcePatternResolver().getResources("classpath:com/cnscud/cavedemo/fundmain/mapper/*.xml")); return (); } @Primary @Bean public SqlSessionTemplate sqlSessionTemplateMainDataSource(@Qualifier("sqlSessionFactoryMainDataSource") SqlSessionFactory sqlSessionTemplateMainDataSource) throws Exception { //Use the Factory configured in the annotation return new SqlSessionTemplate(sqlSessionTemplateMainDataSource); } @Primary @Bean public PlatformTransactionManager mainTransactionManager(@Qualifier("mainDataSource") DataSource prodDataSource) { return new DataSourceTransactionManager(prodDataSource); } }
The key function to obtain the data source here is mainDataSource, which we can implement by ourselves:
Because this is a one-time job, we cannot modify the direction of the DataSource, and can only do things inside the DataSource, so we need to implement a DataSource ourselves.
There are many steps, let’s take a look at the final result:
The final DataSourceWrapper
It completely encapsulates a DataSource, and it does not have any DataSource functions:
package ; import ; import ; import ; import ; import ; import ; /** * Datasource wrapper, for the convenience of dynamic creation of 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) { = simpleDBNConnectionPool; = bizName; } protected DataSource pickDataSource() throws SQLException{ return (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
Supports temporary storage pools for multiple data sources, and can obtain different database DataSource instances according to name:
This class is responsible for creating a DataSource, which is stored in a Map. It can monitor changes in Zookeeper. Once the changes are heard, it closes the existing DataSource.
package ; import ; import ; import org.; import org.; import ; import ; import ; import ; import ; import ; import ; import static ; /** * The simple datasource pool. * * Store DataSources of multiple databases according to the name, and will listen to Zookeeper configuration and dynamic reconstruction. * * @author adyliu (imxylz@) * @since 2011-7-27 */ public class SimpleDBNConnectionPool { final Logger logger = (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) { (); } return null; } public Connection getConnection(String bizName) throws SQLException { DataSource ds = getDataSource(bizName); return (); } public DataSource getDataSource(String bizName) throws SQLException { return findDbInstance(bizName); } protected void destroyInstance(final String bizName) { synchronized (instances) { DataSource oldInstanceIf = (bizName); (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) { ("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 = (bizName); if (ins != null) { return ins; } synchronized (instances) {// Synchronous operation ins = (bizName); if (ins != null) { return ins; } boolean success = false; try { Map<String, String> dbcfg = (bizName); if (dbcfg == null) { throw new SQLException("No such datasouce: " + bizName); } ins = createInstance(dbcfg); //("ins put "+ins); (bizName, ins); if ((bizName)) { (bizName, new IZkDataListener() { public void handleDataDeleted(String dataPath) throws Exception { (dataPath + " was deleted, so destroy the bizName " + bizName); destroyInstance(bizName); } public void handleDataChange(String dataPath, byte[] data) throws Exception { (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) { (bizName); } } } return ins; } }
The code to actually create a DataSource:
package ; import ; import ; import ; import ; /** * Hikari DataSource. * * Thinking: You can use different libraries to create DataSource according to the type in the parameter, such as Druid. (Default is 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(); (getUrl(args)); (("username")); (("password")); (getDriverClassName(args)); String maximumPoolSizeKey = "maximum-pool-size"; int maximumPoolSize = 30; if(((maximumPoolSizeKey))){ maximumPoolSize = ((maximumPoolSizeKey)); } ("cachePrepStmts", "true"); //Whether to customize the configuration, the following two parameters will take effect when true ("prepStmtCacheSize", maximumPoolSize); //The connection pool size is 25 by default, and the official recommendation is 250-500 ("prepStmtCacheSqlLimit", "2048"); //The maximum length of a single sentence is 256 by default, officially recommended 2048 ("useServerPrepStmts", "true"); //The new version of MySQL supports server-side preparation, and can achieve significant performance improvement by enabling enabled ("useLocalSessionState", "true"); ("useLocalTransactionState", "true"); ("rewriteBatchedStatements", "true"); ("cacheResultSetMetadata", "true"); ("cacheServerConfiguration", "true"); ("elideSetAutoCommits", "true"); ("maintainTimeStats", "false"); (maximumPoolSize); // (10);//The minimum number of idle connections, default is 0 (600000);//Maximum survival time (30000);//Timeout time 30 seconds (60000); ("select 1"); return new HikariDataSource(config); } private String getDriverClassName(Map<String, String> args) { return ("driver-class-name"); } private String getUrl(Map<String, String> args) { return ("jdbc-url") == null ? ("url"): ("jdbc-url"); } }
In order to facilitate reading of Zookeeper nodes, there is also a SchemeNodeHelper:
Supports two configuration files: json or Properties format:
package ; import ; import ; import ; import ; import org.; import org.; import ; import ; import ; import ; import ; /** * Read database configuration from Zookeeper's /xpower/dbn node. * The content supports two formats: json or properties format. * * The JSON format is as follows: * { * "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": "" * } * * Properties format is as follows: * 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: * * @author Felix Zhang * @since 2021-8-5 */ public class SchemeNodeHelper { static final Logger logger = (); //Support two formats: json, properties public static Map<String, String> getInstance(final String instanceName) throws Exception { String data = ().getDataAsString("/xpower/dbn/" + instanceName); if((data)){ return null; } data = (); if (("{")) { //as json Map<String, String> swap = (data, ); Map<String, String> result = new HashMap<>(); if (swap != null) { for (String name : ()) { ((), (name)); } } return result; } else { //as properties Properties props = new Properties(); try { (new StringReader(data)); } catch (IOException e) { ("loading global config failed", e); } Map<String, String> result = new HashMap<>(); for (String name : ()) { ((), (name)); } return result; } } public static void watchInstance(final String bizName, final IZkDataListener listener) { final String path = "/xpower/dbn/" + bizName; ().subscribeDataChanges(path, listener); } }
Practical application
Finally, in the MyBatis project, replace the original MainDataSource code as:
/** * Add @Primary annotation, set the default data source, transaction manager. * A DataSource that can be dynamically rebuilt is used here. If the Zookeeper configuration changes, it will be dynamically rebuilt. */ @Primary @Bean(name = "mainDataSource") public DataSource mainDataSource() throws Exception { return ().getDataSource("cavedemo"); }
When you run the project, you find that you can connect to the database and do not restart the project, you can dynamically modify the database configuration and automatically reconnect.
Project code:
/cnscud/xpower/tree/main/xpower-main/src/main/java/com/cnscud/xpower/dbn
The ConfigCenter used is also in this project, and can also be implemented by yourself, so you can get rid of this project.
This article comes from Blog Park, author:Flying Clouds ~ Valley of Wind, please indicate the original link when reprinting:/cnscud/p/
This is the article about MyBatis using Zookeeper to save database configuration and dynamic refresh. This is the end of this article. For more related MyBatis using Zookeeper to save database configuration and dynamic refresh content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!