做网站宣传图片个人免费开发app
Sharding-JDBC系列
1、Sharding-JDBC分库分表的基本使用
2、Sharding-JDBC分库分表之SpringBoot分片策略
3、Sharding-JDBC分库分表之SpringBoot主从配置
4、SpringBoot集成Sharding-JDBC-5.3.0分库分表
5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表
6、【源码】Sharding-JDBC源码分析之JDBC
前言
《Sharding-JDBC系列》的前5篇博文分析了Sharding-JDBC的使用,本篇带大家从源码的角度整体的熟悉Sharding-JDBC的实现原理。从Sharding-JDBC 5.1.2 版本开始,提供了原生JDBC驱动ShardingSphereDriver。
JDBC
2.1 JDBC概述
JDBC(Java DataBase Connectivity),是java连接数据库操作的原生接口API,为开发者访问数据库提供标准的接口。各数据库厂商依照JDBC规范,实现规范中的接口,实现数据库的连接。Java开发者使用同样的访问代码,配置不同的Driver、url以及账号,即可实现不同数据库厂家的数据库连接。
2.2 JDBC组成
JDBC是由一组用Java语言编写的类和接口,主要有DriverManager、Driver、Connection、Statement、ResultSet等。
使用JDBC连接数据库的步骤包含以下步骤:
1)加载JDBC驱动程序:使用Class.forName()方法加载特定数据库的JDBC驱动程序,即对应数据库插件中实现Driver接口的类;
如:
Mysql8之前为com.mysql.jdbc.Driver
Mysql8的com.mysql.cj.jdbc.Driver
Oracle的oracle.jdbc.driver.OracleDriver
2)创建Connection数据库连接:使用DriverManager.getConnection()方法返回一个Connection,创建一个数据库连接对象。该方法需要指定数据库的连接字符串、用户名和密码;
3)创建Statement对象:通过数据库连接对象Connection,创建一个Statement对象,用于执行SQL语句;
也可以通过数据库连接对象Connection,预编译SQL语句,获得一个PreparedStatement对象。该方式通常与动态SQL语句结合使用,获得PreparedStatement对象后,给动态SQL语句中的占位符赋值。使用该方式不会有SQL注入的风险。
4)执行SQL语句:使用Statement对象执行SQL语句,包括插入、查询、更新和删除等操作。如果是查询操作,返回ResultSet对象;
5)关闭数据库连接:使用数据库连接对象Connection的close()方法关闭数据库连接;
2.3 JDBC使用
以下以MySQL为例,讲解一下JDBC的使用。
2.3.1 使用创建Statement的方式
@SpringBootTest
public class Test {@org.junit.jupiter.api.Testpublic void mysqlTest() throws Exception {// 1、加载启动Class.forName("com.mysql.cj.jdbc.Driver");String url = "jdbc:mysql://localhost:3306/test";String username = "root";String password = "123456";// 2、获取Connection对象Connection connection = DriverManager.getConnection(url, username, password);String sql = "select * from tb_user";// 3、创建Statement对象Statement statement = connection.createStatement();// 4、执行Sql。查询语句返回ResultSetResultSet resultSet = statement.executeQuery(sql);// 5、处理结果集while(resultSet.next()) {System.out.println("name : " + resultSet.getString("name"));}// 6、关闭数据库连接statement.close();connection.close();}}
2.3.2 使用PreparedStatement的方式
@org.junit.jupiter.api.Testpublic void mysqlTest2() throws Exception {// 1、加载启动Class.forName("com.mysql.cj.jdbc.Driver");String url = "jdbc:mysql://localhost:3306/test";String username = "root";String password = "123456";// 2、获取Connection对象Connection connection = DriverManager.getConnection(url, username, password);String sql = "insert into tb_user(name, state) values(?, ?)";// 3、预编译SQL语句,没有执行SQL// 此处传入两个参数值,第二个参数为Statement.RETURN_GENERATED_KEYS,指定返回数据库生成的主键PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);// 占位符赋值statement.setString(1, "王五");statement.setString(2, "1");// 4、执行Sql,此处不需要再传入SQL语句。执行更新操作,返回操作成功的记录数int rs = statement.executeUpdate();// 5、获取生成的主键ResultSet generatedKeys = statement.getGeneratedKeys();// 移到结果集的下一行generatedKeys.next();System.out.println("执行结果 : " + rs + ",记录主键:" + generatedKeys.getInt(1));// 6、关闭数据库连接statement.close();connection.close();}
1)通过Connection的prepareStatement()方法,可以在第二个参数中指定Statement.RRTURN_GENERATED_KEYS,获取数据库表中设置的自动生成的主键值;
2)可以通过PreparedStatement的addBatch()方法,实现对数据库的批量操作;
2.4 JDBC原理
2.4.1 JDBC的驱动程序
JDBC的驱动程序为实现了java.sql.Driver接口的类。如com.mysql.cj.jdbc.Driver。当使用Class.forName()将对应的驱动类加载到元空间后,会执行类的static静态代码段。源码如下:
package com.mysql.cj.jdbc;import java.sql.DriverManager;
import java.sql.SQLException;public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}}
}
在static静态代码段中,new一个Driver,并通过DriverManager.registerDriver(),将Driver注册到DriverManager中。DriverManager中使用一个静态的线程安全的List常量,保存进一步封装的Driver对象。
package java.sql;public class DriverManager {// 线程安全的静态集合常量,存放Driverprivate final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();public static synchronized void registerDriver(java.sql.Driver driver)throws SQLException {registerDriver(driver, null);}public static synchronized void registerDriver(java.sql.Driver driver,DriverAction da)throws SQLException {// 将driver封装成DriverInfo,保存到registeredDrivers集合中if(driver != null) {registeredDrivers.addIfAbsent(new DriverInfo(driver, da));} else {// This is for compatibility with the original DriverManagerthrow new NullPointerException();}println("registerDriver: " + driver);}
}class DriverInfo {final Driver driver;DriverAction da;DriverInfo(Driver driver, DriverAction action) {this.driver = driver;da = action;}@Overridepublic boolean equals(Object other) {return (other instanceof DriverInfo)&& this.driver == ((DriverInfo) other).driver;}@Overridepublic int hashCode() {return driver.hashCode();}@Overridepublic String toString() {return ("driver[className=" + driver + "]");}DriverAction action() {return da;}
}
Driver中的主要方法:
1)boolean acceptsURL(String url):查询驱动程序是否认为它可以打开到给定url的连接;
Driver制定自己能够连接的url的协议,只有符合自己协议形式的url,返回true;反之,返回false。
2)Connection connect(String url, Properties info):试图创建一个到给定url的数据库连接;
2.4.2 创建Connection数据库连接
通过DriverManager的getConnection()方法,获取一个Connection对象。源码如下:
package java.sql;public class DriverManager {// Driver集合private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();@CallerSensitivepublic static Connection getConnection(String url,String user, String password) throws SQLException {java.util.Properties info = new java.util.Properties();if (user != null) {info.put("user", user);}if (password != null) {info.put("password", password);}return (getConnection(url, info, Reflection.getCallerClass()));}/*** 公共的获取Connection的方法*/private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {/** 当callerCl为null时,我们应该检查应用程序的(间接调用此类的)类加载器,* 以便可以从这里加载rt.jar之外的JDBC驱动程序类*/ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;synchronized(DriverManager.class) {// synchronize loading of the correct classloader.if (callerCL == null) {callerCL = Thread.currentThread().getContextClassLoader();}}if(url == null) {throw new SQLException("The url cannot be null", "08001");}println("DriverManager.getConnection(\"" + url + "\")");// 浏览已加载的Driver,尝试建立连接。记录引发的第一个异常SQLException reason = null;// 遍历Driverfor(DriverInfo aDriver : registeredDrivers) {// 如果权限不足,跳过if(isDriverAllowed(aDriver.driver, callerCL)) {try {// 建立连接println(" trying " + aDriver.driver.getClass().getName());Connection con = aDriver.driver.connect(url, info);// 连接成功,则返回if (con != null) {// Success!println("getConnection returning " + aDriver.driver.getClass().getName());return (con);}} catch (SQLException ex) {if (reason == null) {reason = ex;}}} else {println(" skipping: " + aDriver.getClass().getName());}}// 如果异常,则抛出if (reason != null) {println("getConnection failed: " + reason);throw reason;}println("getConnection: no suitable driver found for "+ url);throw new SQLException("No suitable driver found for "+ url, "08001");}}
使用注册的Driver,以及传入的url、用户信息,尝试连接数据库,获取一个Connection对象。
2.5 JDBC小结
JDBC通过DriverManager管理Driver,并提供了Driver、Connection、Statement、PreparedStatement、ResultSet等API接口,不同的数据库提供商实现对应接口,实现对数据库的连接、SQL语句的执行等。
ShardingSphereDriver
SpringBoot集成Sharding-JDBC-5.3.0分库分表-CSDN博客
在这篇博文介绍了Sharding-JDBC-5.3.0分库分表的基本使用。示例的application.yml配置信息为:
server:port: 8080
spring:main:# 处理连接池冲突allow-bean-definition-overriding: truedatasource:driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriverurl: jdbc:shardingsphere:classpath:sharding.yml
即对应的Driver为ShardingSphereDriver,url指向shardingsphere的配置文件sharding.yml。
ShardingSphereDriver的源码如下:
package org.apache.shardingsphere.driver;public final class ShardingSphereDriver implements Driver {private static final int MAJOR_DRIVER_VERSION = 5;private static final int MINOR_DRIVER_VERSION = 1;// 数据库连接缓存private final DriverDataSourceCache dataSourceCache = new DriverDataSourceCache();static {try {// 注册到DriverManagerDriverManager.registerDriver(new ShardingSphereDriver());} catch (final SQLException ex) {throw new DriverRegisterException(ex);}}/*** 在hikari的DriverDataSource或其他连接池中,会通过DriverManager.getDriver(url)获得该ShardingSphereDriver,* 然后调用ShardingSphereDriver.connect()获得数据库连接*/@Overridepublic Connection connect(final String url, final Properties info) throws SQLException {return acceptsURL(url) ? dataSourceCache.get(url).getConnection() : null;}/*** ShardingSphereDriver的url中必须是以jdbc:shardingsphere:开头* @param url* @return*/@Overridepublic boolean acceptsURL(final String url) {return null != url && url.startsWith("jdbc:shardingsphere:");}@Overridepublic DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {return new DriverPropertyInfo[0];}@Overridepublic int getMajorVersion() {return MAJOR_DRIVER_VERSION;}@Overridepublic int getMinorVersion() {return MINOR_DRIVER_VERSION;}/*** 不符合JDBC标准* @return*/@Overridepublic boolean jdbcCompliant() {return false;}@Overridepublic Logger getParentLogger() {return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);}
}
1)在static静态代码段中,new一个ShardingSphereDriver,注册到DriverManager中;
2)在acceptsURL(String url)方法中,判断url是否以jdbc:shardingshpere:开头;
3)在connect()方法中,从dataSourceCache中,根据url,获取一个Connection对象;
ShardingSphere中的JDBC
在ShardingSphere框架中,对于JDBC中提供的Driver、Connection、Statement等API接口进行了实现,实现类分别为:
1)Driver:ShardingSphereDriver
2)DataSource:ShardingSphereDataSource
3)Connection:ShardingSphereConnection
4)Statement:ShardingSphereStatement
5)PreparedStatement:ShardingSpherePreparedStatement
小结
限于篇幅,本篇先分享到这里。
由于ShardingSphere采用了原生JDBC的驱动,所以本篇重点讲解了JDBC及其原理。在此基础上,结合ShardingSphereDriver的源码,可以很清晰的对ShardingSphere的框架有一个整体的认识。
ShardingSphere框架就是通过实现JDBC中的接口,在调用真实数据库的Statement执行SQL之前,对SQL语句进行拦截解析,结合分库分表规则,执行对应真实数据库的SQL操作。
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。