三、SpringBoot 整合mybatis 多數據源以及分庫分表

来源:https://www.cnblogs.com/quellanan/archive/2019/09/21/11564099.html
-Advertisement-
Play Games

前言 說實話,這章本來不打算講的,因為配置多數據源的網上有很多類似的教程。但是最近因為項目要用到分庫分表,所以讓我研究一下看怎麼實現。我想著上一篇博客講了多環境的配置,不同的環境調用不同的資料庫,那接下來就將一個環境用到多個庫也就講了。所以才有了這篇文章。 我們先來看一下今天項目的項目結構,在上篇博 ...


前言

說實話,這章本來不打算講的,因為配置多數據源的網上有很多類似的教程。但是最近因為項目要用到分庫分表,所以讓我研究一下看怎麼實現。我想著上一篇博客講了多環境的配置,不同的環境調用不同的資料庫,那接下來就將一個環境用到多個庫也就講了。所以才有了這篇文章。
我們先來看一下今天項目的項目結構,在上篇博客的基礎上進行了一定的增改,主要是增加了一個 config 文件,在dao 中分了兩個子包mapper1 和mapper2 將原先的UserMapper 移入到了 mapper1 中。好了,開始正文
file

多數據源配置

背景

在這之前,還是先說一下為什麼會存在多數據源。如果項目小的話,當然是所有的數據以及邏輯處理都操作同一個庫。但是當業務量大的話,就會考慮到分庫了。比我會將也日誌入庫數據存放到單獨的資料庫。或者用戶許可權信息單獨的一個庫存放。這種如果只是簡單的分庫,一個項目中就用到2~4 個資料庫的話,這種多數據源配置就有意義啦。在配置文件中配置好這幾個數據源,都有唯一標識。項目在啟動載入的時候都進行初始化,然後在調用的時候,想用哪個庫就哪個數據源的連接實例就好了。

如果不整合 mybatis 的話,直接使用使用spring 自帶的jdbcTemplate ,那配置多數據源,以及使用都比較簡單,但是整合 mybatis 的話,就相對複雜點。我們一步一步來將講解。

修改配置文件

打開application-dev.yml 文件,添加數據源。

#開發環境
spring:
  # 數據源配置
  datasource:
    one:
      driver-class-name: com.mysql.jdbc.Driver
      jdbc-url: jdbc:mysql://192.168.252.53:3306/zlflovemm?characterEncoding=utf-8&useSSL=false&zeroDateTimeBehavior=CONVERT_TO_NULL
      username: root
      password: 123456
      max-idle: 10
      max-wait: 10000
      min-idle: 5
      initial-size: 5
    two:
      driver-class-name: com.mysql.jdbc.Driver
      jdbc-url: jdbc:mysql://192.168.252.53:3306/zlfdb?characterEncoding=utf-8&useSSL=false&zeroDateTimeBehavior=CONVERT_TO_NULL
      username: root
      password: 123456
      max-idle: 10
      max-wait: 10000
      min-idle: 5
      initial-size: 5

這裡需要註意的是如果使用的是springboot 2.0 以上的,那麼註意是 driver-class-name 和
jdbc-url 而不是driverClassName和url.這裡是一個坑,提醒大家一下。

配置數據源

接下來就需要我們手動的載入什麼什麼數據源了,我們在config中創建 DataSourcesConfig 類

@Configuration
public class DataSourcesConfig {

    @Bean(name="dbOne")
    @ConfigurationProperties(prefix = "spring.datasource.one")
    @Primary
    DataSource dbOne(){
        return DataSourceBuilder.create().build();
    }

    @Bean(name="dbTwo")
    @ConfigurationProperties(prefix = "spring.datasource.two")
    DataSource dbTwo(){
        return DataSourceBuilder.create().build();
    }

}

這裡定義了兩個數據源的DataSource。分別是我們在配置文件中配置的one 和two 。註解@Primary 表示預設使用的數據源。

MyBatisConfigOne 類

@Configuration
@MapperScan(basePackages = "com.quellan.zlflovemm.dao.mapper1",sqlSessionFactoryRef = "sqlSessionFactory1",sqlSessionTemplateRef = "sqlSessionTemplate1")
public class MyBatisConfigOne {
    @Resource(name = "dbOne")
    DataSource dbOne;

    @Bean
    @Primary
    SqlSessionFactory sqlSessionFactory1()throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dbOne);
        return bean.getObject();
    }
    @Bean
    @Primary
    SqlSessionTemplate sqlSessionTemplate1() throws Exception{
        return new SqlSessionTemplate(sqlSessionFactory1());
    }
}

MyBatisConfigTwo 類

@Configuration
@MapperScan(basePackages = "com.quellan.zlflovemm.dao.mapper2",sqlSessionFactoryRef = "sqlSessionFactory2",sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MyBatisConfigTwo {
    @Resource(name = "dbTwo")
    DataSource dbTwo;

    @Bean
    SqlSessionFactory sqlSessionFactory2()throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dbTwo);
        return bean.getObject();
    }
    @Bean
    SqlSessionTemplate sqlSessionTemplate2()throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory2());
    }
}

註意連個文件的區別:
file

dao 層

在dao 層創建了兩個包mapper1 和mapper2 .包裡面的UserMapper類的內容是完全一樣,放在不同的包中只是區分使用哪個數據源。和昨天是一樣的。

public interface UserMapper {

    @Select("select id,username as userName,password,email,role_code as roleCode,gmt_create as gmtCreate,gmt_update as gmtUpdate,nickname as nickName,user_create as userCreate from sys_user")
    List<UserEntry> findUserList();


    @Insert({"insert into sys_user(username,password,email) values('${user.userName}','${user.password}','${user.email}')"})
    int add(@Param("user") UserEntry user);

    @Delete("delete from sys_user where id = #{id}")
    int delete(int id);
}

service 層

UserService介面

public interface UserService {

    List<UserEntry> findUserList();

    int addUser(String userName,String password,String email);

    int deleteUser(int id);

    List<UserEntry> findUserList2();

    int addUser2(String userName,String password,String email);

    int deleteUser2(int id);
}

UserServiceImpl類:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    protected UserMapper userMapper;

    @Autowired
    protected UserMapper2 userMapper2;


    @Override
    public List<UserEntry> findUserList() {
        return userMapper.findUserList();
    }

    @Override
    public int addUser(String userName, String password, String email) {
        UserEntry user=new UserEntry();
        user.setUserName(userName);
        user.setPassword(password);
        user.setEmail(email);
        return userMapper.add(user);
    }

    @Override
    public int deleteUser(int id) {
        return userMapper.delete(id);
    }

    @Override
    public List<UserEntry> findUserList2() {
        return userMapper2.findUserList();
    }

    @Override
    public int addUser2(String userName, String password, String email) {
        UserEntry user=new UserEntry();
        user.setUserName(userName);
        user.setPassword(password);
        user.setEmail(email);
        return userMapper2.add(user);
    }

    @Override
    public int deleteUser2(int id) {
        return userMapper2.delete(id);
    }
}

controller 層

userController

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/list",method = RequestMethod.GET)
    public List<UserEntry> findUserList(){
        return userService.findUserList();
    }

    @RequestMapping(value = "/add",method = RequestMethod.GET)
    public String addUser(@RequestParam(value = "userName")String uaserName,@RequestParam(value = "password")String password,@RequestParam(value = "email")String email){
        int falg=userService.addUser(uaserName,password,email);
        if(falg>0){
            return "success";
        }
        return "error";
    }
        
    @RequestMapping(value = "/delete",method = RequestMethod.GET)
    public String deleteUser(@RequestParam(value = "id")int id){
        if(userService.deleteUser(id)>0){
            return "success";
        }
        return "error";
    }

    @RequestMapping(value = "/list2",method = RequestMethod.GET)
    public List<UserEntry> findUserList2(){
        return userService.findUserList2();
    }

    @RequestMapping(value = "/add2",method = RequestMethod.GET)
    public String addUser2(@RequestParam(value = "userName")String uaserName,@RequestParam(value = "password")String password,@RequestParam(value = "email")String email){
        int falg= userService.addUser2(uaserName,password,email);
        if(falg>0){
            return "success";
        }
        return "error";
    }

    @RequestMapping(value = "/delete2",method = RequestMethod.GET)
    public String deleteUser2(@RequestParam(value = "id")int id){
        if(userService.deleteUser2(id)>0){
            return "success";
        }
        return "error";
    }
}

測試

file
file
可以看到是從不同的庫中調出來的。這樣就說明我們springboot配置多數據源整合mybatis 已經成功了。其實最主要就是config 包下的那三個配置類。其他的都是常見的業務邏輯,所以後面我就沒有怎麼講了,代碼會同步到github 上,想要實踐的可以拿源碼下來實踐。

到此我們springboot整合mybatis 多數據源已經配置好了,但是我們配置下來可以發現,我們如果想要配置幾個數據源就得在 dao 層創建多少個子包用來區分。那如果我們數據量足夠大,要分庫分表而不是幾個庫呢?

分庫分表

背景

其實分庫分表和多數據源是一樣的,只不過是數據源更多了,多到在配置中配置所有的連接顯得很臃腫,所以不得不另覓它法。分庫分表就是 在項目中配置連接主庫的連接,從主庫中讀取各個分庫的連接,然後進行動態的載入,那個介面想調用那個分庫就載入這個分庫的連接。
我現在項目做的由於不用整合mybatis 直接使用jdbcTemplate ,所以實現起來不是很麻煩。

思路

主要就兩個類;
GetDynamicJdbcTemplate類:手動的創建連接。

/**
 * @ClassName GetDynamicJdbcTemplate
 * @Description 獲取動態的jdbcTemplate
 * @Author zhulinfeng
 * @Date 2019/9/20 14:35
 * @Version 1.0
 */
public class GetDynamicJdbcTemplate {

    private  String driverClassName;
    private  String url;
    private  String dbUsername;
    private  String dbPassword;
    private JdbcTemplate jdbcTemplate;

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public GetDynamicJdbcTemplate(String driverClassName, String url, String dbUsername, String dbPassword){
        this.driverClassName=driverClassName;
        this.url=url;
        this.dbUsername=dbUsername;
        this.dbPassword=dbPassword;
        this.jdbcTemplate=new JdbcTemplate(getDataSource());
    }

    public DriverManagerDataSource getDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(dbUsername);
        dataSource.setPassword(dbPassword);
        return dataSource;
    }
}

GetJdbcTemplateMap類在項目啟動的時候,會讀取主庫中的配置,將所有分庫的連接都創建好放到map中。我們是按照地市分表的,介面在調用的時候根據前端傳過來的地市就可以知道使用哪個資料庫連接了。


@Component
@Slf4j
    public class GetJdbcTemplateMap implements ApplicationRunner {

    @Autowired
    @Qualifier("baseTemplate")
    private JdbcTemplate jdbcTemplate;

    public static Map<String,JdbcTemplate> JdbcTemplateMap=new HashMap<>();

    @Override
    public void run(ApplicationArguments args) throws Exception {
        String sql="CALL proc_baseinfo_cfg_dbsetting_query()";
        List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
        if(list!=null && !list.isEmpty()){
            insertMap(list);
        }
    }

    private void insertMap(List<Map<String, Object>> list){
        for(Map<String, Object> map :list){
            String url="jdbc:mysql://"+map.get("serverip")+":"+map.get("dbport")+"/"+map.get("dbname")+"?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull";
            log.info(url);
            String  dbUsername=  map.get("user").toString();
            String  dbPassword=  map.get("password").toString();
            GetDynamicJdbcTemplate getDynamicJdbcTemplate=new GetDynamicJdbcTemplate(ConstantClass.DRIVERCLASSNAME,url,dbUsername,dbPassword);
            JdbcTemplate jdbcTemplate=getDynamicJdbcTemplate.getJdbcTemplate();
            JdbcTemplateMap.put(map.get("cityid").toString(),jdbcTemplate);
        }
    }
}

file

在介面中調用也很方便。
file

但是上面講的只適合我們自己特有的業務,並且也沒有整合mybatis ,所以我就沒有寫在我自己的項目中,這裡提供出來是給大家一個思路。

番外

也算是寫完了這篇,感覺寫的不是很好,但是有不知道怎麼修改,暫時就先這樣吧,後續有思路了再進行修改。又問問我為什麼不先整合Thymeleaf 弄出頁面來。之所以沒有弄,是因為我想後期做前後端分離都是以介面的形式調用。所以想先將後端的部分都搭建好,再來整合前端的。
好了,就說這麼多啦,今天項目的代碼也同步到github 上啦。
github地址:https://github.com/QuellanAn/zlflovemm

後續加油♡

歡迎大家關註個人公眾號 "程式員愛酸奶"

分享各種學習資料,包含java,linux,大數據等。資料包含視頻文檔以及源碼,同時分享本人及投遞的優質技術博文。

如果大家喜歡記得關註和分享喲❤
file


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 什麼是 PHP 擴展 通俗說,PHP 擴展是增強 PHP 語言功能的插件。PHP 提供了編程語言的語法,比如分支、迴圈、函數、類等,這些是 PHP 本身所提供的。在某些情況下需要在 PHP 語言的基礎上進行擴展,那麼就需要通過 PHP 底層提供的數據結構和介面來開發 PHP 擴展,從而來補充或擴展 ...
  • 聲明 :本博客僅僅是一個初學者的學習記錄、心得總結,其中肯定有許多錯誤,不具有參考價值,歡迎大佬指正,謝謝!想和我交流、一起學習、一起進步的朋友可以加我微信Liu__66666666 這是簡單學習一遍之後的記錄,後期還會修改。 一、學習內容 1. "jvm簡介" 2. 記憶體模型 3. 垃圾回收機制 ...
  • 恢復內容開始 目錄 1. 分支結構 1.1 初步介紹 1.2 使用案例 1.3 練習 2.迴圈結構 1.1 初步介紹 1.2 使用案例 目錄 1. 分支結構 1.1 初步介紹 1.2 使用案例 1.3 練習 2.迴圈結構 1.1 初步介紹 1.2 使用案例 1. 分支結構 1.1 初步介紹 1.2 ...
  • ZooKeeper技術的極少以及ZooKeeper集群的搭建 ...
  • 0. 序 我從一生下來就呆在這個昏暗的地方。 我不明白為什麼程式員這麼喜歡 Dark Mode,Brighten Mode 才是我的最愛。聽說最近連 iphone 都開始支持 Dark Mode 了,沒話講。。。說好的絕不妥協呢? 我周圍是熙熙攘攘的函數群,穿插著變數聲明和巨集定義。 在我們這裡,函數 ...
  • 一、前言 應聘IC前端相關崗位時,FIFO是最常考也是最基本的題目。FIFO經常用於數據緩存、位寬轉換、非同步時鐘域處理。隨著晶元規模的快速增長,靈活的system verilog成為設計/驗證人員的基本功。本文從簡易版的同步FIFO開始,熟悉IP設計與驗證的基礎技能。 二、IP設計 FIFO這一IP ...
  • 背景 運維人員反饋一個容器化的java程式每跑一段時間就會出現OOM問題,重啟後,間隔大概兩天後復現。 問題調查 一查日誌 由於是容器化部署的程式,登上主機後使用docker logs ContainerId查看輸出日誌,並沒有發現任何異常輸出。 使用docker stats查看容器使用的資源情況, ...
  • #set指令 #set指令用於向一個變數或者對象賦值。 格式: #set($var = value) LHS是一個變數,不要使用特殊字元例如英文句號等,不能用大括弧括起來。測試發現#set($user.name = 'zhangsan'),#set(${age} = 18)均賦值失敗。 RHS可以是 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...