织梦博客网站模板下载,网站文章编辑器,甘肃企业网络推广软件,信阳建设网站引言
数据的存储
我们在开发 java 程序时#xff0c;数据都是存储在内存中的#xff0c;属于临时存储#xff0c;当程序停止或重启时#xff0c;内存中的数据就会丢失#xff0c;我们为了解决数据的长期存储问题#xff0c;有以下解决方案#xff1a;
通过 IO流书记数据都是存储在内存中的属于临时存储当程序停止或重启时内存中的数据就会丢失我们为了解决数据的长期存储问题有以下解决方案
通过 IO流书记将数据存储在本地磁盘中这样就解决了持久化问题但是数据没有结构和逻辑不方便管理和维护。通过关系型数据库例如 MySQL将数据按照特定的格式交由数据库管理系统维护关系型数据库是通过库和表分隔不同的数据表中的数据的存储方式是行和列区分相同格式不同值的数据。
数据的操作
数据存储在数据库中仅仅只是解决了我们数据存储的问题当我们程序运行时需要读取数据以及对数据做增删改的操作那么我们如何通过 java 程序对数据库中的数据做增删改查呢 答案就是今天的主角—— jdbc
jdbc
什么是 jdbc
JDBCJava 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尝试建立连接时它会使用这些方法。 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)
) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC;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(Cant 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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC负责管理事务提供了 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 11这样最后拼接的 sql 为 where user xxx or 11这样结果也为 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 语句多次执行的情况下可以复用不比每次重新编译和解析。 更加安全效率更高
常用方法
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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC;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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC;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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC;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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC;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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC;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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC;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 中的一个对象一个列对应的是对象的属性所以我们要把数据存储在一个载体里这个载体就是实体类。ormObject 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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC;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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC;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集合ListStudent 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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC;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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC;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 的 ? 后面追加 rewriteBatchedStatementstrue允许批量操作。新增 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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTCrewriteBatchedStatementstrue;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用于连接数据库的密码。driverClassNameJDBC驱动程序类名通常可以通过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?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC);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 代码
# 必要信息
driverClassNamecom.mysql.cj.jdbc.Driver
urljdbc:mysql://localhost:3306/myjdbc?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneUTC
usernameroot
passwordroot# 非必要信息
initialSize10
maxActive20main 代码
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 连接池使用
连接池配置信息
基本连接属性
jdbcUrlJDBC 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
# 必要信息
driverClassNamecom.mysql.cj.jdbc.Driver
jdbcUrljdbc:mysql://127.0.0.1:3306/myjdbc?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC
usernameroot
password123456# 非必要信息
minimumIdle10
maximumPoolSize20main
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();}
}