1. 程式人生 > >SpringBoot使用Sharding-JDBC讀寫分離

SpringBoot使用Sharding-JDBC讀寫分離

pen jpa 開啟 源碼 onf sele random fault pin

摘要: 本文介紹SpringBoot使用當當Sharding-JDBC進行讀寫分離。 1.有關Sharding-JDBC 本文還是基於當當網Sharding-Jdbc的依賴,與上一篇使用Sharding-Jdbc進行分庫分表依賴一致,並且本文大致內容與上一篇文章相似,建議先查看我的另一篇在查看這篇會簡單許多,傳送門《SpringBoot使用Sharding-JDBC分庫分表》。

本文介紹SpringBoot使用當當Sharding-JDBC進行讀寫分離。

作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要這是一個我的QQ群架構華山論劍:836442475,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿裏面試題、面試經驗,討論技術, 大家一起交流學習成長!

1.有關Sharding-JDBC

本文還是基於當當網Sharding-Jdbc的依賴,與上一篇使用Sharding-Jdbc進行分庫分表依賴一致,並且本文大致內容與上一篇文章相似,建議先查看我的另一篇在查看這篇會簡單許多,傳送門《SpringBoot使用Sharding-JDBC分庫分表》

這裏需要特殊介紹的是,使用Sharding-JDBC進行讀寫分離的時候,只允許設置一個主庫,從庫的話可以設置多個,訪問策略的話從源碼上看只有兩種輪詢(ROUND_ROBIN)和隨機(RANDOM)。

源碼代碼如下:

package com.dangdang.ddframe.rdb.sharding.api.strategy.slave;

public enum MasterSlaveLoadBalanceStrategyType {
    ROUND_ROBIN(new RoundRobinMasterSlaveLoadBalanceStrategy()),
    RANDOM(new RandomMasterSlaveLoadBalanceStrategy());

    private final MasterSlaveLoadBalanceStrategy strategy;

    public static MasterSlaveLoadBalanceStrategyType getDefaultStrategyType() {
        return ROUND_ROBIN;
    }

    private MasterSlaveLoadBalanceStrategyType(MasterSlaveLoadBalanceStrategy strategy) {
        this.strategy = strategy;
    }

    public MasterSlaveLoadBalanceStrategy getStrategy() {
        return this.strategy;
    }
}

2.本文場景

由於本地環境並沒有使用Mysql主從復制,只是創建了三個庫,其中database0作為主庫,database1和database2作為從庫。主庫進行增刪改操作,從庫進行查詢操作,如下圖為本文數據庫的三個表。

技術分享圖片

如上圖分別是三個數據庫中的user表,其中master-user為database0數據庫中的user表,salve-user1為database1中的user表,salve-user2為database2中的user表。

3.代碼實現

本文使用SpringBoot2.0.3,SpringData-JPA,Druid連接池,和當當的sharding-jdbc。

3.1 建表SQL

創建表和數據庫的SQL如下所示,這裏默認在從庫內分別插入了一條數據,name值分別存放dalaoyang1和dalaoyang2便於區分。

CREATE DATABASE database0;
USE database0;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`(
    id bigint(64) not null,
    city varchar(20) not null,
    name varchar(20) not null,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE DATABASE database1;
USE database1;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`(
    id bigint(64) not null,
    city varchar(20) not null,
    name varchar(20) not null,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `database1`.`user`(`id`, `city`, `name`) VALUES (101, ‘beijing‘, ‘dalaoyang1‘);

CREATE DATABASE database2;
USE database2;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`(
    id bigint(64) not null,
    city varchar(20) not null,
    name varchar(20) not null,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `database2`.`user`(`id`, `city`, `name`) VALUES (102, ‘beijing‘, ‘dalaoyang2‘);

3.2 依賴文件

新建項目,依賴文件還是當當的sharding-jdbc-core依賴和druid連接池,完整pom文件代碼如下所示。


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.dalaoyang</groupId>
    <artifactId>springboot2_shardingjdbc_dxfl</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot2_shardingjdbc_dxfl</name>
    <description>springboot2_shardingjdbc_dxfl</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>
        <!-- sharding-jdbc -->
        <dependency>
            <groupId>com.dangdang</groupId>
            <artifactId>sharding-jdbc-core</artifactId>
            <version>1.5.4</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3.3 配置信息

在配置信息中配置了三個數據庫的信息和JPA的簡單配置。

##Jpa配置
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

##數據庫配置
##數據庫database0地址
database0.url=jdbc:mysql://localhost:3306/database0?characterEncoding=utf8&useSSL=false
##數據庫database0用戶名
database0.username=root
##數據庫database0密碼
database0.password=root
##數據庫database0驅動
database0.driverClassName=com.mysql.jdbc.Driver
##數據庫database0名稱
database0.databaseName=database0

##數據庫database1地址
database1.url=jdbc:mysql://localhost:3306/database1?characterEncoding=utf8&useSSL=false
##數據庫database1用戶名
database1.username=root
##數據庫database1密碼
database1.password=root
##數據庫database1驅動
database1.driverClassName=com.mysql.jdbc.Driver
##數據庫database1名稱
database1.databaseName=database1

##數據庫database2地址
database2.url=jdbc:mysql://localhost:3306/database2?characterEncoding=utf8&useSSL=false
##數據庫database1用戶名
database2.username=root
##數據庫database1密碼
database2.password=root
##數據庫database1驅動
database2.driverClassName=com.mysql.jdbc.Driver
##數據庫database1名稱
database2.databaseName=database2

3.4 啟動類

上一篇文章說到在啟動類加入了@EnableAutoConfiguration去除數據庫自動配置,當時也沒太註意,其實可以直接在@SpringBootApplication註解上去除數據庫自動配置,剩下的和上一篇一樣,使用@EnableTransactionManagement開啟事務,使用@EnableConfigurationProperties註解加入配置實體,啟動類完整代碼如下所示。

package com.dalaoyang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableTransactionManagement(proxyTargetClass = true)
@EnableConfigurationProperties
public class Springboot2ShardingjdbcDxflApplication {

    public static void main(String[] args) {
        SpringApplication.run(Springboot2ShardingjdbcDxflApplication.class, args);
    }

}

3.5 實體類和數據庫操作層

User實體類。

package com.dalaoyang.entity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name="user")
@Data
public class User {

    @Id
    private Long id;

    private String city;

    private String name;
}

UserRepository類。

package com.dalaoyang.repository;

import com.dalaoyang.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User,Long> {
}

3.6 數據庫參數類

數據庫配置類,Database0Config。

package com.dalaoyang.database;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;

/**
 * @author yangyang
 * @date 2019/1/30
 */
@Data
@ConfigurationProperties(prefix = "database0")
@Component
public class Database0Config {
    private String url;
    private String username;
    private String password;
    private String driverClassName;
    private String databaseName;

    public DataSource createDataSource() {
        DruidDataSource result = new DruidDataSource();
        result.setDriverClassName(getDriverClassName());
        result.setUrl(getUrl());
        result.setUsername(getUsername());
        result.setPassword(getPassword());
        return result;
    }
}

數據庫配置類,Database1Config。

package com.dalaoyang.database;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;

/**
 * @author yangyang
 * @date 2019/1/30
 */
@Data
@ConfigurationProperties(prefix = "database1")
@Component
public class Database1Config {
    private String url;
    private String username;
    private String password;
    private String driverClassName;
    private String databaseName;

    public DataSource createDataSource() {
        DruidDataSource result = new DruidDataSource();
        result.setDriverClassName(getDriverClassName());
        result.setUrl(getUrl());
        result.setUsername(getUsername());
        result.setPassword(getPassword());
        return result;
    }
}

數據庫配置類,Database2Config。

package com.dalaoyang.database;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;

/**
 * @author yangyang
 * @date 2019/1/30
 */
@Data
@ConfigurationProperties(prefix = "database2")
@Component
public class Database2Config {
    private String url;
    private String username;
    private String password;
    private String driverClassName;
    private String databaseName;

    public DataSource createDataSource() {
        DruidDataSource result = new DruidDataSource();
        result.setDriverClassName(getDriverClassName());
        result.setUrl(getUrl());
        result.setUsername(getUsername());
        result.setPassword(getPassword());
        return result;
    }
}

3.7 讀寫分離配置

創建一個DataSourceConfig類來設置讀寫分離,這裏其實也與分庫分表類似,也可以在分庫分表的基礎上進行讀寫分離,需要創建一個Map集合來接收從庫。在創建數據源時需要傳入五個參數,分別是:

  • name:數據源名稱
  • masterDataSourceName:主庫數據源名稱
  • masterDataSource:主數據源
  • slaveDataSourceMap:從數據源集合
  • strategyType:訪問策略

當然,也可以使用其他方法創建數據源,本文代碼如下:

package com.dalaoyang.database;


import com.dangdang.ddframe.rdb.sharding.api.MasterSlaveDataSourceFactory;
import com.dangdang.ddframe.rdb.sharding.api.strategy.slave.MasterSlaveLoadBalanceStrategyType;
import com.dangdang.ddframe.rdb.sharding.keygen.DefaultKeyGenerator;
import com.dangdang.ddframe.rdb.sharding.keygen.KeyGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author yangyang
 * @date 2019/1/29
 */
@Configuration
public class DataSourceConfig {

    @Autowired
    private Database0Config database0Config;

    @Autowired
    private Database1Config database1Config;

    @Autowired
    private Database2Config database2Config;

    @Bean
    public DataSource getDataSource() throws SQLException {
        return buildDataSource();
    }

    private DataSource buildDataSource() throws SQLException {
        //設置從庫數據源集合
        Map<String, DataSource> slaveDataSourceMap = new HashMap<>();
        slaveDataSourceMap.put(database1Config.getDatabaseName(), database1Config.createDataSource());
        slaveDataSourceMap.put(database2Config.getDatabaseName(), database2Config.createDataSource());

        //獲取數據源對象
        DataSource dataSource = MasterSlaveDataSourceFactory.createDataSource("masterSlave",database0Config.getDatabaseName()
                ,database0Config.createDataSource(), slaveDataSourceMap, MasterSlaveLoadBalanceStrategyType.getDefaultStrategyType());
        return dataSource;
    }


    @Bean
    public KeyGenerator keyGenerator() {
        return new DefaultKeyGenerator();
    }

}

3.8 Controller

Controller做為測試類,創建兩個方法,save方法和getAll方法,其中:

  • save方法用於測試主庫的插入和修改
  • getAll方法用於測試讀數據

UserController類如下所示。

package com.dalaoyang.controller;

import com.dalaoyang.entity.User;
import com.dalaoyang.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @GetMapping("save")
    public void save(){
        User user = new User();
        user.setId(100L);
        user.setName("dalaoyang");
        user.setCity("beijing");
        userRepository.save(user);
    }

    @GetMapping("getAll")
    public Object getAll(){
        return userRepository.findAll();
    }
}

4.測試

4.1 測試主庫

使用postman訪問http://localhost:8080/save,控制臺如圖所示。

技術分享圖片

再次訪問,如圖。

技術分享圖片

主鍵沖突了,其實這是由於插入的時候使用的database0,但是查詢使用的是database1和database2,但是我在從庫內並沒有ID是100的數據,所以JPA判定我為插入,但是數據庫內缺有這樣的數據。

我們接下來測試一下查詢。訪問http://localhost:8080/getAll

技術分享圖片

再次訪問,如圖。

技術分享圖片

證明從庫的讀取是正常的,接下來修改從庫的ID為100。然後訪問http://localhost:8080/save,查看控制臺如圖。

技術分享圖片

因為存在了ID為100的數據,所以SQL為修改語句。

5.源碼

源碼地址:https://gitee.com/dalaoyang/springboot_learn/tree/master/springboot2_shardingjdbc_dxfl

SpringBoot使用Sharding-JDBC讀寫分離