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

前言

說實話,這章本來不打算講的,因爲配置多數據源的網上有很多類似的教程。但是最近因爲項目要用到分庫分表,所以讓我研究一下看怎麼實現。我想着上一篇博客講了多環境的配置,不同的環境調用不同的數據庫,那接下來就將一個環境用到多個庫也就講了。所以纔有了這篇文章。
我們先來看一下今天項目的項目結構,在上篇博客的基礎上進行了一定的增改,主要是增加了一個 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章