1. 程式人生 > 實用技巧 >Sharding-JDBC+MyBatis分庫分表

Sharding-JDBC+MyBatis分庫分表

  隨著專案功能越來越多業務越來越複雜,資料庫儲存的資料逐漸龐大,當mysql單表儲存資料過千萬的時候,對該表的操作變得緩慢,這時候就需要通過分庫分表對資料庫優化。

水平分庫:是把同一個表的資料按一定規則拆到不同的資料庫中,每個庫可以放在不同的伺服器上。

  • 解決了單庫大資料,高併發的效能瓶頸
  • 提高了系統的穩定性及可用性

水平分表:是在同一個資料庫內,把同一個表的資料按一定規則拆到多個表中。

  • 優化單一表資料量過大而產生的效能問題
  • 避免IO爭搶並減少鎖表的機率

  本文采用 Sharding-JDBC 分庫分表,它是一個優秀的開源框架,使用客戶端直連資料庫,以 jar 包形式提供服務,無需額外部署和依賴,可理解為增強版的 JDBC 驅動,完全相容 JDBC 和各種 ORM 框架。

  • 適用於任何基於 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
  • 支援任何第三方的資料庫連線池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
  • 支援任意實現 JDBC 規範的資料庫,目前支援 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 標準的資料庫。

一、建庫建表

CREATE DATABASE ds_0;
USE ds_0;

DROP TABLE IF EXISTS `user_0`;
CREATE TABLE `user_0` (
  `id` bigint(
64) NOT NULL, `city` varchar(20) NOT NULL, `name` varchar(20) NOT NULL, `sex` tinyint(4) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `user_1`; CREATE TABLE `user_1` ( `id` bigint(64) NOT NULL, `city` varchar(20) NOT NULL, `name` varchar(20) NOT NULL, `sex` tinyint(
4) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE DATABASE ds_1; USE ds_1; DROP TABLE IF EXISTS `user_0`; CREATE TABLE `user_0` ( `id` bigint(64) NOT NULL, `city` varchar(20) NOT NULL, `name` varchar(20) NOT NULL, `sex` tinyint(4) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `user_1`; CREATE TABLE `user_1` ( `id` bigint(64) NOT NULL, `city` varchar(20) NOT NULL, `name` varchar(20) NOT NULL, `sex` tinyint(4) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

二、建立SpringBoot專案,引入依賴

<dependency>
     <groupId>org.mybatis.spring.boot</groupId>
     <artifactId>mybatis-spring-boot-starter</artifactId>
     <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>io.shardingjdbc</groupId>
    <artifactId>sharding-jdbc-core</artifactId>
    <version>2.0.3</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.3</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.44</version>
</dependency>

三、建立mapper檔案、類、實體

UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="law.mapper.UserMapper">

    <resultMap id="baseResultMap" type="law.entity.User">
        <result column="id" property="id"/>
        <result column="city" property="city"/>
        <result column="name" property="name"/>
        <result column="sex" property="sex"/>
    </resultMap>

    <insert id="addUser" parameterType="law.entity.User">
        INSERT INTO user (id, city, name, sex)
        VALUES (#{id}, #{city}, #{name}, #{sex})
    </insert>

    <select id="list" resultMap="baseResultMap" parameterType="java.util.ArrayList">
        SELECT * FROM user
        <where>
            id in (
            <foreach collection="list" item="id" index="index" separator=",">
                #{id}
            </foreach>
            )
        </where>
    </select>

    <select id="query" resultMap="baseResultMap">
        SELECT * FROM user order by id asc
    </select>

</mapper>
package law.mapper;

import law.entity.User;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface UserMapper {

    void addUser(User user);

    List<User> list(List<Long> ids);

    List<User> query();
}
package law.entity;

import lombok.Data;
import java.io.Serializable;

@Data
public class User implements Serializable {

    private static final long serialVersionUID = -1205226416664488559L;

    private Long id;

    private String city;

    private String name;

    private Long sex;
}

四、載入資料庫連結、建立分庫分表策略


package law.configs;

import com.alibaba.druid.pool.DruidDataSource;
import io.shardingjdbc.core.api.config.ShardingRuleConfiguration;
import io.shardingjdbc.core.api.config.TableRuleConfiguration;
import io.shardingjdbc.core.api.config.strategy.StandardShardingStrategyConfiguration;
import io.shardingjdbc.core.jdbc.core.datasource.ShardingDataSource;
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.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;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

@Configuration
@MapperScan(basePackages = "law.mapper", sqlSessionTemplateRef = "testSqlSessionTemplate")
public class DataSourceConfig {

/**
* 配置資料來源
*/
@Bean(name = "shardingDataSource")
DataSource getShardingDataSource() throws SQLException {
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTableRuleConfigs().add(userTableRuleConfiguration());
shardingRuleConfig.getTableRuleConfigs().add(caipiaoTableRuleConfiguration());
return new ShardingDataSource(shardingRuleConfig.build(createDataSourceMap()));
}

/**
* 設定單表的分庫分表策略
* 虛擬表與實際表的對映關係
*/
@Bean
TableRuleConfiguration userTableRuleConfiguration() {
TableRuleConfiguration configuration = new TableRuleConfiguration();
configuration.setLogicTable("user");
configuration.setActualDataNodes("ds_${0..1}.user_${0..1}");
configuration.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("id", DatabaseShardingAlgorithm.class.getName()));
configuration.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("sex", DatabaseShardingAlgorithm.class.getName()));
return configuration;
}

/**
* 設定不分庫分表的對映關係
*/
@Bean
TableRuleConfiguration caipiaoTableRuleConfiguration() {
TableRuleConfiguration configuration = new TableRuleConfiguration();
configuration.setLogicTable("caipiao");
configuration.setActualDataNodes("wms.caipiao");
return configuration;
}

/**
* 手動配置事務管理器
*/
@Bean
public DataSourceTransactionManager transactitonManager(DataSource shardingDataSource) {
return new DataSourceTransactionManager(shardingDataSource);
}

@Bean
@Primary
public SqlSessionFactory sqlSessionFactory(DataSource shardingDataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(shardingDataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:law/mapper/*.xml"));
return bean.getObject();
}

@Bean
@Primary
public SqlSessionTemplate testSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}

/**
* 載入資料庫
*/
private Map<String, DataSource> createDataSourceMap() {
Map<String, DataSource> result = new HashMap<>();
result.put("ds_0", createDataSource("ds_0"));
result.put("ds_1", createDataSource("ds_1"));
result.put("wms", createDataSource("wms"));
return result;
}

private DataSource createDataSource(final String dataSourceName) {
DruidDataSource result = new DruidDataSource();
result.setDriverClassName(com.mysql.jdbc.Driver.class.getName());
result.setUrl(String.format("jdbc:mysql://localhost:3306/%s", dataSourceName) + "?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false");
result.setUsername("root");
result.setPassword("123456");

return result;
}
}
package law.configs;

import io.shardingjdbc.core.api.algorithm.sharding.PreciseShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.standard.PreciseShardingAlgorithm;
import java.util.Collection;

/**
*分庫分表演算法,分為2個庫每個庫2個表
*以id欄位分庫,用id值除2取餘,分配到對應庫
*以sex欄位分表,用sex值除2取餘,分配到對應表
*/
public class DatabaseShardingAlgorithm implements PreciseShardingAlgorithm<Long> { @Override public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) { for (String each : collection) { if (each.endsWith(Long.parseLong(preciseShardingValue.getValue().toString()) % 2 + "")) { return each; } } throw new IllegalArgumentException(); } }

五、啟動測試

package law.controller;

import com.google.common.collect.Lists;
import law.entity.Lottery;
import law.entity.User;
import law.mapper.LotteryMapper;
import law.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private LotteryMapper lotteryMapper;

    @Transactional
    @RequestMapping("/addUsers")
    public Object addUsers() {
        for (long i = 1; i < 10; i++) {
            User user = new User();
            user.setId(i);
            user.setCity("北京");
            user.setName("" + i);
            user.setSex((long) 0);
            userMapper.addUser(user);
        }
//        //測試事務
//        if (1 == 1) {
//            throw new RuntimeException();
//        }
        for (long i = 11; i < 20; i++) {
            User user = new User();
            user.setId(i);
            user.setCity("成都");
            user.setName("" + i);
            user.setSex((long) 1);
            userMapper.addUser(user);
        }
        return "success";
    }
  //測試單表 @RequestMapping(
"/addLotteries") public Object addLotteries() { for (int i = 1; i < 3; i++) { Lottery lottery = new Lottery(); lottery.setCpNum(i); lottery.setCpValue("" + i); lotteryMapper.addLottery(lottery); } return "success"; } @RequestMapping("/selectByIds") public Object selectByIds() { List<Long> ids = Lists.newArrayList(); ids.add(1L); ids.add(3L); return userMapper.list(ids); } @RequestMapping("/selectSort") public Object selectSort() { return userMapper.query(); } }
package law;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@MapperScan(value = {"law.mapper"})
@EnableTransactionManagement(proxyTargetClass = true)
@SpringBootApplication
public class Demo {
    public static void main(String[] args) {
        SpringApplication.run(Demo.class, args);
    }
}