JavaWeb 学习笔记 11:JDBC

JavaWeb 学习笔记 11:JDBC

1.简介

JDBC 是一个 Java 为接入不同类型的数据库定义的一个数据库连接和执行 SQL 的 API。

可以用下图表示:

image-20210725130537815

图中的具体数据库的驱动实际上就是数据库厂商提供的 JDBC 接口的实现类。

2.快速开始

用 Maven 创建一个简单的 Java 应用,并添加数据库驱动的依赖:

<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.0.33</version>
</dependency>

用 JDBC 相关的 API 创建连接并执行 SQL:

public class App1 {public static void main(String[] args) throws ClassNotFoundException, SQLException {// 加载 MySQL 驱动Class.forName("com.mysql.cj.jdbc.Driver");// 获取 JDBC 连接String url = "jdbc:mysql://localhost:3306/jdbc_demo";String username = "root";String password = "mysql";Connection connection = DriverManager.getConnection(url, username, password);// 获取 SQL 执行对象Statement statement = connection.createStatement();// 执行 SQLString sql = "update tb_brand set ordered=99 where id=1";int rows = statement.executeUpdate(sql);if (rows > 0) {System.out.println("品牌信息修改成功");} else {System.out.println("品牌信息修改失败");}// 释放资源statement.close();connection.close();}
}

这个代码实际上有点问题,释放资源的相关语句最好放在finally块中:

public class App2 {public static void main(String[] args) throws ClassNotFoundException, SQLException {// 加载 MySQL 驱动Class.forName("com.mysql.cj.jdbc.Driver");// 获取 JDBC 连接String url = "jdbc:mysql://localhost:3306/jdbc_demo";String username = "root";String password = "mysql";Connection connection = DriverManager.getConnection(url, username, password);try {// 获取 SQL 执行对象Statement statement = connection.createStatement();try {// 执行 SQLString sql = "update tb_brand set ordered=99 where id=1";int rows = statement.executeUpdate(sql);if (rows > 0) {System.out.println("品牌信息修改成功");} else {System.out.println("品牌信息修改失败");}} finally {// 清理资源if (statement != null) {statement.close();}}} finally {if (connection != null) {connection.close();}}}
}

这样可以确保在任何情况下都释放资源,但缺点是代码的可读性变差。

可以使用 Lombok 进行改善:

public class App3 {public static void main(String[] args) throws ClassNotFoundException, SQLException {// 加载 MySQL 驱动Class.forName("com.mysql.cj.jdbc.Driver");// 获取 JDBC 连接String url = "jdbc:mysql://localhost:3306/jdbc_demo";String username = "root";String password = "mysql";@Cleanup Connection connection = DriverManager.getConnection(url, username, password);// 获取 SQL 执行对象@Cleanup Statement statement = connection.createStatement();// 执行 SQLString sql = "update tb_brand set ordered=99 where id=1";int rows = statement.executeUpdate(sql);if (rows > 0) {System.out.println("品牌信息修改成功");} else {System.out.println("品牌信息修改失败");}}
}

Lombok 的 @Cleanup 注解的本质是由 Lombok 替我们在生成的字节码中添加相应的资源释放代码到try...finally语句中。所以可以保持源码简洁和可读性的同时确保任何情况下都释放资源。

3.API

3.1.DriverManager

DriverManager的主要用途是注册数据库驱动以及获取数据库连接。

3.1.1.注册驱动

在这个示例中,我们通过加载驱动类的方式实现驱动注册:

Class.forName("com.mysql.cj.jdbc.Driver");

之所以可以这样,是因为 MySQL 驱动类中有一个静态初始化块,会调用DriverManager.registerDriver方法完成驱动注册:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {// Register ourselves with the DriverManager.static {try {java.sql.DriverManager.registerDriver(new Driver());} catch (SQLException E) {throw new RuntimeException("Can't register driver!");}}// ...
}

使用高版本(>=5)的 MySQL 驱动时,不需要通过手动加载驱动类的方式实现驱动注册,这是因为在驱动依赖包的META-INFO/services/java.sql.Driver文件中添加了驱动的完整类名:

com.mysql.cj.jdbc.Driver

这样 DriverManager 就可以正确获取到驱动类并加载。

也就是说,Class.forName(...)是可以省略的:

public class App4 {public static void main(String[] args) throws ClassNotFoundException, SQLException {// 获取 JDBC 连接String url = "jdbc:mysql://localhost:3306/jdbc_demo";String username = "root";String password = "mysql";@Cleanup Connection connection = DriverManager.getConnection(url, username, password);// ...}
}

3.1.2.获取数据库连接

要获取数据库连接,需要提供:

  • 连接数据库的 url
  • 数据库用户名
  • 数据库密码

连接数据库的 url 是一个固定格式,具体包括:

(连接协议)://(数据库服务器ip或主机名):(数据库服务器端口)/(数据库名称)[?参数键值对1[&参数键值对2]...]

比如对于示例中使用的 url:jdbc:mysql://localhost:3306/jdbc_demo,包含的信息为:

  • 连接协议:jdbc:mysql,用于连接 MySQL 数据库
  • 数据库服务器:localhost,本地主机
  • 端口:3306,默认的 MySQL 端口
  • 数据库:jdbc_demo

如果连接的是本地主机(localhost),且是默认端口(3306),可以使用一种简写方式:

jdbc:mysql:///jdbc_demo

可以看做是将localhost:3306这部分删除后的 url。

可以在连接 MySQL 的 url 上添加一些参数,比如禁用 SSL 以提高性能:

String url = "jdbc:mysql:///jdbc_demo?useSSL=false";

3.2.Connection

Connection 的主要用途是获取 SQL 执行对象以及管理事务。

3.2.1.获取 SQL 执行对象

  • 普通执行SQL对象

    Statement createStatement()
    

    入门案例中就是通过该方法获取的执行对象。

  • 预编译SQL的执行SQL对象:防止SQL注入

    PreparedStatement  prepareStatement(sql)
    

    通过这种方式获取的 PreparedStatement SQL语句执行对象是我们一会重点要进行讲解的,它可以防止SQL注入。

  • 执行存储过程的对象

    CallableStatement prepareCall(sql)
    

    通过这种方式获取的 CallableStatement 执行对象是用来执行存储过程的,而存储过程在MySQL中不常用,所以这个我们将不进行讲解。

3.2.2.管理事务

直接看示例:

public class App6 {public static void main(String[] args) throws Exception {// 获取 JDBC 连接String url = "jdbc:mysql:///jdbc_demo?useSSL=false";String username = "root";String password = "mysql";@Cleanup Connection connection = DriverManager.getConnection(url, username, password);// 获取 SQL 执行对象@Cleanup Statement statement = connection.createStatement();// 执行 SQL// 开启事务connection.setAutoCommit(false);try{String sql1 = "update tb_brand set ordered=66 where id=1";statement.executeUpdate(sql1);String sql2 = "update tb_brand set ordered=66 where id=2";statement.executeUpdate(sql2);// 提交事务connection.commit();}catch (Exception e){// 回滚事务connection.rollback();throw e;}}
}

这里使用Connection的3个方法管理事务:

  • setAutoCommit,将数据库的自动提交设置为false,即开启事务。
  • commit,提交事务
  • rollback,回滚事务

MySQl 数据库默认为自动提交,即不开启事务,此时在 MySQL 客户端执行一条 SQL 会立即生效。

可以在两个 SQL 执行之间“人为制造”一个异常来观察事务的执行:

String sql1 = "update tb_brand set ordered=66 where id=1";
statement.executeUpdate(sql1);
int i = 1/0;
String sql2 = "update tb_brand set ordered=66 where id=2";
statement.executeUpdate(sql2);

结果是两条数据都没有改变,因为有异常产生,事务被回滚了。这体现了事务的原子性,即要么都被执行(提交),要么都不执行(回滚)。

3.3.Statement

可以用Statement执行 DDL 或 DML 的 SQL 语句,在执行 DML 语句时,可以通过返回值判断是否执行成功:

String sql1 = "update tb_brand set ordered=99 where id=1";
int rows = statement.executeUpdate(sql1);
if (rows > 0){System.out.println("修改成功");
}
else{System.out.println("修改失败");
}

示例中的返回值rows表示执行 SQL 后受影响的行数,因此大于零表示成功,小于等于零表示失败。

这里的执行失败和常见的 SQL 编写错误执行失败是有区别的,这里是没有数据行受影响,比如查询条件没有匹配到任何数据行,因此没有发生更新动作。

但如果执行的是 DDL 语句,就没法用这种方式判断,比如:

String sql1 = "drop database db2";
int rows = statement.executeUpdate(sql1);
if (rows > 0){System.out.println("删除成功");
}
else{System.out.println("删除失败");
}

这里用 DDL 语句删除一个数据库,明明删除成功了,但是返回的rows 是零。

3.4.ResultSet

JDBC 将查询语句返回的数据封装为一个ResultSet对象。

ResultSet 中有一个光标,用于标记结果集中的一行(初始位置在第一行之前),可以用一些 API 控制光标的移动。比如用next()方法可以将光标移动到下一行,且会返回一个 Bool 值,告诉你下一行是否有数据。当光标移动到一个有效的数据行后,我们可以通过getXXX方法获取某个列的结果,可以通过列的下标(从1开始)获取,也可以通过列名获取。比如通过getInt(1)可以获取光标所在行第一列的值,且转换为 Int。

示例:

String sql = "select * from tb_brand where id<5";
@Cleanup ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){int id = resultSet.getInt("id");String brandName = resultSet.getString("brand_name");String companyName = resultSet.getString("company_name");System.out.println(String.format("%d[brandName:%s,companyName:%s]", id, brandName, companyName));
}

要注意,ResultSet本身也是一个资源,需要调用ResultSet.closer()方法关闭资源,这里同样使用 Lombok 的@Cleanup注解。

也可以使用列下标:

int id = resultSet.getInt(1);
String brandName = resultSet.getString(2);
String companyName = resultSet.getString(3);

3.5.PreparedStatement

PreparedStatement继承自Statement,用于执行预处理 SQL,可以用于预防 SQL 注入攻击。

3.5.1.SQL 注入攻击

看一个用户登录的例子:

private static boolean login(String username, String password) throws Exception {//查询数据库,如果有用户名和密码的数据,就视为用户名和密码正确,登录成功// 获取 JDBC 连接String url = "jdbc:mysql:///jdbc_demo?useSSL=false";String dbUsername = "root";String dbPassword = "mysql";@Cleanup Connection connection = DriverManager.getConnection(url, dbUsername, dbPassword);// 获取 SQL 执行对象@Cleanup Statement statement = connection.createStatement();// 执行 SQLString sql = "select * from tb_user where username='%s' and password='%s' limit 1";sql = String.format(sql, username, password);System.out.println(sql);@Cleanup ResultSet resultSet = statement.executeQuery(sql);if (resultSet.next()) {// 查询到结果,登录成功System.out.println(String.format("登录成功...[username:%s,password:%s]",username, password));return true;}// 没有查询到结果,登录失败System.out.println(String.format("登录失败...[username:%s,password:%s]",username, password));return false;
}

调用登录方法模拟登录:

login("zhangsan", "123");
login("zhangsan", "111");

输出:

select * from tb_user where username='zhangsan' and password='123' limit 1
登录成功...[username:zhangsan,password:123]
select * from tb_user where username='zhangsan' and password='111' limit 1
登录失败...[username:zhangsan,password:111]

没有问题。

但如果用下边的方式登录:

login("abc", "' or '1'='1");

输出:

select * from tb_user where username='abc' and password='' or '1'='1' limit 1
登录成功...[username:abc,password:' or '1'='1]

观察打印的 SQL 就能发现,通过将输入的密码伪造成' or '1'='1这样的 SQL 片段,就能改变 SQL 的内容,让 SQL 的查询条件中出现or '1'='1'这样的恒等式,在这种情况下,SQL 执行的结果会是表的第一行数据。因此返回的是true

3.5.2.预防注入攻击

使用 PrepareStatement预防 SQL 注入攻击:

// 获取 SQL 执行对象
String sql = "select * from tb_user where username=? and password=? limit 1";
@Cleanup PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
// 执行 SQL
@Cleanup ResultSet resultSet = preparedStatement.executeQuery();

获取PreparedStatement时需要传入 SQL 语句,并且用占位符?表示可能由用户输入的查询条件。并且在之后使用setXXX方法设置占位符对应的参数的值(下标从1开始)。

再次执行测试:

login("zhangsan", "123");
login("zhangsan", "111");
login("abc", "' or '1'='1");

结果:

登录成功...[username:zhangsan,password:123]
登录失败...[username:zhangsan,password:111]
登录失败...[username:abc,password:' or '1'='1]

SQL 注入攻击没有成功。

3.5.3.原理

之所以 PreparedStatement 可以预防 SQL 注入攻击,是因为它会对占位符对应的参数值进行处理,对其中的敏感字符(比如单引号)进行转义(在单引号前添加反斜杠),这样参数值就只能作为一个字符串充当查询条件,不能改变 SQL 语句的结构了。

要证明这一点,需要使用 MySQL 自身的日志功能。

要开启 MySQL 日志,需要修改 MySQL 的全局配置文件my.ini(或 my.cnf):

[mysqld]
port=3306
# 输出日志到文件
log-output=FILE
# 开启日志功能
general-log=ON
# 日志文件
general_log_file="D:/software/coding/mysql-8.0.29-winx64/logs/mysql.log"
# 开启慢查询功能
slow-query-log=ON
# 慢查询日志文件
slow_query_log_file="D:/software/coding/mysql-8.0.29-winx64/logs/mysql_slow.log"
# 超过 2 秒的查询是慢查询
long_query_time=2
  • my.ini 文件可以存在于好几个位置,一般会放在 MySQL 的安装根目录,如果电脑上没有该文件,就手动添加一个。关于 MySQL 配置文件的更多说明可以查看官方文档。
  • 全局配置文件中的可选环境变量说明可以查看官方文档。
  • 需要注意,配置文件中的文件路径中的目录分隔符要使用/而不是反斜杠,因为后者是转义符,或者也可以给后者添加转义符变成双反斜杠\\,否则会导致 MySQL 不能正确写入日志。

查看日志就能看到,实际执行的 SQL 是:

image-20230917203911120

在 VSC 中很明显,使用了转义符',导致整个参数值都是一个字符串。

在 SQL 中两个单引号''表示一个字符串中的单引号。

使用 PrepareStatement 除了可以预防 SQL 注入攻击,还可以提升性能。

用 Java 执行 SQL 的过程可以用下图表示:

image-20210725195756848

大致有这么几个步骤:

  1. Java 将 SQL 语句发送给 MySQL 服务器。
  2. MySQL 检查 SQL 语法。
  3. MySQL 对 SQL 进行编译,生成可执行的函数。
  4. 执行 SQL。

这其中 MySQL 检查 SQL 语法和编译 SQL 占用相当长的时间。

如果使用的是 PrepareStatement,在用 SQL 创建 PrepareStatement 对象的时候,Java 就会将带占位符的 SQL 发送给 MySQL,MySQL 会对 SQL 检查语法和编译,这样在执行 SQL 的时候就只需要传输参数给 MySQL,MySQL 使用编译好的 SQL 直接执行即可,无需检查 SQL 语法和编译 SQL。

当然,对于单条 SQL 来说两者没有什么性能提升,只是将 SQL 的语法检查和编译提前进行罢了。但是如果是对同一条预编译 SQL 执行多次查询,只是使用不同的参数,那就会带来不错的性能提升,每执行 N 次,就可以减少 N-1 次的语法检查和编译过程。

要使用“预编译 SQL”功能,还需要打开一个 MySQL 开关,MySQL 默认是关闭预编译 SQL 功能的。

String url = "jdbc:mysql:///jdbc_demo?useSSL=false&useServerPrepStmts=true";

编写一个简单示例,在一个 PrepareStatement 上执行两次不同参数的查询:

String sql = "select * from tb_user where username=? and password=? limit 1";
@Cleanup PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
@Cleanup ResultSet resultSet = preparedStatement.executeQuery();
preparedStatement.setString(1, "aaa");
preparedStatement.setString(2, "bbb");
@Cleanup ResultSet resultSet2 = preparedStatement.executeQuery();

日志:

image-20230917210425771

结果很清楚,一条 Prepare 语句,两条 Execute 语句,说明只编译了一次。

4.数据库连接池

数据库连接池用于管理和分配数据库连接(Connection)。

这个过程可以用下图表示:

image-20210725210432985

Java 官方对数据库连接池定义了一个接口DataSource,由第三方提供具体的实现。

常见的数据库连接池有:

  • DBCP
  • C3P0
  • Druid

这里以 Druid 为例进行说明。

添加 Druid 的依赖:

<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.17</version>
</dependency>

/resources目录下添加一个配置文件 jdbc.properties

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///jdbc_demo?useSSL=false&useServerPrepStmts=true
username=root
password=mysql
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000

定义一个类变量 DataSource 作为数据库连接池句柄,并在静态初始化块中创建 Druid 数据库连接池:

public class App13 {private static DataSource dataSource;static {// 从配置文件读取数据库连接信息Properties properties = new Properties();InputStream propertiesStream = App13.class.getClassLoader().getResourceAsStream("jdbc.properties");try {// 加载数据库连接信息到 Propertiesproperties.load(propertiesStream);try {// 创建 Druid 数据库连接池dataSource = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {e.printStackTrace();}} catch (IOException e) {e.printStackTrace();}}
}

现在获取数据库连接时使用数据库连接池即可,不再需要使用DriverManager

private static boolean login(String username, String password) throws Exception {//查询数据库,如果有用户名和密码的数据,就视为用户名和密码正确,登录成功// 获取 JDBC 连接@Cleanup Connection connection = dataSource.getConnection();// ...
}

The End,谢谢阅读。

本文的完整示例可以从这里获取。

5.参考资料

  • 黑马程序员JavaWeb基础教程
  • 从零开始 Spring Boot 35:Lombok - 红茶的个人站点 (icexmoon.cn)
  • Maven 简易指南 - 红茶的个人站点 (icexmoon.cn)
  • MySQL 8.0参考手册::4.2.2.2使用选项文件

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 PyTorch 的计算机视觉简介 (5/6)

一、说明 本文主要介绍CNN中在pytorch的实现&#xff0c;其中VGG16网络&#xff0c;数据集来源&#xff0c;以及训练过程&#xff0c;模型生成和存储&#xff0c;模型调入等。 二、预训练模型和迁移学习 训练 CNN 可能需要大量时间&#xff0c;并且该任务需要大量数据。但是&am…

Python+Django前后端分离

程序示例精选 PythonDjango前后端分离 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonDjango前后端分离》编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。 学习与应…

安防视频/集中云存储平台EasyCVR(V3.3)部分通道显示离线该如何解决?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

c语言练习67:写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。

写一个宏&#xff0c;可以将一个整数的二进制位的奇数位和偶数位交换。 #define SwapIntBit(n) (((n) & 0x55555555) << 1 | ((n) & 0xaaaaaaaa) >> 1) 交换奇偶位&#xff0c;需要先分别拿出奇偶位。既然是宏&#xff0c;分别拿出用循环不是很现实&…

消息中间件(二)——kafka

文章目录 Apache Kafka综述什么是消息系统&#xff1f;点对点消息类型发布-订阅消息类型 什么是Kafka?优点关键术语Kafka基本原理用例 Apache Kafka综述 在大数据中&#xff0c;会使用到大量的数据。面对这些海量的数据&#xff0c;我们一是需要做到能够收集这些数据&#xf…

Vue3 中的虚拟DOM、 h() 函数,渲染函数,渲染器知识点一网打尽!

在平常开发阶段我们总是分不清虚拟 DOM、 h() 函数、渲染函数和渲染器的知识。笔者在翻阅相关文档之后&#xff0c;总结了下面这些知识点。 h() 函数用于创建虚拟 DOM&#xff0c;渲染函数的作用就是返回虚拟 DOM。因此&#xff0c;我们可以在渲染函数中使用 h() 创建虚拟 DOM…

【KingFusion】如何在3D场景实现流水效果

哈喽&#xff0c;大家好,我是雷工&#xff01; 在项目过程中&#xff0c;经常会涉及到实现管道水流动效果&#xff0c;此篇记录在KingFusion中的3D场景实现水流效果。 以下为简单流水效果的样例&#xff0c; 一、效果展示 当点击水泵&#xff0c;水泵启动&#xff0c;显示流水…

知网G4期刊-基础教育论坛-如何投稿?

《基础教育论坛》知网 3版5000字符 24年上半年刊期&#xff0c;可收中小学基础教育&#xff0c;幼儿教育等教育全科文章。 《基础教育论坛》主要刊登有关教育教学理论探讨及课程改革、教学改革、考试改革研究等方面的文章&#xff0c;为广大基础教育工作者提供学术交流的…

卫星图像应用 - 洪水检测 数据预处理

执行环境&#xff1a;Google Colab !pip install basemap下载basemap用于在地图上绘制2D数据。 import os import json import numpy as np import matplotlib.pyplot as plt import matplotlib.image as mpimg import matplotlib.gridspec as gridspec from mpl_toolkits.ba…

逻辑像素与物理像素引发学习型探索

文章目录 目的关于像素从像素到分辨率DP(设备像素&#xff09;- 物理像素DIP(逻辑像素&#xff09;- 设备独立像素CSS 像素屏幕特性 DRP&#xff08;设备像素比&#xff09;PPI(Pixels Per Inch ) - 像素密度屏幕像素密度PPI 目的 做一个前端或或者产品开发者&#xff0c; 在涉…

MySQL基础篇-约束

目录 1.约束概述 2.分类 3.测试user表的约束情况 主键约束 非空约束及唯一约束 检查约束 默认约束 4.外键约束 外键约束的语法 外键约束的删除/更新行为 小结 1.约束概述 MySQL约束&#xff08;Constraints&#xff09;是用于确保表中数据完整性和一致性的规则。它们定…

ArrayList 的扩容机制

ArrayList扩容的本质就是计算出新的扩容数组的size后实例化&#xff0c;并将原有数组内容复制到新数组中去。默认情况下&#xff0c;新的容量会是原容量的1.5倍。以JDK1.8为例说明 ArrayList的构造方法有三种&#xff1a; 第一个构造方法用来返回一个初始容量为10的数组 在初…

C++学习资源

https://www.cnblogs.com/xueweihan/p/13928719.html GitHub - Light-City/CPlusPlusThings: C那些事 GitHub - 0voice/introduce_c-cpp_manual: 一个收集C/C新手学习的入门项目&#xff0c;整理收纳开发者开源的小项目、工具、框架、游戏等&#xff0c;视频&#xff0c;书籍&a…

Oracle实现主键字段自增

Oracle实现主键自增有4种方式&#xff1a; Identity Columns新特性自增&#xff08;Oracle版本≥12c&#xff09;创建自增序列&#xff0c;创建表时&#xff0c;给主键字段默认使用自增序列创建自增序列&#xff0c;使用触发器使主键自增创建自增序列&#xff0c;插入语句&…

ceph分布式存储部署

一、概述 是一个统一的分布式存储系统&#xff0c;设计初衷是提供较好的性能、可靠性和可扩展性。 特点 1、统一存储 虽然 ceph 底层是一个分布式文件系统&#xff0c;但由于在上层开发了支持对象和块的接口。所以在开源存储软件中&#xff0c;能够一统江湖。至于能不能千秋万…

【0223】源码剖析smgr底层设计机制(3)

1. smgr设计机制 PG内核中smgr完整磁盘存储介质的管理是通过下面三部分实现的。 1.1 函数指针结构体 f_smgr 函数指针结构体 f_smgr。 通过该函数指针类型,可完成类似于UNIX系统中的VFD功能,上层只需要调用open()、read()、write()等系统函数,用户不必去关系底层的文件系统…

【Vue.js】使用ElementUI搭建动态树数据表格与分页

一&#xff0c;动态树 本文章为上一篇文章拓展内容》》实现首页导航及左侧菜单 将左侧菜单结构更换为下面代码&#xff1a; 菜单结构&#xff1a; <el-menu><el-submenu index"" key""><template slot"title"><i class…

某高校的毕设

最近通过某个平台接的单子&#xff0c;最后Kali做的测试没有公开可以私聊给教程。 下面是规划与配置 1.vlan方面&#xff1a;推荐一个vlan下的所有主机为一个子网网段 连接电脑和http客户端的接口配置为access接口 交换机与交换机或路由器连接的接口配置为trunk接口---也可以…

MySQL详细案例 1:MySQL主从复制与读写分离

文章目录 1. MySQL主从复制1.1 使用场景1.2 MySQL的复制类型1.3 主从复制的作用1.4 主从复制的工作过程1.5 实现MySQL主从复制1.5.1 前置准备1.5.2 主服务器mysql配置1.5.3 从服务器1 mysql配置1.5.4 从服务器2 mysql配置 1.6 MySQL主从复制延时问题的原因和解决办法1.6.1 故障…

计算机专业毕业设计项目推荐09-个人医疗系统(Spring+Js+Mysql)

个人医疗系统&#xff08;SpringJsMysql&#xff09; **介绍****系统总体开发情况-功能模块****各部分模块实现** 介绍 本系列(后期可能博主会统一为专栏)博文献给即将毕业的计算机专业同学们,因为博主自身本科和硕士也是科班出生,所以也比较了解计算机专业的毕业设计流程以及…