SpringBoot 整合 MyBatis 配置多資料來源操作MySQL資料庫
阿新 • • 發佈:2020-01-22
本文以多個 MySQL 資料庫為例,採用 SpringBoot 框架,整合 MyBatis 配置多資料來源進行資料庫操作。在實際專案中,為了減少流量高峰期間對資料庫的壓力,可對一些資料庫惰性資料(以查詢為主,且不經常更新的資料)快取到 JVM 記憶體中,可快速響應,且減少資料庫壓力。
專案原始碼 git 地址:https://github.com/piaoranyuji/muldb
一、MySQL 表結構(雙資料庫)
本專案中共用到了 2 個數據庫,分別為 testmgmdb 和 testonldb。
- testmgmdb 資料庫中表 tbl_mgm_menu 的建表語句如下。
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_name` varchar(30) NOT NULL DEFAULT '' COMMENT '選單名稱',`menu_logo` varchar(64) NOT NULL DEFAULT '' COMMENT '選單logo',`menu_url` varchar(64) NOT NULL DEFAULT '' COMMENT '選單url',`menu_seq` int(2) NOT NULL DEFAULT '0' COMMENT '選單順序',`rec_st` char(1) NOT NULL DEFAULT '1' COMMENT '記錄狀態,0:無效;1:有效',`rec_crt_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',`rec_upd_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',PRIMARY KEY (`menu_id`) ) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8; INSERT INTO `tbl_mgm_menu` VALUES ('23','今日播報','https://www.baidu.com','0','1','2020-01-21 14:44:03','2020-01-21 14:44:03');
- testonldb 資料庫中表 tbl_onl_sp 的建表語句如下。
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 '服務方ID',`call_dt` char(8) NOT NULL DEFAULT '' COMMENT '介面呼叫時間(yyyyMMdd)',`sp_name` char(30) NOT NULL DEFAULT '' COMMENT '服務方名稱',PRIMARY KEY (`sp_id`,`call_dt`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
二、SpringBoot 配置檔案配置資料庫連線資訊
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 #出現錯誤時,直接丟擲異常 spring.mvc.throw-exception-if-no-handler-found=true #不要為我們工程中的資原始檔建立對映 spring.resources.add-mappings=false spring.aop.proxy-target-class=true conf.file.path=D:/testconf/muldb/svcConfig.properties log_dir=D:/testlog
三、配置多資料庫DataSource、SqlSessionFactory、SqlSessionTemplate 和 DataSourceTransactionManager
- Mgm 資料庫配置
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 //必須加此註解,不然報錯,下一個類則不需要新增
@ConfigurationProperties(prefix = "spring.datasource.mgmdb") // prefix值必須是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);
//新增XML目錄
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 資料庫配置
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);
}
}
四、generater 外掛配置檔案說明
以 Mgm 資料庫表的配置檔案 generatorMgmDb.xml 為例。
<?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>
<!--資料庫驅動-->
<context id="mysql4" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--資料庫連結地址賬號密碼-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/testmgmdb"
userId="root"
password="">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--生成Model類存放位置-->
<javaModelGenerator targetPackage="com.test.svc.model.mgm" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!--生成對映檔案存放位置-->
<sqlMapGenerator targetPackage="sqlmapper.mgmMapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!--生成Dao類存放位置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.test.svc.dao.mgm" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--生成對應表及類名-->
<table tableName="tbl_mgm_menu" domainObjectName="Menu" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
</context>
</generatorConfiguration>
五、專案中 generater 外掛配置
點選 idea 選單欄 Run,單擊 Edit Configurations,新增 Maven,配置具體的專案路徑和命令列(mybatis-generator:generate),點選 Apply 和 OK。
依次修改 pom.xml 中的 generater 配置檔案,依次為每個表生成對應的 Model、Dao 和 Mapper 檔案。
六、建立記憶體資料重新整理和讀取方法
在記憶體中,儲存雙份資料,即開闢 A、B Cache,並定時重新整理。具體應重新整理和讀取哪塊資料,依賴標記量來區分。當重新整理 A 區資料時,讀取 B 區資料;當重新整理 B 區資料時,讀取 A 區資料。
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 {
/**
* 記憶體塊取用與重新整理狀態標誌
* 0:不可用(初態),下次定時任務重新整理A cache塊
* 1:A cache塊可用,下次定時任務重新整理B cache塊
* 2:B cache塊可用,下次定時任務重新整理A cache塊
*/
public static int indicator = 0;
// A cache快取Object
private static HashMap<String,Object> blockObjectA = new HashMap<>();
// B cache快取Object
private static HashMap<String,Object> blockObjectB = new HashMap<>();
// 快取單執行緒
private static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
// 快取例項
private static DualBlockCache cache = new DualBlockCache();
private DualBlockCache() {
}
/**
* 根據key值查詢Object
*
* @param key 記憶體中的key值
* @return 返回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() {
// 定時重新整理記憶體資料
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
// 重新整理記憶體引數
flushData();
// 根據reloadCache引數開關判斷是否執行下次重新整理任務,0:關閉,不重新整理記憶體;1:開啟,定時重新整理記憶體,程序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); // 專案啟動後1秒開始執行,此後10分鐘重新整理一次
}
/**
* 重新整理記憶體資料
*/
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("記憶體資料重新整理完畢,indicator = {}",indicator);
}
}
選單表資料列表讀取方法
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 更新記憶體資料
*/
@Component
public class QueryCacheManageService {
@Resource
private MenuMapper menuMapper;
private static QueryCacheManageService dbCacheMapper;
@PostConstruct
public void init() {
dbCacheMapper = this;
// 以下5行程式碼可以註釋,也可以保留
dbCacheMapper.menuMapper = this.menuMapper;
}
public List<Menu> listMenu() {
Menu menu = new Menu();
menu.setRecSt(RecSt._1.getCode());
return dbCacheMapper.menuMapper.selectSelective(menu);
}
}
七、編寫測試方法
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 重新整理記憶體資料後臺入口
*/
@RestController
@Slf4j
public class TestController {
@Resource
private QueryService queryService;
// 手動重新整理記憶體業務引數
@RequestMapping("/flushData")
public void flushData() {
DualBlockCache.getInstance().flushData();
log.info("手動重新整理快取完成");
}
// 手動調整記憶體重新整理標誌位為初始態
@RequestMapping("/setFlag")
public int setFlag(String indicator) {
DualBlockCache.indicator = Integer.parseInt(indicator);
return DualBlockCache.indicator;
}
// 獲取記憶體標誌位
@RequestMapping("/getFlag")
public int getFlag() {
return DualBlockCache.indicator;
}
// 根據 menuId 獲取選單詳情(記憶體查詢)
@RequestMapping("/getMenu")
public Menu getMenu(String menuId) {
log.info("記憶體查詢選單詳情,收到選單主鍵:[{}]",menuId);
Menu menu = queryService.getMenu(menuId);
log.info("記憶體查詢選單詳情,應答資料:[{}]",JSON.toJSONString(menu));
return menu;
}
// 根據 menuId 獲取選單詳情(資料庫查詢)
@RequestMapping("/getMenu1")
public Menu getMenu1(String menuId) {
log.info("資料庫查詢選單詳情,收到選單主鍵:[{}]",menuId);
Menu menu = queryService.getMenu1(menuId);
log.info("資料庫查詢選單詳情,應答資料:[{}]",JSON.toJSONString(menu));
return menu;
}
}
經測試,資料庫操作正常,記憶體資料可定時重新整理和讀取。