《项目实战》使用JDBC手写分库

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: 《项目实战》使用JDBC手写分库

1、概要

在Mysql上手写数据库分库,技术栈:Java、JDBC、反射

2、整体架构流程

分库的重点是要制定出合适的分库规则,以及数据操作时,需要使用对应的路由规则。

3、技术名词解释

例如:

  • Mysql:关系型数据库,java程序通过jdbc访问、操作数据库
  • 分库:传统的关系型数据库,在表数据量增长到一定量级后(单表500万),查询速度显著下降,这时候需要把数据分散分拆到其他数据库,分散承担压力。

4、技术细节

4.1、指定分库规则

案例中分库规则:根据3取余,拆分为3个数据库(后缀0,1,2)

4.2、安装Mysql数据库以及建库建表

  • 安装mysql数据库
  • 按照分库规则创建数据库database,比如:store0,store1,store2
  • 创建订单表:order,并创建模拟数据

4.3、创建Java项目

4.3.1、使用 Idea创建Maven项目

创建空项目

4.3.1.1、修改pom.xml配置

<dependencies>
    <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>3.0.0.RELEASE</version>
        </dependency>
      <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.context</artifactId>
            <version>3.0.0.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.10</version>
        </dependency>
     <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
      <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
    </dependencies>
  <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
        </plugins>
    </build>

4.3.2、编写分库/路由规则 DbRouter

/***
 * @title DbRouter
 * @desctption <TODO description class purpose>
 * @author Administrator
 * @create 2023/6/16 14:23
 **/
public class DbRouter {
    public static final String BASE_DARASOURCE_FIX = "cms_spring_db";
    public static final String DARASOURCE_FIX = BASE_DARASOURCE_FIX + "_";
    public static final int BASE_NUM = 3;
    /**
     * 根据code取余
     * @return
     */
    public static String getDBRouter(Long code) {
        int dataSourceNum = (int) (code % BASE_NUM);
        return DARASOURCE_FIX + dataSourceNum;
    }
    /**
     * 根据code取余
     * @return
     */
    public static String getDBRouter(Integer code) {
        int dataSourceNum = code % BASE_NUM;
        return DARASOURCE_FIX + dataSourceNum;
    }
    /**
     * 根据code取余
     * @return
     */
    public static String getDBRouter(Double code) {
        int dataSourceNum = code.intValue() % BASE_NUM;
        return DARASOURCE_FIX + dataSourceNum;
    }
    /**
     * 根据code取余
     * @return
     */
    public static String getDBRouter(Float code) {
        int dataSourceNum = code.intValue() % BASE_NUM;
        return DARASOURCE_FIX + dataSourceNum;
    }
}

4.3.3、编写数据库交互工具 DaoUtil

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
 * 数据库交互工具
 */
public class DaoUtil {
    /**
     * 通过类获取类名 转sql格式(包含前后缀` `)
     * @param clazz
     * @return
     */
    public static String HumpToSQL(Class clazz){
        String simpleName = clazz.getSimpleName();
        String str = "`";
        for (int i = 0; i < simpleName.length(); i++) {
            char c = simpleName.charAt(i);
            if (i == 0 && Character.isUpperCase(c)){
                c = Character.toLowerCase(c);
            }
            if ( i != 0 && Character.isUpperCase(c) ){
                str += "_" + Character.toLowerCase(c);
            }else {
                str += c;
            }
        }
        str += "`";
        return str;
    }
    /**
     * String转sql格式
     * @param simpleName
     * @return
     */
    public static String HumpToSQL(String simpleName){
        String str = "";
        for (int i = 0; i < simpleName.length(); i++) {
            char c = simpleName.charAt(i);
            if (i == 0 && Character.isUpperCase(c)){
                c = Character.toLowerCase(c);
            }
            if ( i != 0 && Character.isUpperCase(c) ){
                str += "_" + Character.toLowerCase(c);
            }else {
                str += c;
            }
        }
        return str;
    }
    /**
     * get方法名转对应sql字段名
     * 例子:getTypeName ->  type_name
     */
    public static String getToSQL(String getMethodName){
        String str = "";
        String substring = getMethodName.substring(3);
        for (int i = 0; i < substring.length(); i++) {
            char c = substring.charAt(i);
            if (i == 0){
                str +=  Character.toLowerCase(c);
            }else{
                if (Character.isUpperCase(c)){
                    str += "_" + Character.toLowerCase(c);
                }else {
                    str += c;
                }
            }
        }
        return str;
    }
    /**
     * 数据库连接
     * @param database
     * @return
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public static Connection getConnection(String database) throws ClassNotFoundException, SQLException {
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取数据库连接
        database = "jdbc:mysql://127.0.0.1:3306/" + database;
        Connection conn = DriverManager.getConnection(database, "root", "root");
        return conn;
    }
}

4.3.4、编写数据库操作接口 BaseDAO

public interface BaseDAO {
//根据id查询单条数据
    public Object queryInfo(Class clazz, Long id , String dataSource) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException ;
 //创建数据
    public int createInfo(Class clazz, Object obj , String dataSource) throws SQLException, ClassNotFoundException, InvocationTargetException, IllegalAccessException;
}

4.3.5、编写数据库操作类 BaseDAOImpl

public class BaseDAOImpl implements BaseDAO {
  /**
     * 根据id查询单条数据
     * @param id
     */
    @Override
    public Object queryInfo(Class clazz, Long id , String dataSource) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        //1.实例化自定义工具类
        //2.获取连接
        Connection conn = DaoUtil.getConnection(dataSource);
        //3.创建Statement\PreparedStatement对象
        PreparedStatement ps;
        //4.初始化查询sql语句
        String sql = "select * from " + DaoUtil.HumpToSQL(clazz) + " where " + clazz.getSimpleName().toLowerCase() + "_is_delete = 0 and "
                + clazz.getSimpleName().toLowerCase() + "_id = " + id;
        //5.执行sql
        ps = conn.prepareStatement(sql);
        ResultSet rs = ps.executeQuery();
        Field[] declaredFields = clazz.getDeclaredFields();
        //6.处理结果
        while (rs.next()){
            //实例化新对象
            Object obj = clazz.newInstance();
            //给对象set值
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                declaredField.set(obj,rs.getObject(DaoUtil.HumpToSQL(declaredField.getName())));
                declaredField.setAccessible(false);
            }
            // 7、释放资源
            conn.close();
            ps.close();
            return obj;
        }
        // 7、释放资源
        conn.close();
        ps.close();
        return null;
    }
   /**
     * Base创建数据
     * @param clazz
     * @param obj
     */
    @Override
    public int createInfo(Class clazz, Object obj , String dataSource) throws SQLException, ClassNotFoundException, InvocationTargetException, IllegalAccessException {
        //1.实例化自定义工具类
        //2.获取连接
        Connection conn = DaoUtil.getConnection(dataSource);
        //3.创建Statement\PreparedStatement对象
        PreparedStatement ps;
        //4.初始化查询sql语句
        String sql = "insert into " + DaoUtil.HumpToSQL(clazz);
        //6.字符串构造字段--完善sql语句
        Field[] declaredFields = clazz.getDeclaredFields();
        StringBuffer stringFields = new StringBuffer(" (");
        StringBuffer stringSeats= new StringBuffer(" (");
        //数据库字段集合
        List<String> fildsList = new ArrayList<>();
        //类属性集合
        List<String> attributesList = new ArrayList<>();
        for (Field declaredField : declaredFields) {
            if ( !declaredField.getName().contains("UpdateTime")
                    && !declaredField.getName().contains("IsDelete") && !declaredField.getName().equals("newsViews")){
                //如果是不用设置的属性就加入集合
                fildsList.add(DaoUtil.HumpToSQL(declaredField.getName()));
                attributesList.add(declaredField.getName());
            }
        }
//        System.out.println("字段集合:" + fildsList);
        for (int i = 0; i < fildsList.size(); i++) {
            stringFields.append(fildsList.get(i));
            stringSeats.append("?");
            if (i != fildsList.size() - 1){
                //如果不是最后一个
                stringFields.append(",");
                stringSeats.append(",");
            }else {
                //最后一个
                stringFields.append(") ");
                stringSeats.append(") ");
            }
        }
        sql += stringFields + " value " + stringSeats;
//        System.out.println("sql语句:" + sql);
        //填充占位符
        ps =  conn.prepareStatement(sql);
        Method[] methods = clazz.getMethods();
        //初始化n n用来标志占位符位置
        int n = 0;
        for (Method method : methods) {
            //获取符合的get方法  枚举排除掉不用插入的属性(排除数据库部分自动生成的字段默认值)
            if ( method.getName().contains("get") && !method.getName().contains("UpdateTime")
                    && !method.getName().contains("IsDelete") && !method.getName().equals("getClass") && !method.getName().equals("getNewsViews")) {
                //遍历属性集合,通过找到get方法对应上的属性,更新占位符索引
                for (int i = 0; i < attributesList.size(); i++) {
                    if (method.getName().toLowerCase().contains(attributesList.get(i).toLowerCase())){
                        n = i + 1;
                    }
                }
                //用get方法获取对象的属性值
                method.setAccessible(true);
                Object date = method.invoke(obj);
                //把获取到的值填充到sql的占位符
                ps.setObject(n,date);
//                System.out.println(method.getName() + " :" + date + "  占位符位置" + n);
                method.setAccessible(false);
            }
        }
        int i = ps.executeUpdate();
        //7、释放资源
        conn.close();
        ps.close();
        return i;
    }
}

4.3.6、测试并检查分库结果

4.3.6.1、从主库迁移数据到分库

List<News> list = baseDAO.queryInfo(News.class);
        for (News news : list) {
            Long newsId = news.getNewsId();
            String dataSource = DbRouter.getDBRouter(newsId) ;
            System.out.println("newsId:" +newsId + " , 所属数据源:" +  dataSource );
            baseDAO.createInfo(News.class, news ,dataSource);
        }*
        System.out.println(list.size());

4.3.6.2、通过ID查询数据,并查看路由的数据库信息

Long newsId = 1007L;
        BaseDAO baseDAO = new BaseDAOImpl();
        String dataSource = DbRouter.getDBRouter(newsId) ;
        System.out.println("数据源路由地址:" + dataSource);
        News news = (News) baseDAO.queryInfo(News.class ,newsId ,  dataSource);
        System.out.println( new Gson().toJson(news));

5、小结

通过上述实验,可以看出分库核心是分库/路由规则,基于此我们可以慢慢完善开发出分库分表组件,如开源框架:ShardingSphere-JDBC。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
8月前
|
SQL 算法 搜索推荐
Mqsql使用Sharding-JDBC案例实战 2
Mqsql使用Sharding-JDBC案例实战
63 0
|
Java 程序员 数据库
手写数据库连接池实战
手写数据库连接池实战
329 0
手写数据库连接池实战
|
9天前
|
存储 弹性计算 中间件
|
8月前
|
算法 Java 关系型数据库
Mqsql使用Sharding-JDBC案例实战 1
Mqsql使用Sharding-JDBC案例实战
24 0
|
7月前
|
SQL 算法 Java
分库分表(4)——ShardingJDBC原理和源码分析
分库分表(4)——ShardingJDBC原理和源码分析
182 1
|
8月前
|
druid Java 数据库连接
MyBatis初级实战之四:druid多数据源
完整的springboot+mybatis+druid多数据源开发和验证
2605 4
MyBatis初级实战之四:druid多数据源
|
8月前
|
Java 数据库连接 数据库
基于mybatis分库之小试牛刀
基于mybatis分库之小试牛刀
41 0
|
9月前
|
SQL XML 算法
|
11月前
|
Java 数据库连接 mybatis
Mybatis多表实战案例(二)
Mybatis多表实战案例