java基础学习——jdbc基础知识详细介绍
引言
数据的存储
我们在开发 java 程序时,数据都是存储在内存中的,属于临时存储,当程序停止或重启时,内存中的数据就会丢失,我们为了解决数据的长期存储问题,有以下解决方案:
- 通过 IO流书记,将数据存储在本地磁盘中,这样就解决了持久化问题,但是数据没有结构和逻辑,不方便管理和维护。
- 通过关系型数据库(例如 MySQL),将数据按照特定的格式交由数据库管理系统维护,关系型数据库是通过库和表分隔不同的数据,表中的数据的存储方式是行和列,区分相同格式不同值的数据。
数据的操作
数据存储在数据库中,仅仅只是解决了我们数据存储的问题,当我们程序运行时,需要读取数据以及对数据做增删改的操作,那么我们如何通过 java 程序对数据库中的数据做增删改查呢?
答案就是今天的主角—— jdbc
jdbc
什么是 jdbc
JDBC(Java Database Connectivity)是Java编程语言中用于执行 SQL 语句的 API,它为数据库访问提供了一种标准的方法。通过使用 JDBC API,开发者可以以一种统一的方式与各种不同的数据库进行交互,而无需关心底层的数据库驱动细节。
以下是关于 JDBC 的一些关键点:
- 通用性:JDBC 允许 Java 应用程序连接到几乎任何 SQL 数据库,包括 MySQL、Oracle、PostgreSQL、Microsoft SQL Server 等。
- 数据库无关性:编写一次代码,就可以在不同类型的数据库上运行,只要相应地更换数据库驱动即可。
- 驱动程序:为了使 JDBC 能够与特定的数据库通信,需要有相应的数据库驱动程序。这些驱动程序实现了JDBC 接口,并负责处理与特定数据库的通信协议。
- JDBC URL:每个数据库都有一个唯一的 URL 格式,用于建立到数据库的连接。这个 URL 通常包含数据库类型、主机地址、端口号和数据库名称等信息。
- 连接管理:JDBC 提供了
java.sql.Connection
接口来表示与数据库的连接。开发者可以通过DriverManager.getConnection()
方法获取连接。 - 执行 SQL 命令:通过
Statement
、PreparedStatement
或CallableStatement
对象,可以发送 SQL 语句给数据库并处理返回的结果。 - 结果集处理:执行查询后,会返回一个
ResultSet
对象,它包含了查询结果的数据。可以通过迭代ResultSet
来读取每一行数据。 - 事务管理:JDBC 支持事务控制,包括提交(
commit
)和回滚(rollback
)操作,这使得可以在一组相关联的操作完成后作为一个整体来提交或撤销更改。 - 性能优化:如使用预编译的 SQL 语句(
PreparedStatement
)可以提高性能,减少 SQL 注入风险;批量更新(Batch Updates
)可以一次性执行多个插入、更新或删除操作。 - 资源清理:在完成数据库操作后,必须正确关闭所有打开的资源(如
Connection
、Statement
和ResultSet
),以防止内存泄漏。
随着 Java 的发展,JDBC 也不断演进,增加了新的特性和改进了性能。例如,JDBC 4.0引入了自动加载驱动程序的功能,简化了开发过程。此外,JDBC 还支持分布式事务、XA资源管理和更多高级特性,以满足企业级应用的需求。
jdbc 的核心组成
- 接口规范:
- 为了项目代码的可移植性,可维护性,SUN公司从最初就制定了 java 程序连接各种数据库的统一接口规范,这样的话,不管是连接哪一种 DBMS 软件,java 代码都可以保持一致性。
- 接口存储在
java.sql
和javax.sql
包下。
- 实现规范:
- 因为各个数据库厂商的 DBMS 软件各有不同,那么各自的内部如何通过 sql 实现增删改查等操作管理数据,只有这个数据库厂商自己清楚,因此把接口规范的内部实现由各个数据库厂商自己实现。
- 厂商将实现内容和过程封装成 jar 文件包,我们程序员只需要将 jar 包引入到项目中集成即可,就可以开发调用实现过程操作数据库了。
jdbc 实现的常用接口和类
- DriverManager:
DriverManager
类管理一组JDBC驱动程序,并选择适当的驱动程序来建立到给定数据库URL的连接。它还处理加载和注册JDBC驱动程序的任务。
- Driver接口:
- 每个JDBC驱动程序必须实现
java.sql.Driver
接口。该接口定义了用于与数据库通信的方法。当DriverManager
尝试建立连接时,它会使用这些方法。
- 每个JDBC驱动程序必须实现
- Connection接口:
Connection
对象代表与特定数据库的连接。通过这个对象可以创建Statement
、PreparedStatement
或CallableStatement
对象来执行SQL命令,并且可以管理事务。
- Statement接口:
Statement
接口用于执行静态的SQL语句并返回结果。它是执行SQL语句的基础,包括简单的查询和更新操作。
- PreparedStatement接口:
PreparedStatement
是Statement
的子接口,用于执行预编译的SQL语句。它允许设置参数化查询,这有助于防止SQL注入攻击,并可能提高性能。
- CallableStatement接口:
CallableStatement
也是Statement
的子接口,专门用于调用数据库中的存储过程。它可以处理输入和输出参数。
- ResultSet接口:
ResultSet
对象封装了执行SQL查询后得到的结果表。它提供了遍历表格数据的方法以及获取每一列数据值的方法。
- SQLException类:
SQLException
是一种受检异常,用于报告数据库访问错误。它包含了有关数据库错误的信息,如错误代码和消息文本。
- DataSource接口(自JDBC 2.0引入):
DataSource
提供了一种更灵活的获取数据库连接的方式,特别是在容器环境中。它不仅支持标准的用户名/密码认证,还可以支持分布式事务和其他高级特性。
- RowSet接口(自JDBC 2.0扩展包引入):
RowSet
是一个特殊的ResultSet
,它增加了滚动和更新能力,并且可以在断开连接的情况下工作。它分为连接型(例如JdbcRowSet
)和非连接型(例如CachedRowSet
)两种类型。
这些组件一起工作,使得Java应用程序能够以一种标准化的方式连接到不同的数据库系统,执行SQL查询和更新,并处理返回的数据。随着JDBC规范的发展,新的功能被添加进来以支持更多的特性和改进性能。
jdbc 快速开始
搭建 jdbc
- 准备数据库。
- 官网下载数据库连接驱动 jar 包。
- 创建 java 项目,在项目下创建 lib 文件夹,将下载的驱动 jar 包复制到文件夹里。
- 选中 lib 文件夹右键 -> Add as Library,与项目集成。
- 编写代码。
代码实现
数据库
CREATE DATABASE `myjdbc`;USE `myjdbc`;DROP TABLE IF EXISTS `student`;CREATE TABLE `student` (`id` int NOT NULL AUTO_INCREMENT COMMENT '学生编号',`name` varchar(10) NOT NULL COMMENT '学生姓名',`age` int NOT NULL COMMENT '学生年龄',`score` double(10,5) NOT NULL COMMENT '学生成绩',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;INSERT INTO `student` (`id`, `name`, `age`, `score`) VALUES (1, '张三', 18, 59.50000);
INSERT INTO `student` (`id`, `name`, `age`, `score`) VALUES (2, '李四', 3, 70.00000);
INSERT INTO `student` (`id`, `name`, `age`, `score`) VALUES (3, '王五', 66, 30.00000);
INSERT INTO `student` (`id`, `name`, `age`, `score`) VALUES (4, '赵六', 100, 22.33333);
INSERT INTO `student` (`id`, `name`, `age`, `score`) VALUES (5, '田七', 28, 30.00000);
编写 java 代码
步骤:
- 注册驱动。
- 获取连接对象。
- 获取执行 sql 语句的对象。
- 编写 sql 语句并执行。
- 处理结果。
- 释放资源。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;public class Demo01 {public static void main(String[] args) throws Exception {// 1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 2.获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 3.获取执行sql语句的对象Statement statement = connection.createStatement();// 4.编写sql语句并执行String sql = "select * from student";ResultSet result = statement.executeQuery(sql);// 5.处理结果while (result.next()) {int id = result.getInt("id");String name = result.getString("name");int age = result.getInt("age");double score = result.getDouble("score");System.out.println(id + "\t" + name + "\t" + age + "\t" + score);}// 6.释放资源result.close();statement.close();connection.close();}
}
结果:
1 张三 18 59.5
2 李四 3 70.0
3 王五 66 30.0
4 赵六 100 22.33333
5 田七 28 30.0
核心 API 理解
注册驱动
-
Class.forName("com.mysql.cj.jdbc.Driver");
学习过反射的可以看出,这段代码是在加载 com.mysql.cj.jdbc
包下的 Driver
,我们进入这个类,可以看到有这样一段代码:
static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}
}
这个类在加载时会执行 DriverManager.registerDriver(new Driver());
,这就是注册驱动
- 在Java 中,当使用 JDBC (Java Database Connectivity)连接数据库时,需要加载数据库特定的驱动程序,以便与数据库进行通信。加载驱动程序的目的是为了注册驱动程序,使得 JDBC API 能够多识别并与特定的数据库进行交互。
- 从 JDK6 开始,不再需要显示地调用
Class.forName()
来加载 JDBC 驱动程序,只要在类路径中集成了对应的 jar 文件,会自动在初始化时注册驱动程序。
DriverManager
DriverManager
类管理一组 JDBC 驱动程序,并选择适当的驱动程序来建立到给定数据库 URL 的连接。它负责加载和注册 JDBC 驱动程序,以及创建 Connection
对象。
getConnection(String url, String user, String password)
:尝试根据提供的数据库URL、用户名和密码建立连接。registerDriver(Driver driver)
和deregisterDriver(Driver driver)
:显式地注册或注销驱动程序
Connection
Connection
接口是 JDBC API 的重要接口,用于建立与数据库的通信通道,换而言之,Connection
对象不为空,则代表一次数据库连接。- 在建立连接时,需要指定数据库 url,用户名,密码参数。格式:
# jdbc:mysql://IP地址:端口号/数据库名称?参数键值对1&参数键值对2&...
jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
- 负责管理事务,提供了
commit
和rollback
方法,用于提交事务和回滚事务。 - 可以创建
Statement
对象,用于执行 sql 语句并与数据库进行交互。 - 在使用 jdbc 技术时,必须要先获取
Connection
对象,在使用完毕后,要释放资源,避免资源占用浪费及泄露。
常用方法
createStatement()
:创建一个Statement
对象用于发送SQL语句。prepareStatement(String sql)
:创建一个PreparedStatement
对象,它可以包含IN参数。prepareCall(String sql)
:创建一个CallableStatement
对象用于调用存储过程。setAutoCommit(boolean autoCommit)
:设置是否自动提交更改。commit()
和rollback()
:手动提交或回滚事务。close()
:关闭连接并释放资源。
Statement
Statement
接口用于执行 sql 语句并与数据库进行交互,通过Statement
对象,可以向数据库发送 sql 语句并获取执行结果。- 结果可以是一个或多个结果。
- 增删改:受影响行数单个结果。
- 查询:单行单列、多行多列、单行多列等结果。
- 但是
Statement
接口在执行 sql 语句时,会产生 sql 注入问题:- 因为它是将查询条件与 sql 语句直接拼在一起,不会验证,这时候黑客可以钻漏洞在查询条件里加上 sql 语句,让 sql 的查询条件始终为 true,例如查询条件为
where user = 'root'
,黑客在查询条件输入xxx' or '1'='1
,这样最后拼接的 sql 为where user = 'xxx' or '1'='1'
,这样结果也为 true。
- 因为它是将查询条件与 sql 语句直接拼在一起,不会验证,这时候黑客可以钻漏洞在查询条件里加上 sql 语句,让 sql 的查询条件始终为 true,例如查询条件为
常用方法
executeQuery(String sql)
:执行查询语句并返回结果集。executeUpdate(String sql)
:执行插入、更新或删除语句,并返回受影响的行数。execute(String sql)
:执行任意SQL语句,对于复杂的操作非常有用。addBatch(String sql)
和executeBatch()
:用于批量执行多个SQL语句。
PreparedStatement
PreparedStatement
是Statement
接口的子接口,用于执行预编译的 sql 查询,作用如下:- 预编译 sql 语句:在创建
PreparedStatement
时,就会预编译 sql 语句,也就是 sql 语句已经固定。 - 防止 sql 注入:
PreparedStatement
支持参数化查询,将数据作为参数传递到 sql 语句中,采用?
占位符的方式,将传入的参数用一对单引号包裹起来,无论传递什么都只作为值,可以有效防止传入关键字或值导致 sql 注入问题。 - 性能提升:
PreparedStatement
是预编译 sql 语句,同一 sql 语句多次执行的情况下,可以复用,不比每次重新编译和解析。
- 预编译 sql 语句:在创建
- 更加安全,效率更高
常用方法
setString(int parameterIndex, String x)
等方法:为SQL语句中的参数占位符设置值。executeQuery()
和executeUpdate()
:与Statement
类似,但针对预编译的SQL语句。
代码示例
// 3.获取执行sql语句的对象
PreparedStatement statement = connection.prepareStatement("select * from student where name = ?");// 4.编写sql语句并执行
statement.setString(1, "张三");
ResultSet result = statement.executeQuery();
ResultSet
ResultSet
用于表示从数据库中执行 sql 语句所返回的结果集,它提供了一种用于遍历和访问查询结果的方式。- 遍历结果:
ResultSet
可以使用next()
方法将游标移动到结果集的下一行,逐行遍历数据库查询的结果,返回值为boolean
,true
代表有下一行结果,false
则代表没有。 - 获取单列结果:可以通过
getXxx()
的方法获取单列数据库,Xxx
代表数据类型,支持索引和列名进行获取。
常用方法
next()
:将游标移动到下一行。getXxx(int columnIndex)
或getXxx(String columnName)
:获取当前行中指定列的数据值,其中XXX
代表数据类型。beforeFirst()
、afterLast()
、absolute(int row)
等方法:控制游标的移动位置。
基于 PreparedStatement 实现 crud
查询单行单列
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;public class Demo02 {public static void main(String[] args) throws Exception {// 1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 2.获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 3.预编译sql语句获取对象PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) AS count FROM student");// 4.执行sql语句ResultSet result = statement.executeQuery();// 5.获取结果(如果明确只有一个结果,也要进行一次next()方法判断)if (result.next()) {// 根据列名获取,上面sql设置了别名,所以这里取countint count = result.getInt("count");System.out.println("总数为:" + count);}// 6.释放资源result.close();statement.close();connection.close();}
}
查询单行多列
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;public class Demo03 {public static void main(String[] args) throws Exception {// 1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 2.获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 3.预编译sql语句获取对象PreparedStatement statement = connection.prepareStatement("SELECT id,`name`,age,score FROM student WHERE id = ?");// 4.补充占位符,执行sql语句statement.setInt(1, 5);ResultSet result = statement.executeQuery();// 5.获取结果while (result.next()) {int id = result.getInt("id");String name = result.getString("name");int age = result.getInt("age");double score = result.getDouble("score");System.out.println(id + "\t" + name + "\t" + age + "\t" + score);}// 6.释放资源result.close();statement.close();connection.close();}
}
查询多行多列
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;public class Demo04 {public static void main(String[] args) throws Exception {// 1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 2.获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 3.预编译sql语句获取对象PreparedStatement statement = connection.prepareStatement("SELECT id,`name`,age,score FROM student WHERE age > ?");// 4.补充占位符,执行sql语句statement.setInt(1, 10);ResultSet result = statement.executeQuery();// 5.获取结果while (result.next()) {int id = result.getInt("id");String name = result.getString("name");int age = result.getInt("age");double score = result.getDouble("score");System.out.println(id + "\t" + name + "\t" + age + "\t" + score);}// 6.释放资源result.close();statement.close();connection.close();}
}
新增数据
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;public class Demo05 {public static void main(String[] args) throws Exception {// 1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 2.获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 3.预编译sql语句获取对象PreparedStatement statement = connection.prepareStatement("INSERT INTO student(`name`,age,score) VALUES (?,?,?)");// 4.补充占位符,执行sql语句statement.setString(1, "钱八");statement.setInt(2, 9);statement.setDouble(3, 99.9);int result = statement.executeUpdate();// 5.结果result是受影响的行数if (result > 0) {System.out.println("添加成功");} else {System.out.println("添加失败");}// 6.释放资源statement.close();connection.close();}
}
修改数据
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;public class Demo06 {public static void main(String[] args) throws Exception {// 1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 2.获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 3.预编译sql语句获取对象PreparedStatement statement = connection.prepareStatement("UPDATE student SET score = ? WHERE id = ?");// 4.补充占位符,执行sql语句statement.setDouble(1, 66.66);statement.setInt(2, 6);int result = statement.executeUpdate();// 5.结果result是受影响的行数if (result > 0) {System.out.println("修改成功");} else {System.out.println("修改失败");}// 6.释放资源statement.close();connection.close();}
}
删除数据
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;public class Demo07 {public static void main(String[] args) throws Exception {// 1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 2.获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 3.预编译sql语句获取对象PreparedStatement statement = connection.prepareStatement("DELETE FROM student WHERE id = ?");// 4.补充占位符,执行sql语句statement.setInt(1, 6);int result = statement.executeUpdate();// 5.结果result是受影响的行数if (result > 0) {System.out.println("删除成功");} else {System.out.println("删除失败");}// 6.释放资源statement.close();connection.close();}
}
实体类和 orm 思想
- 在使用 jdbc 操作数据库时,我们会发现数据都是零散的,明明在数据库中是一行完整的数据,到了 java 中变成了一个一个的变量,不利于维护和管理,由于 java 是面向对象的,所以一个表应该对应的是一个类,一行数据就对应的是 java 中的一个对象,一个列对应的是对象的属性,所以我们要把数据存储在一个载体里,这个载体就是实体类。
- orm(Object Relational Mapping,对象关系映射)思想,对象到关系数据库的映射,作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来,以面向对象的角度操作数据库中的数据,即一张表对应一个类,一行数据对应一个对象,一个列对应一个属性。
- jdbc 的这种过程我们称其为手动 orm,后续会升级为 orm 框架,例如 Mybatis 等。
实体类代码示例
package pojo;public class Student {private int id;private String name;private int age;private double score;public Student() {}public Student(int id, String name, int age, double score) {this.id = id;this.name = name;this.age = age;this.score = score;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public double getScore() {return score;}public void setScore(double score) {this.score = score;}@Overridepublic String toString() {return "Student{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", score=" + score +'}';}
}
我们通常会把所有的实体类放在同一个包下。
查询单个数据
import pojo.Student;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;public class Demo08 {public static void main(String[] args) throws Exception {// 1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 2.获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 3.预编译sql语句获取对象PreparedStatement statement = connection.prepareStatement("SELECT id,`name`,age,score FROM student WHERE id = ?");// 4.补充占位符,执行sql语句statement.setInt(1, 1);ResultSet result = statement.executeQuery();// 创建一个student对象Student student = new Student();// 5.获取结果while (result.next()) {int id = result.getInt("id");String name = result.getString("name");int age = result.getInt("age");double score = result.getDouble("score");// 将数据映射到对象上student.setId(id);student.setName(name);student.setAge(age);student.setScore(score);System.out.println(student);}// 6.释放资源result.close();statement.close();connection.close();}
}
查询多个数据,使用集合封装
import pojo.Student;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;public class Demo09 {public static void main(String[] args) throws Exception {// 1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 2.获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 3.预编译sql语句获取对象PreparedStatement statement = connection.prepareStatement("SELECT id,`name`,age,score FROM student");// 4.补充占位符,执行sql语句ResultSet result = statement.executeQuery();// 创建一个student集合List<Student> studentList = new ArrayList<>();// 5.获取结果while (result.next()) {Student student = new Student();int id = result.getInt("id");String name = result.getString("name");int age = result.getInt("age");double score = result.getDouble("score");// 将数据映射到对象上student.setId(id);student.setName(name);student.setAge(age);student.setScore(score);studentList.add(student);}studentList.forEach(System.out::println);// 6.释放资源result.close();statement.close();connection.close();}
}
主键回显
在数据中,执行新增操作时,主键列为自动增长,可以在表中直观的看到,但是在 java 程序中,我们执行完新增后,只能得到受影响行数,无法得知当前新增数据的主键值。在 java 程序中获取数据库中插入新数据后的主键值,并赋值给 java 对象,此操作为主键回显。
代码示例
只要在预编译 sql 时添加一个参数 Statement.RETURN_GENERATED_KEYS
即可。
import pojo.Student;import java.sql.*;public class Demo10 {public static void main(String[] args) throws Exception {// 1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 2.获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 3.预编译sql语句获取对象,告知数据库,返回新增数据主键的值String sql = "INSERT INTO student(`name`,age,score) VALUES (?,?,?)";PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);// 4.补充占位符,执行sql语句Student student = new Student(0, "钱八", 9, 99.9);statement.setString(1, student.getName());statement.setInt(2, student.getAge());statement.setDouble(3, student.getScore());int result = statement.executeUpdate();// 5.结果result是受影响的行数if (result > 0) {System.out.println("添加成功");// 获取新增数据的主键值,返回到student对象的id属性// 返回的主键值是一个单行单列的结果集ResultSet resultSet = statement.getGeneratedKeys();while (resultSet.next()) {int id = resultSet.getInt(1);student.setId(id);}System.out.println(student);resultSet.close();} else {System.out.println("添加失败");}// 6.释放资源statement.close();connection.close();}
}
批量操作
如果想要一次性插入多条数据,常用的方法就是使用循环,但是循环本质是执行多次插入操作,循环每进行一次就会执行一次 insert
插入,也就要与数据库交互一次,非常消耗时间。
批量操作的本质是对 sql 语句的拼接,将要执行的多条 insert ... value ()
语句拼接成 insert ... values (),(),.....
一条 sql 语句,这样可以减少与数据库的交互次数,节省时间。
循环插入代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;public class Demo11 {public static void main(String[] args) throws Exception {// 注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 预编译sql语句String sql = "INSERT INTO student(`name`,age,score) VALUES (?,?,?)";PreparedStatement statement = connection.prepareStatement(sql);// 获取开始执行,执行循环插入10000条数据long start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {statement.setString(1, "赵六"+i);statement.setInt(2, 55);statement.setDouble(3, 60);statement.executeUpdate();}// 获取结束时间,查看耗费时间long end = System.currentTimeMillis();System.out.println("耗费时间:"+(end-start));// 释放资源statement.close();connection.close();}
}
批量插入代码
想要执行批量插入,需要注意以下几点:
- 必须在连接数据库的 url 的
?
后面追加rewriteBatchedStatements=true
,允许批量操作。 - 新增 sql 语句必须使用 values,且语句最后不要追加
;
结束。 - 调用
addBatch()
方法,将 sql 语句进行批量添加操作。 - 统一执行批量操作,调用
executeBatch()
方法。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;public class Demo12 {public static void main(String[] args) throws Exception {// 注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 获取连接对象String url = "jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true";String username = "root";String password = "123456";Connection connection = DriverManager.getConnection(url, username, password);// 预编译sql语句String sql = "INSERT INTO student(`name`,age,score) VALUES (?,?,?)";PreparedStatement statement = connection.prepareStatement(sql);// 获取开始执行,执行循环插入10000条数据long start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {statement.setString(1, "赵六"+i);statement.setInt(2, 55);statement.setDouble(3, 60);statement.addBatch();}statement.executeBatch();// 获取结束时间,查看耗费时间long end = System.currentTimeMillis();System.out.println("耗费时间:"+ (end-start));// 释放资源statement.close();connection.close();}
}
可以对比,批量插入的时间有了显著降低。
连接池
现有问题
- 每次操作数据库都要获取新连接,使用完毕后就 close 释放,频繁的创建和销毁造成资源浪费。
- 连接的数量无法把控,对服务器来说压力巨大。
连接池介绍
连接池本质上就是数据库连接对象的缓冲区,通过配置,由连接池负责创建连接,管理连接,释放连接等操作。
预先创建数据库连接放入连接池,用户在请求时,通过池直接获取连接,使用完毕后,将连接放回池中,避免了频繁的创建和销毁,同时解决了创建的效率。
当池中无连接可用,且未达到上限时,连接池会新建连接。
池中连接达到上限,用户请求会等待,可以设置超时时间。
常见连接池
Druid
Druid 是阿里巴巴开源的一个综合数据库连接池解决方案,除了连接池功能外,还提供了 SQL 解析、监控等功能。
- 特点:
- 高性能,经过大规模生产环境验证。
- 内置监控面板,可以实时查看连接池状态和性能指标。
- 支持多种数据库类型。
- 提供了 SQL 注入防护机制。
- 可以动态调整连接池参数。
- 适用场景:特别适合于中国开发者,因为它是国内广泛使用的连接池之一,并且有中文文档和支持。
Hikari
HikariCP 是一个高性能、轻量级的连接池库,因其出色的性能而广受欢迎。它的设计目标是成为最快的 Java 连接池之一。
- 特点:
- 极高的性能。
- 简单配置,易于使用。
- 支持自动加载驱动程序。
- 提供详细的监控和统计信息。
- 兼容多种数据库(如 MySQL、PostgreSQL 等)。
- 适用场景:适用于追求极致性能的应用,特别是在高并发环境下。
Apache DBCP (Commons DBCP)
Apache DBCP 是由 Apache Commons 项目提供的一个成熟的连接池解决方案。
- 特点:
- 功能丰富,支持广泛的配置选项。
- 集成了与 Apache Tomcat 的紧密协作。
- 提供了两种不同的实现:BasicDataSource 和 PoolingDriver。
- 包含了对 JMX 的支持,便于管理和监控。
- 适用场景:适合那些希望使用稳定、功能齐全的连接池的应用程序,尤其是在 Web 应用中。
C3P0
C3P0 是另一个流行的开源连接池库,以其灵活性和可配置性著称。
- 特点:
- 强大的配置选项,包括自动测试连接、预加载连接等功能。
- 支持多数据源配置。
- 内置了对 Hibernate 的支持。
- 提供了良好的文档和社区支持。
- 适用场景:适用于需要高度定制化配置的应用程序,特别是当您需要复杂的连接管理策略时。
在目前的开发中,Druid 和 Hikari 是使用最多的两个连接池。
Druid 连接池使用
使用步骤
- 引入 jar 包。
- 创建
DruidDataSource
连接池对象 - 设置连接池的配置信息(包含必要信息和非必要信息)
- 通过连接池获取连接对象
- 回收连接(不是释放连接,而是将连接归还给连接池,给其它线程进行复用)
连接池配置信息
基本连接属性(必要信息)
url
:数据库的 JDBC URL。username
:用于连接数据库的用户名。password
:用于连接数据库的密码。driverClassName
:JDBC驱动程序类名(通常可以通过URL自动检测)。
以上4种属性是创建连接池时必须配置的属性,下面几种是非必要配置的属性,根据实际需求进行相关配置即可。
连接池大小配置
initialSize
:初始化时创建的连接数。minIdle
:最小空闲连接数。maxActive
:最大活跃连接数,即同时可用的最大连接数。maxWait
:当没有可用连接时,等待获取连接的最大时间(毫秒),默认值为-1表示无限期等待。
连续回收策略
timeBetweenEvictionRunsMillis
:检测连接是否空闲的时间间隔(毫秒),用于回收空闲连接,默认是60秒。minEvictableIdleTimeMillis
:连接在池中最小生存时间(毫秒),超过这个时间如果空闲则被回收,默认是1800秒(30分钟)。validationQuery
:用来验证连接是否有效的SQL查询语句,例如SELECT 'x'
或SELECT 1
。testWhileIdle
:建议设置为true
,表示在空闲时测试连接的有效性。testOnBorrow
和testOnReturn
:分别表示在从池中借用连接前和归还连接后是否进行有效性测试,默认都是false
。
PreparedStatement 缓存
poolPreparedStatements
:是否开启PSCache,默认为false
。对于支持的数据库,如Oracle,可以显著提高性能。maxPoolPreparedStatementPerConnectionSize
:每个连接上最大的PSCache数量。
监控与统计
filters
:指定要启用的过滤器,多个过滤器用逗号分隔。常见的过滤器包括:stat
:用于收集和展示连接池的状态信息。log4j
或common-log
:用于记录SQL日志。wall
:提供简单的SQL防火墙功能,防止某些类型的SQL注入攻击。
事务相关配置
defaultAutoCommit
:设置默认的自动提交模式,默认为null
,意味着使用数据库的默认设置。defaultTransactionIsolation
:设置默认的事务隔离级别,默认为null
,同样表示使用数据库的默认设置。
其它配置
removeAbandoned
:是否移除长时间未关闭的物理连接,默认为false
。removeAbandonedTimeout
:移除长时间未关闭的物理连接之前等待的超时时间(秒),默认为60秒。logAbandoned
:是否记录移除长时间未关闭的物理连接事件,默认为false
。
代码示例
硬编码实现(不推荐)
硬编码:将连接池的配置信息与 java 程序耦合在一起。
import com.alibaba.druid.pool.DruidDataSource;import java.sql.Connection;public class Demo12 {public static void main(String[] args) throws Exception {// 1.创建 DruidDataSource 连接池对象DruidDataSource dataSource = new DruidDataSource();// 2.设置连接池的配置信息(包含必要信息和非必要信息)// 2.1 设置必要信息dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC");dataSource.setUsername("root");dataSource.setPassword("123456");// 2.2 设置非必要信息dataSource.setInitialSize(10);dataSource.setMaxActive(20);// 3.通过连接池获取连接对象Connection connection = dataSource.getConnection();// 实现crud等操作System.out.println(connection);// 4.回收连接(不是释放连接,而是将连接归还给连接池,给其它线程进行复用)connection.close();}
}
软编码方式(推荐)
软编码:是指在项目目录下创建 resources
文件夹,标识该文件夹为资源目录,创建 db.properties
配置文件,将连接信息定义在该文件中。
步骤:
- 创建
Properties
集合,用于存储外部配置文件的 key 和 values 值。 - 读取外部配置文件,获取输入流,加载到
Properties
集合里。 - 基于
Properties
集合构建DruidDataSource
连接池。 - 通过连接池获取连接对象。
- 回收连接。
代码实现:
db.properties
代码:
# 必要信息
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/myjdbc?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username=root
password=root# 非必要信息
initialSize=10
maxActive=20
main
代码:
import com.alibaba.druid.pool.DruidDataSourceFactory;import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;public class Demo13 {public static void main(String[] args) throws Exception {// 1.创建Properties集合,用于存储外部配置文件的key和values值。Properties properties = new Properties();// 2.读取外部配置文件,获取输入流,加载到Properties集合里。InputStream inputStream = Demo13.class.getClassLoader().getResourceAsStream("db.properties");properties.load(inputStream);// 3.基于Properties集合构建DruidDataSource连接池。DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);// 4.通过连接池获取连接对象。Connection connection = dataSource.getConnection();// 实现crud等操作System.out.println(connection);// 5.回收连接。connection.close();}
}
Hikari 连接池使用
连接池配置信息
基本连接属性
jdbcUrl
:JDBC URL,指定要连接的数据库。username
:用于连接数据库的用户名。password
:用于连接数据库的密码。driverClassName
(可选):JDBC驱动程序类名,通常不需要设置,因为HikariCP会自动检测。
连接池大小配置
minimumIdle
:最小空闲连接数,默认为idleTimeout
和maximumPoolSize
中的较小者。maximumPoolSize
:最大活跃连接数,默认是10。idleTimeout
:空闲连接被关闭之前等待的时间(毫秒),默认是10分钟(600,000毫秒)。connectionTimeout
:获取连接的最大等待时间(毫秒),默认是30秒(30,000毫秒)。maxLifetime
:连接的最大生命周期(毫秒),超过这个时间将被关闭并替换,默认是30分钟(1800,000毫秒)。
连接测试
connectionTestQuery
:用于验证连接是否有效的SQL查询语句。对于大多数现代数据库驱动,此属性可以省略,因为HikariCP使用了更高效的“connection init SQL”方法。validationTimeout
:验证连接有效性的超时时间(毫秒),默认是5秒(5000毫秒)。
初始化与清理
initSQL
:在每个新连接创建时执行的SQL语句,可用于设置会话级别的参数或模式。poolName
:为连接池指定一个名称,便于监控和调试。
泄露检测
leakDetectionThreshold
:当连接从池中借出的时间超过给定的毫秒数时,记录警告日志。默认是0,表示禁用。
其他配置
autoCommit
:设置连接的自动提交模式,默认为true
。transactionIsolation
:设置事务隔离级别,默认为null
,即使用数据库的默认隔离级别。dataSourceProperties
:传递给数据源的其他属性,例如读取副本地址等。
代码实现(使用软编码方式)
步骤:
- 创建
Properties
集合,用于存储外部配置文件的 key 和 values 值。 - 读取外部配置文件,获取输入流,加载到
Properties
集合里。 - 创建
HikariConfig
连接池配置对象,将Properties
集合加载到HikariConfig
配置对象中。 - 基于
HikariConfig
连接池配置对象,构建HikariDataSource
连接池。 - 获取连接。
- 回收连接。
代码如下:
db.properties
:
# 必要信息
driverClassName=com.mysql.cj.jdbc.Driver
jdbcUrl=jdbc:mysql://127.0.0.1:3306/myjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username=root
password=123456# 非必要信息
minimumIdle=10
maximumPoolSize=20
main
:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;public class Demo14 {public static void main(String[] args) throws Exception {// 1.创建Properties集合,用于存储外部配置文件的key和values值。Properties properties = new Properties();// 2.读取外部配置文件,获取输入流,加载到Properties集合里。InputStream inputStream = Demo14.class.getClassLoader().getResourceAsStream("db.properties");properties.load(inputStream);// 3.创建HikariConfig连接池配置对象,将Properties集合加载到HikariConfig配置对象中。HikariConfig hikariConfig = new HikariConfig(properties);// 4.基于HikariConfig连接池配置对象,构建HikariDataSource连接池。HikariDataSource dataSource = new HikariDataSource(hikariConfig);// 5.获取连接Connection connection = dataSource.getConnection();System.out.println(connection);// 6.回收连接。connection.close();}
}
相关文章:
java基础学习——jdbc基础知识详细介绍
引言 数据的存储 我们在开发 java 程序时,数据都是存储在内存中的,属于临时存储,当程序停止或重启时,内存中的数据就会丢失,我们为了解决数据的长期存储问题,有以下解决方案: 通过 IO流书记&…...
第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
第十五届的题目在规定时间内做出了前5道,还有2道找时间再磨一磨。现在把做的一些思路总结如下: 题1:握手问题 问题描述 小蓝组织了一场算法交流会议,总共有 50人参加了本次会议。在会议上,大家进行了握手交流。按照惯例…...
基于JAVA的微信点餐小程序设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
2K320Hz显示器哪个好?
2K320Hz显示器哪个好?320Hz这种高刷新率的显示器确实很少见,那究竟哪个牌子哪个型号更适合你呢? 1.HKC G27H4Pro - 2K320Hz显示器哪个好 外观设计 - HKC G27H4Pro 2K320Hz显示器 三面微边框超震撼:采用三面微边框设计࿰…...
八股学习 微服务篇
微服务篇 常见面试内容Spring Cloud 常见组件注册中心Ribbon负载均衡策略服务雪崩 常见面试内容 Spring Cloud 常见组件 Spring Cloud有5个常见组件: Eureka/Nacos:注册中心;Ribbon:负载均衡;Feign:远程调用;Hystrix/Sentinel:服…...
C# 中 readonly 与 const 的使用
总目录 前言 在C#编程中,readonly 和 const 是两个用于定义不可变数据的关键字。它们都旨在创建那些一旦赋值后就不能再改变的字段或变量。尽管这两个关键字看起来相似,但它们有着不同的特性和适用场景。本文将深入探讨 readonly 和 const 的区别&#…...
Spring Boot Starter介绍
前言 大概10来年以前,当时springboot刚刚出现并没有流行,当时的Java开发者们开发Web应用主要是使用spring整合springmvc或者struts、iBatis、hibernate等开发框架来进行开发。项目里一般有许多xml文件配置,其中配置了很多项目中需要用到的Be…...
Kafak 单例生产者实现-C#操作
前面写了一篇入门操作的文章,因为工作需要,简单修改了下如何实现单例生产者。 Kafka入门-C#操作_c# kafka-CSDN博客文章浏览阅读1.6k次,点赞20次,收藏9次。2).报错:“kafka.zookeeper.ZooKeeperClientTimeoutException: Timed out waiting for connection while in state…...
软件开发学习路线——roadmap
推荐软件学习路线网站:https://roadmap.sh/get-started 有有关前端后端开发的学习路径,也有AI,移动开发,管理相关的学习路径 会有相应的词条路径,深入学习 右上角可以设置学习任务的完成情况...
移动端VR处理器和传统显卡的不同
骁龙 XR 系列芯片 更多地依赖 AI 技术 来优化渲染过程,而传统的 GPU 渲染 则倾向于在低画质下运行以减少负载。这种设计是为了在有限的硬件资源下(如移动端 XR 设备)实现高性能和低功耗的平衡。以下是具体的分析: 1. AI 驱动的渲染…...
Jenkins下载 Maven、Allure 插件并且配置环境
文章目录 Jenkins在插件中心下载 maven、allure插件maven插件下载allure插件下载 配置maven、allure 往期推荐: 最新! 在 Linux上搭建Jenkins环境! Jenkins邮件通知的详细配置含邮件通知模板! Jenkin配置企业微信通知 Jenkins在插件中心下载 maven、…...
C# OpenCV机器视觉:连通域分割
在一个阳光明媚得有些 “嚣张” 的午后,阿强像只好奇的小松鼠,一头扎进了他那乱得像被打劫过的实验室。这实验室里,各种电路板、奇形怪状的传感器和缠成一团的电线肆意横陈,仿佛在诉说着主人平日里为科研疯狂的 “战斗” 痕迹。阿…...
【嵌入式】总结——Linux驱动开发(三)
鸽了半年,几乎全忘了,幸亏前面还有两篇总结。出于快速体验嵌入式linux的目的,本篇与前两篇一样,重点在于使用、快速体验,uboot、linux、根文件系统不作深入理解,能用就行。 重新梳理一下脉络,本…...
python操作mysql
前言 在 Python3 中,我们可以使用mysqlclient或者pymysql三方库来接入 MySQL 数据库并实现数据持久化操作。二者的用法完全相同,只是导入的模块名不一样。我们推荐大家使用纯 Python 的三方库pymysql,因为它更容易安装成功。下面我们仍然以之…...
OpenCV:高通滤波之索贝尔、沙尔和拉普拉斯
目录 简述 什么是高通滤波? 高通滤波的概念 应用场景 索贝尔算子 算子公式 实现代码 特点 沙尔算子 算子公式 实现代码 特点 拉普拉斯算子 算子公式 实现代码 特点 高通滤波器的对比与应用场景 相关阅读 OpenCV:图像滤波、卷积与卷积核…...
游戏设备升级怎么选?RTX4070独显,ToDesk云电脑更具性价比
过新年、添喜气!正逢节期来临不知道各位是否都跟小编一样在考虑购置生活中的各样所需呐? 25年可谓是3A游戏大作之年,例如《GTA6》《文明7》《死亡搁浅2》《刺客信条:影》下半年落地的《塞尔达传说:新篇章》《生化危机9…...
【useLayoutEffect Hook】在浏览器完成布局和绘制之前执行副作用
目录 前言语法useLayoutEffect 对比 useEffect:示例 前言 useLayoutEffect 是 React 中的一个 Hook, 类似于 useEffect,但有一个关键的区别:它会在所有的 DOM 变更之后同步调用 effect。这意味着它可以读取 DOM 布局并同步重新渲…...
Llama 3:开源大模型的里程碑式突破
标题:Llama 3:开源大模型的里程碑式突破 文章信息摘要: Meta通过Llama 3展现了开源LLM的重大突破:采用超大规模训练数据和多阶段训练方法(SFT、rejection sampling、PPO和DPO),突破了传统的Chi…...
Spring 框架基础:IOC 与 AOP 原理剖析及面试要点
在上一篇中,我们深入探讨了 Java 反射机制,了解了它在运行时动态操作类和对象的强大能力。而今天,我们将进入 Spring 框架的世界。Spring 框架作为 Java 企业级开发中最流行的框架之一,极大地简化了企业级应用的开发过程。对于春招…...
《开源与合作:驱动鸿蒙Next系统中人工智能技术创新发展的双引擎》
在当今科技飞速发展的时代,鸿蒙Next系统作为一款具有创新性和前瞻性的操作系统,为人工智能技术的发展提供了广阔的舞台。而开源和合作则是推动鸿蒙Next系统中人工智能技术创新和发展的两大关键引擎。 开源:创新的源泉 代码共享与知识传播&am…...
Redis使用基础
1 redis介绍 Redis(Remote Dictionary Server ),即远程字典服务 ! 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。 使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并…...
React和Vue有什么区别,如何选择?
React和Vue有什么区别,如何选择? React 和 Vue 是当前最受欢迎的前端框架之一,两者在开发者中都有极高的声誉。它们都旨在帮助开发人员构建用户界面,但在实现方式和适用场景上有所不同。如果你正考虑在项目中选择 React 或 Vue&a…...
C++|开源日志库log4cpp和glog
文章目录 log4cpp 和 glog对比1. **功能对比**2. **易用性和配置**3. **性能**4. **线程安全**5. **日志输出**6. **功能扩展**7. **适用场景**8. **总结** 其它开源C日志库1. **spdlog**2. **easylogging**3. **Boost.Log**4. **loguru**5. **Poco Logging**6. **Qt Logging (…...
安卓程序作为web服务端的技术实现(三):AndServer作为服务
安卓程序作为web服务端的技术实现:AndServer 实现登录权限拦截-CSDN博客 安卓程序作为web服务端的技术实现(二):Room 实现数据存储-CSDN博客 经过两次捣鼓 AndServer已经能正常访问了 但是发现一个问题 就是当我app退出时 AndSe…...
数据结构(Java)——二叉树
1.概念 二叉树是一种树形数据结构,其中每个节点最多有两个子节点,通常被称为左子节点和右子节点。二叉树可以是空的(即没有节点),或者由一个根节点以及零个或多个左子树和右子树组成,其中左子树和右子树也分…...
深度学习系列76:流式tts的一个简单实现
1. 概述 使用queue,producer不断向queue中添加audio,然后consumer不断从queue中消费audio。 下面的样例使用melo来生成语音,需要先下载melo.tts。模型在https://myshell-public-repo-hosting.s3.amazonaws.com/openvoice/basespeakers/ZH/ch…...
数据结构(三) 排序/并查集/图
目录 1. 排序 2.并查集 3.图 1.排序: 1.1 概念: 排序就是将数据按照某种规则进行排列, 具有某种顺序. 分为内排序和外排序. 内排序就是: 将数据放在内存中的排序; 外排序是: 数据太多无法在内存中排序的. 1.2 插入排序: 插入排序包含: 直接插入排序和希尔排序. (1) 直接插入…...
WPA Supplicant 技术详解
目录 前言 1. 简介 2. 源码获取 3. 代码架构 3.1 模块结构 3.2. 主要文件和目录 3.3. 顶层模块 3.4 模块之间的关系 4. 工作流程简要描述 启动 加载配置 初始化 认证 数据传输 5. 编译与安装 5.1 编译 5.1.1 libnl库与openssl库准备 5.1.2 修改配置文件 5.…...
Avalonia UI MVVM DataTemplate里绑定Command
Avalonia 模板里面绑定ViewModel跟WPF写法有些不同。需要单独绑定Command. WPF里面可以直接按照下面的方法绑定DataContext. <Button Content"Button" Command"{Binding DataContext.ClickCommand, RelativeSource{RelativeSource AncestorType{x:Type User…...
macOS如何进入 Application Support 目录(cd: string not in pwd: Application)
错误信息 cd: string not in pwd: Application 表示在当前目录下找不到名为 Application Support 的目录。可能的原因如下: 拼写错误或路径错误:确保你输入的目录名称正确。目录名称是区分大小写的,因此请确保使用正确的大小写。正确的目录名…...
【探索 Kali Linux】渗透测试与网络安全的终极操作系统
探索 Kali Linux:渗透测试与网络安全的终极操作系统 在网络安全领域,Kali Linux 无疑是最受欢迎的操作系统之一。无论是专业的渗透测试人员、安全研究人员,还是对网络安全感兴趣的初学者,Kali Linux 都提供了强大的工具和灵活的环…...
《SwinIR:使用Swin-Transformer图像恢复》学习笔记
paper:2108.10257 GitHub:GitHub - JingyunLiang/SwinIR: SwinIR: 使用 Swin Transformer 进行图像修复 (官方仓库) 目录 摘要 1、Introduction 2、Related Work 2.1 图像修复 2.2 视觉Transformer…...
AR智慧点巡检系统探究和技术方案设计
一、项目背景 随着工业生产规模的不断扩大和设备复杂度的提升,传统的人工点巡检方式效率低下、易出错,难以满足现代化企业对设备运行可靠性和安全性的要求。AR(增强现实)技术的发展为点巡检工作带来了新的解决方案,通…...
电路研究9.2——合宙Air780EP使用AT指令
这里正式研究AT指令的学习了,之前只是接触的AT指令,这里则是深入分析AT指令了。 软件的开发方式: AT:MCU 做主控,MCU 发 AT 命令给模组的开发方式,模组仅提供标准的 AT 固件, 所有的业务控制逻辑…...
OpenCV相机标定与3D重建(62)根据两个投影矩阵和对应的图像点来计算3D空间中点的坐标函数triangulatePoints()的使用
加粗样式- 操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 这个函数通过使用立体相机对3维点的观测,重建这些点的三维坐标(以齐次坐标表示)。 cv::triangula…...
基于ollama,langchain,springboot从零搭建知识库四【设计通用rag系统】
需求: 1:可以自定义管理大模型,可自行选择ollama,openai,千问等大模型 2:自定义向量数据库,支持pgvector,elasticsearch,milvus(这三个目前比较常用ÿ…...
【Go面试】工作经验篇 (持续整合)
这里写目录标题 什么是逃逸分析服务端怎么接受客户端上传的文件说一下对gin框架的理解gin有哪些常用中间件gin怎么用swagger写接口文档nginx一般是用来做什么如果调用方法经常超时怎么办gin中怎么和mysql通信从mysql调数据到redis,如何同步延时双删redis ,mysql都不存在用户请求…...
“腾讯、钉钉、飞书” 会议开源平替,免费功能强大
在数字化时代,远程办公和线上协作越来越火。然而,市面上的视频会议工具要么贵得离谱,要么功能受限,甚至还有些在数据安全和隐私保护上让人不放心。 今天开源君给大家安利一个超棒的开源项目 - Jitsi Meet,这可是我在网…...
怎样使用树莓派自己搭建一套ADS-B信号接收系统
0 我们知道,ADS-B全称广播式自动相关监视系统,其实就是飞机发出的广播信号,用明码来对外发送自己的位置、高度、速度、航向等信息,是公开信息。连续接收到一架飞机发出的ADS-B信息后,可以通过其坐标点来描绘出飞机的航…...
终极的复杂,是简单
软件仿真拥有最佳的信号可见性和调试灵活性,能够高效捕获很多显而易见的常见错误,被大多数工程师熟练使用。 空间领域应用的一套数据处理系统(Data Handling System),采用抗辐FPGA作为主处理器,片上资源只包含10752个寄存器,软仿也是个挺花时间的事。 Few ms might take …...
粒子群算法 笔记 数学建模
引入: 如何找到全局最大值:如果只是贪心的话,容易被局部最大解锁定 方法有:盲目搜索,启发式搜索 盲目搜索:枚举法和蒙特卡洛模拟,但是样例太多花费巨量时间 所以启发式算法就来了,通过经验和规…...
Vue.js 嵌套路由和动态路由
Vue.js 嵌套路由和动态路由 在 Vue.js 开发中,Vue Router 是官方提供的路由管理器,用于构建单页应用(SPA)。它支持嵌套路由和动态路由,帮助开发者构建复杂的应用结构。 嵌套路由 嵌套路由允许在路由配置中定义子路由…...
Docker导入镜像
使用命令行进行处理: docker load < onething1_wxedge.tar如下图所示 查看状态 docker images...
C# OpenCV机器视觉:红外体温检测
在一个骄阳似火的夏日,全球却被一场突如其来的疫情阴霾笼罩。阿强所在的小镇,平日里熙熙攘攘的街道变得冷冷清清,人们戴着口罩,行色匆匆,眼神中满是对病毒的恐惧。阿强作为镇上小有名气的科技达人,看着这一…...
STM32项目分享:智能厨房安全检测系统
目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 PCB图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片: 哔哩哔哩视频链接: STM32智能厨房安全检测系统 (资料分…...
docker 安装 redis 详解
在平常的开发工作中,我们经常会用到 redis,那么 docker 下应该如何安装 redis 呢?简单来说:第一步:拉取redis镜像;第二步:设置 redis.conf 配置文件;第三步:编写 docker-…...
《探秘鸿蒙Next:人工智能助力元宇宙高效渲染新征程》
在元宇宙的宏大愿景中,高效的渲染技术是构建沉浸式虚拟世界的关键。鸿蒙Next凭借与人工智能的深度融合,为元宇宙的渲染带来了全新的解决方案和无限可能。 智能场景分析与优化 人工智能能够对元宇宙场景进行智能分析。鸿蒙Next可以利用AI技术对场景中的…...
nginx分发请求超时切换服务
nginx的upstream模块实现超时自动切换服务 upstream testfail {server 192.168.1.218 max_fails1 fail_timeout10s;server 192.168.1.129 max_fails1 fail_timeout10s;} max_fails代表失败尝试次数,达到设置的次数则视为该服务不可用, fail_timeout代…...
vulfocus/fastjson-cnvd_2017_02833复现
漏洞概述 Fastjson 是阿里巴巴开发的一个高性能的 Java 库,用于将 Java 对象转换成 JSON 格式(序列化),以及将 JSON 字符串转换回 Java 对象(反序列化)。 fastjson在解析json的过程中,支持使用type字段来指…...
.Net Core微服务入门全纪录(五)——Ocelot-API网关(下)
系列文章目录 1、.Net Core微服务入门系列(一)——项目搭建 2、.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上) 3、.Net Core微服务入门全纪录(三)——Consul-服务注…...