SpringBoot 整合 MyBatis 配置多数据源操作MySQL数据库

23 篇文章 0 订阅
8 篇文章 0 订阅

本文以多个 MySQL 数据库为例,采用 SpringBoot 框架,整合 MyBatis 配置多数据源进行数据库操作。在实际项目中,为了减少流量高峰期间对数据库的压力,可对一些数据库惰性数据(以查询为主,且不经常更新的数据)缓存到 JVM 内存中,可快速响应,且减少数据库压力。
项目源码 git 地址:https://github.com/piaoranyuji/muldb

一、MySQL 表结构(双数据库)

本项目中共用到了 2 个数据库,分别为 testmgmdb 和 testonldb。

  1. 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', 'https://www.baidu.com', '0', '1', '2020-01-21 14:44:03', '2020-01-21 14:44:03');
  1. 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 '服务方名称',
  `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 (`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
  1. 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);
    }
}

  1. 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;
    }
}

经测试,数据库操作正常,内存数据可定时刷新和读取。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值