Taking several MySQL databases as an example, this paper adopts spring boot framework to integrate MyBatis to configure multiple data sources for database operation. In the actual project, in order to reduce the pressure on the database during the peak flow period, we can cache some database inert data (mainly for query, and infrequently updated data) into the JVM memory, which can respond quickly and reduce the pressure on the database.
Project source git address: https://github.com/piaoranyuji/muldb
1, MySQL table structure (double database)
Two databases, testmgmdb and testonldb, are shared in this project.
- The table creation statement of TABLE tbl? MGM? Menu in testmgdb database is as follows.
SET FOREIGN_KEY_CHECKS=0; DROP TABLE IF EXISTS `tbl_mgm_menu`; CREATE TABLE `tbl_mgm_menu` ( `menu_id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'Menu key', `menu_name` varchar(30) NOT NULL DEFAULT '' COMMENT 'Menu name', `menu_logo` varchar(64) NOT NULL DEFAULT '' COMMENT 'menu logo', `menu_url` varchar(64) NOT NULL DEFAULT '' COMMENT 'menu url', `menu_seq` int(2) NOT NULL DEFAULT '0' COMMENT 'Menu order', `rec_st` char(1) NOT NULL DEFAULT '1' COMMENT 'Record status, 0: invalid, 1: valid', `rec_crt_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time', `rec_upd_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update time', PRIMARY KEY (`menu_id`) ) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8; INSERT INTO `tbl_mgm_menu` VALUES ('23', 'Broadcast today', 'https://www.baidu.com', 'https://www.baidu.com', '0', '1', '2020-01-21 14:44:03', '2020-01-21 14:44:03');
- The table creation statement of TABLE tbl? Onl? SP in testonldb database is as follows.
SET FOREIGN_KEY_CHECKS=0; DROP TABLE IF EXISTS `tbl_onl_sp`; CREATE TABLE `tbl_onl_sp` ( `sp_id` char(16) NOT NULL DEFAULT '' COMMENT 'Service provider ID', `call_dt` char(8) NOT NULL DEFAULT '' COMMENT 'Interface call time( yyyyMMdd)', `sp_name` char(30) NOT NULL DEFAULT '' COMMENT 'Name of service provider', `rec_crt_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time', `rec_upd_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update time', PRIMARY KEY (`sp_id`,`call_dt`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2, SpringBoot configuration file configuration database connection information
spring.datasource.mgmdb.jdbc-url=jdbc:mysql://localhost:3306/testmgmdb?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false spring.datasource.mgmdb.username=root spring.datasource.mgmdb.password= spring.datasource.mgmdb.driver-class-name=com.mysql.jdbc.Driver spring.datasource.mgmdb.max-idle=10 spring.datasource.mgmdb.max-wait=10000 spring.datasource.mgmdb.min-idle=5 spring.datasource.mgmdb.initial-size=5 spring.datasource.mgmdb.maximum-pool-size=200 spring.datasource.mgmdb.validation-query=SELECT 1 spring.datasource.mgmdb.test-on-borrow=false spring.datasource.mgmdb.test-while-idle=true spring.datasource.mgmdb.time-between-eviction-runs-millis=18800 spring.datasource.onldb.jdbc-url=jdbc:mysql://localhost:3306/testonldb?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false spring.datasource.onldb.username=root spring.datasource.onldb.password= spring.datasource.onldb.driver-class-name=com.mysql.jdbc.Driver spring.datasource.onldb.max-idle=10 spring.datasource.onldb.max-wait=10000 spring.datasource.onldb.min-idle=5 spring.datasource.onldb.initial-size=5 spring.datasource.onldb.maximum-pool-size=200 spring.datasource.onldb.validation-query=SELECT 1 spring.datasource.onldb.test-on-borrow=false spring.datasource.onldb.test-while-idle=true spring.datasource.onldb.time-between-eviction-runs-millis=18800 mapper.not-empty=false mapper.identity=MYSQL server.port=8899 #Throw an exception directly when an error occurs spring.mvc.throw-exception-if-no-handler-found=true #Do not map the resource files in our project spring.resources.add-mappings=false spring.aop.proxy-target-class=true conf.file.path=D:/testconf/muldb/svcConfig.properties log_dir=D:/testlog
3, Configure multi database DataSource, SqlSessionFactory, SqlSessionTemplate, and DataSourceTransactionManager
- Mgm database configuration
package com.test.svc.conf; 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 javax.sql.DataSource; @Configuration @MapperScan(basePackages = {"com.test.svc.dao.mgm"}, sqlSessionTemplateRef = "mgmSqlSessionTemplate") public class MgmConfig { @Bean(name = "mgmDataSource") @Primary //This annotation must be added. Otherwise, an error is reported. The next class does not need to be added @ConfigurationProperties(prefix = "spring.datasource.mgmdb") // The prefix value must be the prefix of the corresponding property in application.properteis public DataSource mgmDataSource() { DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); return dataSourceBuilder.build(); } @Bean(name = "mgmSqlSessionFactory") @Primary public SqlSessionFactory mgmSqlSessionFactory(@Qualifier("mgmDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); //Add XML directory try { bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sqlmapper/mgmMapper/*.xml")); return bean.getObject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } @Bean(name = "mgmSqlSessionTemplate") @Primary public SqlSessionTemplate mgmSqlSessionTemplate(@Qualifier("mgmSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } @Bean(name = "mgmTransactionManager") @Primary public DataSourceTransactionManager mgmTransactionManager(@Qualifier("mgmDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
- Onl database configuration
package com.test.svc.conf; 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.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; @Configuration @MapperScan(basePackages = {"com.test.svc.dao.onl"}, sqlSessionTemplateRef = "onlSqlSessionTemplate") public class OnlConfig { @Bean(name = "onlDataSource") @ConfigurationProperties(prefix = "spring.datasource.onldb") public DataSource onlDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "onlSqlSessionTemplate") public SqlSessionTemplate onlSqlSessionTemplate(@Qualifier("onlSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } @Bean(name = "onlSqlSessionFactory") public SqlSessionFactory onlSqlSessionFactory(@Qualifier("onlDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); try { bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:sqlmapper/onlMapper/*.xml")); return bean.getObject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } @Bean(name = "onlTransactionManager") public DataSourceTransactionManager onlTransactionManager(@Qualifier("onlDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
4, Generator plug in configuration file description
Take the configuration file generatorMgmDb.xml of the Mgm database table as an example.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!--Database driven--> <context id="mysql4" targetRuntime="MyBatis3"> <commentGenerator> <property name="suppressDate" value="true"/> <property name="suppressAllComments" value="true"/> </commentGenerator> <!--Database link address account password--> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/testmgmdb" userId="root" password=""> </jdbcConnection> <javaTypeResolver> <property name="forceBigDecimals" value="false"/> </javaTypeResolver> <!--generate Model Class storage location--> <javaModelGenerator targetPackage="com.test.svc.model.mgm" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> <property name="trimStrings" value="true"/> </javaModelGenerator> <!--Build mapping file storage location--> <sqlMapGenerator targetPackage="sqlmapper.mgmMapper" targetProject="src/main/resources"> <property name="enableSubPackages" value="true"/> </sqlMapGenerator> <!--generate Dao Class storage location--> <javaClientGenerator type="XMLMAPPER" targetPackage="com.test.svc.dao.mgm" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <!--Generate corresponding table and class name--> <table tableName="tbl_mgm_menu" domainObjectName="Menu" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"> </table> </context> </generatorConfiguration>
5, Configuration of the generator plug-in in the project
Click Run in the idea menu bar, click Edit Configurations, add Maven, configure the specific project path and command line (mybatis generator: generate), and click Apply and OK.
Modify the generator configuration file in pom.xml in turn, and generate the corresponding Model, Dao and Mapper files for each table in turn.
6, Create memory data refresh and read methods
In memory, double data are stored, i.e. A and B caches are opened and refreshed regularly. Which data should be refreshed or read depends on the tag quantity. When refreshing area A data, read area B data; when refreshing area B data, read area A data.
package com.test.svc.utils; import com.test.svc.ApplicationContextHelper; import com.test.svc.constants.SvcConstant; import com.test.svc.model.mgm.Menu; import lombok.extern.slf4j.Slf4j; import org.springframework.core.env.Environment; import java.util.HashMap; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @Slf4j public class DualBlockCache { /** * Memory block access and refresh status flag * 0: Unavailable (initial), refresh A cache block for next scheduled task * 1: A cache Block available, refresh B cache block for next scheduled task * 2: B cache Block available, refresh A cache block for next scheduled task */ public static int indicator = 0; // A cache cache Object private static HashMap<String, Object> blockObjectA = new HashMap<>(); // B cache cache Object private static HashMap<String, Object> blockObjectB = new HashMap<>(); // Cache single thread private static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); // Cache instance private static DualBlockCache cache = new DualBlockCache(); private DualBlockCache() { } /** * Find Object according to key value * * @param key key value in memory * @return Return Object object */ public Object getObject(String key) { switch (indicator) { case 1: return blockObjectA.get(key); case 2: return blockObjectB.get(key); default: return null; } } public static DualBlockCache getInstance() { return cache; } public void start() { // Refresh memory data regularly executorService.scheduleWithFixedDelay(new Runnable() { @Override public void run() { // Refresh memory parameters flushData(); // According to the reloadCache parameter switch, judge whether to perform the next refresh task. 0: close, do not refresh memory. 1: open, refresh memory regularly, and cannot be recovered after the process shutdown Environment environment = ApplicationContextHelper.applicationContext.getEnvironment(); String reloadCache = PropertyUtil.getProperties(environment.getProperty("conf.file.path"), "reloadCache"); if ("0".equals(reloadCache)) { executorService.shutdown(); } } }, 1L, 600L, TimeUnit.SECONDS); // The project starts to execute 1 second after startup, and refreshes once every 10 minutes } /** * Refresh memory data */ public void flushData() { log.info(DateUtils.now() + " start to reload A-B Cache task ==="); HashMap<String, Object> blockObject = (indicator == 1) ? blockObjectB : blockObjectA; blockObject.clear(); QueryCacheManageService queryCacheManageService = new QueryCacheManageService(); List<Menu> listMenu = queryCacheManageService.listMenu(); for (Menu menu : listMenu) { blockObject.put(SvcConstant.MENUINFO + menu.getMenuId(), menu); } indicator = (indicator == 1) ? 2 : 1; log.info("blockObject size : " + blockObject.size()); log.info("Refresh of memory data completed, indicator = {}", indicator); } }
Reading method of menu table data list
package com.test.svc.utils; import com.test.svc.constants.RecSt; import com.test.svc.dao.mgm.MenuMapper; import com.test.svc.model.mgm.Menu; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.util.List; /** * @description Update memory data */ @Component public class QueryCacheManageService { @Resource private MenuMapper menuMapper; private static QueryCacheManageService dbCacheMapper; @PostConstruct public void init() { dbCacheMapper = this; // The following five lines of code can be annotated or retained dbCacheMapper.menuMapper = this.menuMapper; } public List<Menu> listMenu() { Menu menu = new Menu(); menu.setRecSt(RecSt._1.getCode()); return dbCacheMapper.menuMapper.selectSelective(menu); } }
7, Write test methods
package com.test.svc.controller; import com.alibaba.fastjson.JSON; import com.test.svc.model.mgm.Menu; import com.test.svc.service.QueryService; import com.test.svc.utils.DualBlockCache; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @description Refresh memory data background entry */ @RestController @Slf4j public class TestController { @Resource private QueryService queryService; // Refresh memory business parameters manually @RequestMapping("/flushData") public void flushData() { DualBlockCache.getInstance().flushData(); log.info("Manual cache refresh complete"); } // Manually adjust the memory refresh flag to the initial state @RequestMapping("/setFlag") public int setFlag(String indicator) { DualBlockCache.indicator = Integer.parseInt(indicator); return DualBlockCache.indicator; } // Get memory flags @RequestMapping("/getFlag") public int getFlag() { return DualBlockCache.indicator; } // Get menu details according to menuId (memory search) @RequestMapping("/getMenu") public Menu getMenu(String menuId) { log.info("Memory query menu details, receive menu primary key:[{}]", menuId); Menu menu = queryService.getMenu(menuId); log.info("Memory query menu details, answer data:[{}]", JSON.toJSONString(menu)); return menu; } // Get menu details according to menuId (database search) @RequestMapping("/getMenu1") public Menu getMenu1(String menuId) { log.info("Database query menu details, receive menu primary key:[{}]", menuId); Menu menu = queryService.getMenu1(menuId); log.info("Database query menu details, response data:[{}]", JSON.toJSONString(menu)); return menu; } }
After testing, the database operation is normal, and the memory data can be refreshed and read regularly.