Sharding-JDBC 数据源分片:Java 配置实现基于原生 JDBC 的精确分片方案

本文介绍 Sharding-JDBC 数据源分片之使用 Java 配置实现基于原生 JDBC 的精确分片方案。


目录

  • 开发环境
  • 基础示例
  • 总结

开发环境

  • Oracle JDK 1.8.0_201
  • Apache Maven 3.6.0
  • IntelliJ IDEA (Version 2018.3.3)
  • MySQL 5.6.38

基础示例

  1. 创建 2 个数据库实例 ds_0ds_1

  2. 创建 Maven 工程,参考:IntelliJ IDEA 创建 Maven 工程。

  3. 在生成的 pom 文件中添加 Sharding-JDBC 和 MySQL 驱动依赖,使用阿里巴巴 Druid 数据库连接池。



    
        shardingsphere-tutorial
        tutorial.shardingsphere
        1.0-SNAPSHOT
    
    4.0.0

    shardingsphere-raw-jdbc

    
        UTF-8
        UTF-8
        1.8
    

    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.8.0
                
                    ${java.version}
                    ${java.version}
                    ${project.build.sourceEncoding}
                
            
        
    

    
        
            org.apache.shardingsphere
            sharding-jdbc-core
            4.0.0-RC1
        
        
            mysql
            mysql-connector-java
            5.1.47
        
        
            com.alibaba
            druid
            1.1.17
        
        
            junit
            junit
            4.12
            test
        
    

  1. 创建一个领域类 Order(订单),包含 3 个属性:orderId(订单编号),userId(订单所属用户ID),details(订单详情)。
package tutorial.shardingsphere.jdbc.bean;

import java.io.Serializable;
import java.util.Objects;

public class Order implements Serializable {

    private static final long serialVersionUID = -3614937227437805644L;

    private Long orderId;

    private Long userId;

    private String details;

    public Order() {
    }

    public Order(Long userId, String details) {
        this.userId = userId;
        this.details = details;
    }

    public Long getOrderId() {
        return orderId;
    }

    public void setOrderId(Long orderId) {
        this.orderId = orderId;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Order)) {
            return false;
        }
        Order order = (Order) o;
        return Objects.equals(orderId, order.orderId) &&
                Objects.equals(userId, order.userId) &&
                Objects.equals(details, order.details);
    }

    @Override
    public int hashCode() {
        return Objects.hash(orderId, userId, details);
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderId=" + orderId +
                ", userId=" + userId +
                ", details='" + details + '\'' +
                '}';
    }
}
  1. 定义通用数据访问接口(DAO:Data Access Object)。
package tutorial.shardingsphere.jdbc.dao;

import java.util.List;

public interface IBasicDao {

    void createTableIfNotExists();

    void dropTable();

    void truncateTable();

    Long insert(T entity);

    List select();

    void update(T entity);

    void delete(P key);
}
  1. 定义 Order(订单)数据访问接口,继承通用 DAO。
package tutorial.shardingsphere.jdbc.dao;

import tutorial.shardingsphere.jdbc.bean.Order;

public interface IOrderDao extends IBasicDao {
}
  1. 定义 Order(订单)数据访问实现。
package tutorial.shardingsphere.jdbc.dao.impl;

import tutorial.shardingsphere.jdbc.bean.Order;
import tutorial.shardingsphere.jdbc.dao.IOrderDao;

import javax.sql.DataSource;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;

public class OrderDaoImpl implements IOrderDao {

    private final DataSource dataSource;

    public OrderDaoImpl(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public void createTableIfNotExists() {
        String sql = "CREATE TABLE IF NOT EXISTS t_order "
                + "(order_id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, details VARCHAR(100), PRIMARY KEY (order_id))";
        try (Connection connection = dataSource.getConnection();
             Statement statement = connection.createStatement()) {
            statement.executeUpdate(sql);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void dropTable() {
        String sql = "DROP TABLE t_order";
        try (Connection connection = dataSource.getConnection();
             Statement statement = connection.createStatement()) {
            statement.executeUpdate(sql);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void truncateTable() {
        String sql = "TRUNCATE TABLE t_order";
        try (Connection connection = dataSource.getConnection();
             Statement statement = connection.createStatement()) {
            statement.executeUpdate(sql);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Long insert(Order order) {
        String sql = "INSERT INTO t_order (user_id, details) VALUES (?, ?)";
        try (Connection connection = dataSource.getConnection();
             PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
            preparedStatement.setLong(1, order.getUserId());
            preparedStatement.setString(2, order.getDetails());
            preparedStatement.executeUpdate();
            try (ResultSet resultSet = preparedStatement.getGeneratedKeys()) {
                if (resultSet.next()) {
                    order.setOrderId(resultSet.getLong(1));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return order.getOrderId();
    }

    @Override
    public List select() {
        String sql = "SELECT * FROM t_order";
        return listOrders(sql);
    }

    protected List listOrders(String sql) {
        List result = new LinkedList<>();
        try (Connection connection = dataSource.getConnection();
             PreparedStatement preparedStatement = connection.prepareStatement(sql);
             ResultSet resultSet = preparedStatement.executeQuery()) {
            while (resultSet.next()) {
                Order order = new Order();
                order.setOrderId(resultSet.getLong(1));
                order.setUserId(resultSet.getLong(2));
                order.setDetails(resultSet.getString(3));
                result.add(order);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result;
    }

    @Override
    public void update(Order order) {
        String sql = "UPDATE t_order SET user_id=?, details=? WHERE order_id=?";
        try (Connection connection = dataSource.getConnection();
             PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            preparedStatement.setLong(1, order.getUserId());
            preparedStatement.setString(2, order.getDetails());
            preparedStatement.setLong(3, order.getOrderId());
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void delete(Long id) {
        String sql = "DELETE FROM t_order WHERE order_id=?";
        try (Connection connection = dataSource.getConnection();
             PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            preparedStatement.setLong(1, id);
            preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  1. 定义数据源工具类。
package tutorial.shardingsphere.jdbc.util;

import com.alibaba.druid.pool.DruidDataSource;

import javax.sql.DataSource;

public class DataSourceUtils {

    /**
     * 创建数据源对象
     */
    public static DataSource createDataSource(final String dataSourceName) {
        DruidDataSource dataSource = new DruidDataSource();
        String jdbcUrl =
                String.format(
                        "jdbc:mysql://localhost:3306/%s?useSSL=false&useUnicode=true&characterEncoding=UTF-8",
                        dataSourceName);
        dataSource.setUrl(jdbcUrl);
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        // 数据源其它配置(略)
        return dataSource;
    }
}
  1. 定义获取数据源的工厂类。
package tutorial.shardingsphere.jdbc.util;

import org.apache.shardingsphere.api.config.sharding.KeyGeneratorConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.InlineShardingStrategyConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;

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

public class DataSourceFactory {

    /**
     * 配置数据源映射
     */
    private static Map createDataSourceMap() {
        Map result = new HashMap<>();
        result.put("ds_0", DataSourceUtils.createDataSource("ds_0"));
        result.put("ds_1", DataSourceUtils.createDataSource("ds_1"));
        return result;
    }

    public static DataSource getDataSource() throws SQLException {
        // 配置数据源映射
        Map dataSourceMap = createDataSourceMap();
        // 配置表规则
        TableRuleConfiguration tableRuleConfiguration = new TableRuleConfiguration("t_order");
        tableRuleConfiguration.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "order_id"));
        // 配置分片规则
        ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();
        shardingRuleConfiguration.getTableRuleConfigs().add(tableRuleConfiguration);
        // 配置默认分库策略
        shardingRuleConfiguration.setDefaultDatabaseShardingStrategyConfig(
                new InlineShardingStrategyConfiguration("user_id", "ds_${user_id % 2}")
        );
        // 获取数据源对象
        return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfiguration, new Properties());
    }
}
  1. 编写单元测试。
package tutorial.shardingsphere.jdbc;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import tutorial.shardingsphere.jdbc.bean.Order;
import tutorial.shardingsphere.jdbc.dao.IOrderDao;
import tutorial.shardingsphere.jdbc.dao.impl.OrderDaoImpl;
import tutorial.shardingsphere.jdbc.util.DataSourceFactory;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class JdbcConfigPreciseShardingDatabaseTest {

    private static IOrderDao orderDao;

    @BeforeClass
    public static void init() throws SQLException {
        DataSource dataSource = DataSourceFactory.getDataSource();
        orderDao = new OrderDaoImpl(dataSource);
    }

    @Test
    public void test() {
        orderDao.createTableIfNotExists();
        orderDao.truncateTable();
        Assert.assertEquals(0, orderDao.select().size());
        Order order1 = new Order(1L, "Order 1");
        Order order2 = new Order(2L, "Order 2");
        order1.setOrderId(orderDao.insert(order1));
        order2.setOrderId(orderDao.insert(order2));
        List orders = orderDao.select();
        List expected = Arrays.asList(order1, order2);
        Assert.assertEquals(expected, orders);
        order1.setUserId(2L);
        order1.setDetails("Order 1 changed user id!");
        orderDao.update(order1);
        order2.setUserId(1L);
        order2.setDetails("Order 2 changed user id!");
        orderDao.update(order2);
        orderDao.delete(0L);
        orders = orderDao.select();
        Assert.assertEquals(expected, orders);
        orderDao.delete(order1.getOrderId());
        orders = orderDao.select();
        Assert.assertEquals(Collections.singletonList(order2), orders);
        orderDao.delete(order2.getOrderId());
        orders = orderDao.select();
        Assert.assertEquals(0, orders.size());
        orderDao.dropTable();
    }
}

测试执行结果略。


总结

本示例实现了基于 t_orderuser_id 字段的精确数据分片,user_id 为单数时存储在 ds_1 中,为双数时存储在 ds_0 中。

使用 Sharding-JDBC 实现基于原生 JDBC 的单表分库关键步骤:

  1. 配置数据源映射 Map
  2. 配置数据分片规则 ShardingRuleConfiguration
  3. 在数据分片规则中添加表规则配置 TableRuleConfiguration
  4. 在数据分片规则中设置默认数据库分片策略 setDefaultDatabaseShardingStrategyConfig
  5. 调用 ShardingDataSourceFactory.createDataSource 创建分片后数据源对象。

过程中使用的关键类:

  • ShardingDataSourceFactory:数据分片的数据源创建工厂。
  • ShardingRuleConfiguration:分片规则配置对象。
  • TableRuleConfiguration:表分片规则配置对象。
  • ShardingStrategyConfiguration:分片策略配置对象。

Sharding-JDBC 提供了几个 ShardingStrategyConfiguration 的实现类处理不同的分片场景:

  • StandardShardingStrategyConfiguration:用于单分片键的标准分片场景。
  • ComplexShardingStrategyConfiguration:用于多分片键的复合分片场景。
  • InlineShardingStrategyConfiguration:用于配置行表达式分片策略。
  • HintShardingStrategyConfiguration:用于配置Hint方式分片策略。
  • NoneShardingStrategyConfiguration:用于配置不分片的策略。

更多详细配置参考:Sharding-JDBC 官方 Java 配置

你可能感兴趣的:(Sharding-JDBC 数据源分片:Java 配置实现基于原生 JDBC 的精确分片方案)