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

news2024/12/23 23:02:29

文章目录

  • 1、概要
  • 2、整体架构流程
  • 3、技术名词解释
  • 4、技术细节
    • 4.1、指定分库规则
    • 4.2、安装Mysql数据库以及建库建表
    • 4.3、创建Java项目
      • 4.3.1、使用 Idea创建Maven项目
        • 4.3.1.1、修改pom.xml配置
      • 4.3.2、编写分库/路由规则 DbRouter
      • 4.3.3、编写数据库交互工具 DaoUtil
      • 4.3.4、编写数据库操作接口 BaseDAO
      • 4.3.5、编写数据库操作类 BaseDAOImpl
      • 4.3.6、测试并检查分库结果
        • 4.3.6.1、从主库迁移数据到分库
        • 4.3.6.2、通过ID查询数据,并查看路由的数据库信息
  • 5、小结

在这里插入图片描述

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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/654194.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyBits的创建与使用

文章目录 前言MyBits的优点这里简单回忆下用JDBC的流程 MyBits的调用流程MyBits的配置传递参数之# 与 $ 的区别 当mysql与程序属性映射不一致时的解决方案 前言 上篇博客讲述了 Spring后端与前端进行交互的过程, 而这篇博客将讲述Spring与数据库的交互 , 众所周知 后端与数据库…

1.1数据结构绪论

一、数据结构 学习如何使用程序代码把现实世界的问题信息化 二、数据的基本概 1、数据&#xff1a;信息的载体&#xff0c;是描述客观世界属性的数、字符及被计算机程序识别和处理的集合。 早期计算机处理的数据——纯数值类型&#xff1b;现代计算机处理数据——非数据类型 …

融合创新:AI虚拟数字人与3D VR全景引领未来旅游潮流

导语&#xff1a; 随着科技不断发展&#xff0c;AI虚拟数字人和3D VR全景技术的融合正引领着创新的潮流。这种融合不仅仅是对传统导览的升级&#xff0c;更为各个领域带来了全新的创新应用。让我们一起探索AI虚拟数字人与3D VR全景融合的创新应用&#xff0c;看看它们如何在多…

快速解决Github无法访问的问题

Github访问慢&#xff0c;是困扰很多人的问题&#xff0c;今天就出一个解决方案&#xff0c;按照下面思路&#xff0c;可以实现快速访问Github&#xff0c;来查看我们需要的资源。 目录 一、获取DNS 二、修改hosts文件内容 2.1 修改hosts权限 2.2 修改hosts内容 三、轻…

以指标驱动,企业数智化迈向新阶段

近年来&#xff0c;我国数字经济蓬勃发展&#xff0c;数据成为推动经济社会发展的新要素。国家十四五规划指出&#xff0c;要激活数据要素潜能&#xff0c;加快建设数字经济&#xff0c;需要重点实施“上云用数赋智”行动&#xff0c;推动数据赋能全产业链协同转型。为进一步迈…

保姆级教你用Python制作超级玛丽游戏“爷青回~”(文末赠书)

名字&#xff1a;阿玥的小东东 学习&#xff1a;Python、C/C 主页链接&#xff1a;阿玥的小东东的博客_CSDN博客-python&&c高级知识,过年必备,C/C知识讲解领域博主 目录 贪吃蛇游戏 弹珠游戏 超级玛丽&#xff08;爷青回~&#xff09; 完整代码如下&#xff1a; 总…

SpringBoot 实现 PDF 添加水印

SpringBoot 实现 PDF 添加水印 使用场景方式一&#xff1a;使用 Apache PDFBox 库方式二&#xff1a;使用 iText 库方式三&#xff1a;Free Spire.PDF for JavaDemo 使用场景 PDF&#xff08;Portable Document Format&#xff0c;便携式文档格式&#xff09;是一种流行的文件…

LIME论文阅读笔记

这是暗图增强领域一篇经典的传统方法论文&#xff0c;发表在TIP这个顶刊 文章基于的是这样一个公式&#xff1a; L R ⋅ T LR\cdot T LR⋅T 其中&#xff0c; L L L是暗图&#xff0c; R R R是反射分量&#xff0c; T T T是illumination map&#xff0c;并且对于彩色图像来说…

OpenCV reshape函数

reshape函数 在opencv中&#xff0c;reshape函数比较有意思&#xff0c;它既可以改变矩阵的通道数&#xff0c;又可以对矩阵元素进行序列化&#xff0c;非常有用的一个函数。 函数原型&#xff1a; C: Mat Mat::reshape(int cn, int rows0) const参数比较少&#xff0c;但设…

DJ3-5 TCP:流量控制、连接控制

目录 一、流量控制 二、连接管理 1. 建立连接&#xff08;三次握手&#xff09; 2. 关闭连接 3. TCP 连接的生命周期 一、流量控制 一条 TCP 连接的每一侧主机都为该连接设置了接收缓冲区。 TCP 的接收方的接收缓冲区&#xff1a; 1. 提供流量控制服务的原因 应用进程会…

Burpsuit使用03:拦截请求并修改响应

burpsuite是渗透的必备工具&#xff0c;使用它可以进行一些截包分析&#xff0c;修改包数据、暴力破解、扫描等功能&#xff0c;使用最多的场景应该是设置代理拦截数据包分析数据和爆破。 文章目录 拦截请求并修改响应Intercept is offForwardDropAction 拦截请求并修改响应 拦…

5.4、docker-compose

h ttps://www.runoob.com/docker/docker-compose.html Docker Compose docker-compose.yml 配置文件编写详解_docker-compose.yml 编写_种子选手的博客-CSDN博客 docker-compose.yml 配置文件编写详解 1.dockerfile: 构建镜像&#xff1b; 2.docker run: 启动容器&#xff1b;…

upyter Notebook:内核似乎挂掉

项目场景&#xff1a;提示&#xff1a;这里简述项目相关背景&#xff1a;项目场景&#xff1a;深度强化学习在中国股票量化交易上的应用&#xff0c;要求跑赢大盘问题描述提示&#xff1a;这里描述项目中遇到的问题&#xff1a;使用Jupyter Notebook运行时&#xff0c;跑到绘图…

瑞萨RA系列mcu学习笔记--RTT-pwm驱动

方案1&#xff1a;Studio 2.2.6和使用了RASC3.5下使用pwm驱动 开发环境必须说一下&#xff0c;本人在在开发环境的问题上栽了一个跟头&#xff0c; 使用最新版的RTT Studio 2.2.6和使用了RASC4.0的版本生成的公共编译ok&#xff0c;但是一下载到mcu就直接不能运行&#xff1a…

浅谈Java的IO与Netty

一、Java的IO((Input/Output))模型 传统IO和Java NIO最大的区别是传统的IO是面向流&#xff0c;NIO是面向Buffer Socket之间建立链接及通信的过程&#xff01;实际上就是对TCP/IP连接与通信过程的抽象: 1.服务端Socket会bind到指定的端口上&#xff0c;Listen客户端的”插入”…

如何迁移现有应用和数据到阿里云服务器?有哪些迁移工具和方法?

如何迁移现有应用和数据到阿里云服务器&#xff1f;有哪些迁移工具和方法&#xff1f;   随着云计算技术的不断发展&#xff0c;越来越多的企业和个人开始将应用和数据迁移到云服务器上。阿里云作为国内领先的云服务提供商&#xff0c;为用户提供了一系列方便可靠的应用和数据…

2023开放原子全球开源峰会参会感受:英特尔开源技术合作与产品创新

2023开放原子全球开源峰会参会感受&#xff1a;英特尔开源技术合作与产品创新 文章目录 2023开放原子全球开源峰会参会感受&#xff1a;英特尔开源技术合作与产品创新一. 引言二. 6.11参会感受2.1 英特尔基础软件开放生态合作2.2 Celadon加速安卓在英特尔架构上的产品创新2.3 面…

ASEMI代理光宝高速光耦LTV-5341参数,LTV-5341应用

编辑-Z LTV-5341参数描述&#xff1a; 型号&#xff1a;LTV-5341 储存温度Tstg&#xff1a;-55~125℃ 工作温度Topr&#xff1a;-40~110℃ 总输出电源电压(VCC –VEE)&#xff1a;35V 平均正向输入电流IF&#xff1a;20mA 峰值瞬态输入电流IF(TRAN)&#xff1a;1A 输入…

【面试中的网络知识】DNS原理-如何实现域名和IP地址的查询转换

接上一篇&#xff0c;理解 浏览器是如何生成HTTP消息的 &#xff0c;最好是按照顺序来读。  从上一篇博客我们得知浏览器是如何生成了HTTP消息了&#xff0c;但是浏览器作为应用程序&#xff0c;是不具备向网络中发送请求的能力&#xff0c;而是需要委托给操作系统的内核协议栈…

淘宝详情页分发推荐算法总结:用户即时兴趣强化

转子&#xff1a;https://juejin.cn/post/6992169847207493639 商品详情页是手淘内流量最大的模块之一&#xff0c;它加载了数十亿级商品的详细信息&#xff0c;是用户整个决策过程必不可少的一环。这个区块不仅要承接用户对当前商品充分感知的诉求&#xff0c;同时也要能肩负起…