JDBC(Java Database Connectivity)作为 Java 访问数据库的基石,为开发者提供了一种标准、通用的方式来操作各种关系型数据库。本文将深入探讨 JDBC 的相关知识,从其基本概念、体系结构,到如何编写 JDBC 程序以及获取数据库连接等方面进行详细介绍,帮助了解和掌握 JDBC 技术。
1. JDBC概述
1.1 数据的持久化
持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系数据库来完成。
持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。
1.2 Java中的数据存储技术
在Java中,数据库存取技术可分为如下几类:
- JDBC直接访问数据库
- JDO (Java Data Object )技术
- 第三方O/R工具,如Hibernate, Mybatis 等
JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。
1.3 JDBC介绍
- JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。
- JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
- JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
- 如果没有JDBC,那么Java程序访问数据库时是这样的:
- 有了JDBC,Java程序访问数据库时是这样的:
- 总结如下:
1.4 JDBC体系结构
JDBC接口(API)包括两个层次:
- 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
- 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。
1.5 JDBC程序编写步骤
补充:ODBC(Open Database Connectivity,开放式数据库连接),是微软在Windows平台下推出的。使用者在程序中只需要调用ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。
步骤总结:
- 注册驱动【依赖的驱动类,进行安装】
- 获取连接【Connection建立连接】
- 创建发送SQL语句对象【Connection创建发送SQL语句的Statement】
- 发送SQL语句,并获取返回结果【Statement 发送sql语句到数据库并且取得返回结果】
- 结果集解析【结果集解析,将查询结果解析出来】
- 资源关闭【释放ResultSet、Statement 、Connection】
2. 获取数据库连接
MySQL官网下载数据库连接驱动jar包:https://downloads.mysql.com/archives/c-j/
- 对接的mysql数据库是5.7版本的话,可以使用
mysql-connector-java-5.1.37-bin.jar
。- 对接的mysql数据库是8.0版本的话,可以使用
mysql-connector-java-8.0.27-bin.jar
。
连接驱动包版本可以根据使用的数据库版本自行选择jar包,没必要一一对应,版本接近即可。
2.1 引入JAR包
这里使用IDEA编辑器进行举例。
- 首先在项目或模块下创建一个
lib文件夹
用于存放依赖库。- 将准备好的连接驱动jar包复制到
lib文件夹
下。- 右击选择“
Add as Library…
”的选项创建依赖库。如果界面进行汉化了的话可能叫“添加为库…
”。- 在创建依赖库的界面填写和选择一下内容,一般默认就行,确认保存就行了。
2.2 要素一:Driver接口实现类
2.2.1 Driver接口介绍
java.sql.Driver
接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager
)去调用这些Driver实现。
- Oracle的驱动:oracle.jdbc.driver.OracleDriver
- MySQL的驱动: com.mysql.jdbc.Driver(5.x)/ com.mysql.cj.jdbc.Driver(8.x)
驱动jar包从官网上下载下来之后,解压压缩包,找到对应的jar包拷贝到Java工程的一个目录中。这里习惯上新建一个
lib
文件夹,和src目录平级。然后选中lib文件夹右键
——>Add as Library
,与项目集成。
2.2.2 加载与注册JDBC驱动
加载驱动:加载 JDBC 驱动需调用 Class 类的静态方法 forName()
,向其传递要加载的 JDBC 驱动的类名。
Class.forName("com.mysql.jdbc.Driver");
Class.forName("com.mysql.cj.jdbc.Driver"); // 8.x
注册驱动:DriverManager 类是驱动程序管理器类,负责管理驱动程序。
使用
DriverManager.registerDriver(com.mysql.jdbc.Driver)
来注册驱动但是通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例。 因为 Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用
DriverManager.registerDriver() 方法
来注册自身的一个实例。
2.3 要素二:URL
JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。JDBC URL的标准由三部分组成,各部分间用冒号分隔。
jdbc:子协议:子名称
- 协议:JDBC URL中的协议总是jdbc
- 子协议:子协议用于标识一个数据库驱动程序
- 子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名
几种常用数据库的 JDBC URL
- MySQL的连接URL编写方式:
- jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值
jdbc:mysql://localhost:3306/test
jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)jdbc:mysql://localhost:3306/test?user=root&password=123456
- Oracle 9i的连接URL编写方式:
- jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称
jdbc:oracle:thin:@localhost:1521:test
- SQLServer的连接URL编写方式:
- jdbc:sqlserver://主机名称:sqlserver服务端口号:DatabaseName=数据库名称
jdbc:sqlserver://localhost:1433:DatabaseName=test
2.4 要素三:用户名和密码
用户名和密码可以用“属性名=属性值”方式,即创建属性变量的方式告诉数据库。
可以调用 DriverManager
类的 getConnection()
方法建立到数据库的连接。
2.5 连接举例
2.5.1 连接方式一
public void testConnection1() {
try {
//1.提供java.sql.Driver接口实现类的对象
Driver driver = null;
driver = new com.mysql.cj.jdbc.Driver();
//2.提供url,指明具体操作的数据
String url = "jdbc:mysql://localhost:3306/test";
//3.提供Properties的对象,指明用户名和密码
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "abc123");
//4.调用driver的connect(),获取连接
Connection conn = driver.connect(url, info);
System.out.println(conn); // 打印获取连接
} catch (SQLException e) {
e.printStackTrace();
}
}
说明:上述代码中显式出现了第三方数据库的API
2.5.2 连接方式二
public void testConnection2() {
try {
//1.实例化Driver
String className = "com.mysql.cj.jdbc.Driver";
Class clazz = Class.forName(className);
Driver driver = (Driver) clazz.newInstance();
//2.提供url,指明具体操作的数据
String url = "jdbc:mysql://localhost:3306/test";
//3.提供Properties的对象,指明用户名和密码
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "abc123");
//4.调用driver的connect(),获取连接
Connection conn = driver.connect(url, info);
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
说明:相较于方式一,这里使用反射实例化Driver,不在代码中体现第三方数据库的API。体现了面向接口编程思想。
2.5.3 连接方式三
public void testConnection3() {
try {
//1.数据库连接的4个基本要素:
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "abc123";
String driverName = "com.mysql.cj.jdbc.Driver";
//2.实例化Driver
Class clazz = Class.forName(driverName);
Driver driver = (Driver) clazz.newInstance();
//3.注册驱动
DriverManager.registerDriver(driver);
//4.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
说明:使用DriverManager实现数据库的连接。体会获取连接必要的4个基本要素。
2.5.4 连接方式四
public void testConnection4() {
try {
//1.数据库连接的4个基本要素:
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "abc123";
String driverName = "com.mysql.cj.jdbc.Driver";
//2.加载驱动 (①实例化Driver ②注册驱动)
Class.forName(driverName);
//Driver driver = (Driver) clazz.newInstance();
//3.注册驱动
//DriverManager.registerDriver(driver);
/*
可以注释掉上述代码的原因,是因为在mysql的Driver类中声明有:
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
*/
//3.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
说明:不必显式的注册驱动了。因为在DriverManager的源码中已经存在静态代码块,实现了驱动的注册。
从JDK6开始,不再需要显式地调用
Class.forName()
来加载 JDBC 驱动程序,只要在类路径中集成了对应的jar文件,会自动在初始化时注册驱动程序。但是不要去掉,建议保留。
2.5.5 连接方式五(使用properties文件配置参数)
public void testConnection5() throws Exception {
//1.加载配置文件
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
//2.读取配置信息
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//3.加载驱动
Class.forName(driverClass);
//4.获取连接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
其中,配置文件声明在工程的src目录下:【jdbc.properties】
user=root
password=abc123
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.cj.jdbc.Driver
说明:使用配置文件的方式保存配置信息,在代码中加载配置文件。
使用配置文件的好处:
①实现了代码和数据的分离,实现了解耦。如果需要修改配置信息,直接在配置文件中修改,不需要深入代码
②如果修改了配置信息,省去重新编译的过程。
3. 使用PreparedStatement实现CRUD操作
3.1 操作和访问数据库
数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。在 java.sql 包
中有 3 个接口分别定义了对数据库的调用的不同方式:
Statement
:用于执行静态 SQL 语句并返回它所生成结果的对象。PrepatedStatement
:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。CallableStatement
:用于执行 SQL 存储过程。
3.2 使用Statement操作数据表
通过调用 Connection 对象的 createStatement()
方法创建该对象。该对象用于执行静态的 SQL 语句,能实现对数据表的查询、插入、更新和删除等操作,并且返回执行结果。
Statement 接口中定义了下列方法用于执行 SQL 语句:
int excuteUpdate(String sql)
// 用于执行 INSERT、UPDATE、DELETE 等 SQL 语句,以及 DDL(数据定义语言)语句,如 CREATE TABLE、ALTER TABLE 等。
// 该方法返回一个整数,表示受影响的行数。如果是 DDL 语句,通常返回 0。
ResultSet executeQuery(String sql)
// 该方法用于执行静态的 SELECT 语句,返回一个 ResultSet 对象,这个对象包含了查询所返回的结果集。
Boolean execute(String sql)
// 可以执行任意的 SQL 语句,包括 SELECT、INSERT、UPDATE、DELETE 以及 DDL 语句。
// 该方法返回一个布尔值,如果第一个结果是 ResultSet 对象(即执行的是查询语句),则返回 true;
// 如果是更新计数或者没有结果,则返回 false。可以通过 getResultSet() 方法获取 ResultSet 对象,通过 getUpdateCount() 方法获取受影响的行数。
使用Statement进行查询操作的代码演示:
package com.test.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class StatementTest {
// 数据库连接信息
private static final String DB_URL = "jdbc:mysql://10.10.20.235:3306/rpa_database";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "root";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 注册 JDBC 驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立数据库连接
System.out.println("正在连接数据库...");
conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
// 创建 Statement 对象
stmt = conn.createStatement();
// 查询数据
String selectSql = "SELECT * FROM rpa_jnhl_customer_table";
ResultSet rs = stmt.executeQuery(selectSql);
System.out.println("查询结果:");
while (rs.next()) {
int id = rs.getInt("id");
String account = rs.getString("account");
int department_code = rs.getInt("department_code");
String erp_code = rs.getString("erp_code");
System.out.println("ID: " + id + ", 账号: " + account + ", 部门编码: " + department_code + ", ERP部门编码: " + erp_code);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (stmt != null) stmt.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
if (conn != null) conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
使用Statement操作数据表存在一些弊端:
- 性能问题:每次执行 SQL 语句时,数据库都需要对 SQL 语句进行编译和解析。如果多次执行结构相似但参数不同的 SQL 语句,数据库会重复进行编译和解析操作,增加了系统开销,降低了性能。
- 代码可读性和可维护性差:当 SQL 语句比较复杂,或者需要拼接大量的参数时,使用 Statement 会使代码变得冗长和复杂,降低了代码的可读性和可维护性。
- 存在SQL注入问题(最主要):如果用户输入的内容包含恶意的 SQL 代码,就可能改变原 SQL 语句的语义,从而执行非预期的操作,这就是 SQL 注入攻击。
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:
SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1'
) ,从而利用系统的 SQL 引擎完成恶意行为的做法。
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement
(从Statement扩展而来)取代 Statement 就可以了。
3.3 PreparedStatement的使用
3.3.1 PreparedStatement介绍
PreparedStatement 接口继承自 Statement 接口,主要用于执行预编译的 SQL 语句。相比于 Statement,PreparedStatement 提供了更高效、更安全和更便捷的方式来操作数据库。
- 预编译机制
PreparedStatement 的核心优势在于预编译机制。当使用 PreparedStatement 时,SQL 语句会先被发送到数据库进行预编译,数据库会对 SQL 语句的语法进行检查和优化,并生成执行计划。之后,无论执行多少次该 SQL 语句,只要语句结构不变,数据库就可以直接使用之前生成的执行计划,避免了重复编译的开销。
......
// 预编译 SQL 语句
String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, "John");
pstmt.setInt(2, 25);
// 执行 SQL 语句
int rowsInserted = pstmt.executeUpdate();
......
PreparedStatement 对象所代表的 SQL 语句中的
参数用问号(?)
来表示,调用 PreparedStatement 对象的setXxx()
方法来设置这些参数。
setXXX(int parameterIndex, XXX value)
:用于为占位符设置具体的值,parameterIndex 表示占位符的索引(从 1 开始),XXX 表示数据类型,如setString()
、setInt()
、setDouble()
等。
3.3.2 PreparedStatement对比优势
- 预编译机制避免了重复编译的开销,提高了性能。
- SQL 语句和参数是分离的,通过
setXXX()
方法设置参数,使代码更加清晰和易于理解。同时,如果需要修改 SQL 语句或参数,只需要修改相应的部分,而不需要像 Statement 那样在拼接的字符串中进行复杂的修改。 - 使用占位符来表示参数,数据库会将参数作为一个整体进行处理,不会将其解析为 SQL 语句的一部分,从而有效防止了 SQL 注入攻击。
3.3.3 Java与SQL对应数据类型转换表
以下是整合后的 Java 与 SQL 常见数据类型对应关系表格:
SQL 数据类型 | Java 数据类型 | 说明 |
---|---|---|
整数类型 | ||
TINYINT |
byte 或 Byte |
用于表示较小范围的整数,通常是 -128 到 127 |
SMALLINT |
short 或 Short |
表示比 TINYINT 范围稍大的整数,如 -32768 到 32767 |
INT 或 INTEGER |
int 或 Integer |
常用的整数类型,范围一般为 -2147483648 到 2147483647 |
BIGINT |
long 或 Long |
用于表示大范围的整数 |
浮点类型 | ||
FLOAT |
float 或 Float |
单精度浮点数,精度相对较低 |
DOUBLE |
double 或 Double |
双精度浮点数,精度较高 |
字符类型 | ||
CHAR |
String |
定长字符串,当存储的字符串长度小于定义长度时会用空格填充 |
VARCHAR |
String |
变长字符串,仅存储实际长度的字符 |
TEXT (如 MySQL) |
String |
用于存储较长的文本数据 |
日期和时间类型 | ||
DATE |
java.sql.Date |
仅表示日期,不包含时间信息 |
TIME |
java.sql.Time |
仅表示时间,不包含日期信息 |
TIMESTAMP |
java.sql.Timestamp |
包含日期和时间信息,精度较高 |
布尔类型 | ||
BOOLEAN |
boolean 或 Boolean |
表示逻辑值,通常为 true 或 false |
二进制类型 | ||
BLOB |
byte[] |
用于存储二进制大对象,如图片、文件等 |
- 注意事项
- 不同数据库系统对 SQL 数据类型的实现可能存在细微差异,例如
TEXT
在不同数据库中的具体长度限制和性能表现可能不同。- 在实际开发中,使用 JDBC 从数据库中获取数据时,要注意数据类型的正确转换,避免出现
ClassCastException
等异常。
3.3.4 使用PreparedStatement实现增、改、删、查操作
package com.test.jdbc;
import org.junit.Test;
import java.sql.*;
public class PreparedstatementTest {
// 插入一条数据
@Test
public void testInsert() throws ClassNotFoundException, SQLException {
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://10.10.20.235/rpa_database", "root", "root");
// 3.编写sql语句
String sql = "Insert into rpa_jnhl_customer_table(id,account,department_code,erp_code) values(?,?,?,?)";
// 4.创建PS对象,传入SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 5.占位符赋值
preparedStatement.setObject(1,27);
preparedStatement.setObject(2,"142119");
preparedStatement.setObject(3,10119);
preparedStatement.setObject(4,"LH234923");
// 6.执行sql语句
int rows = preparedStatement.executeUpdate();
// 7.输出结果
if (rows > 0) {
System.out.println("数据插入成功!");
} else {
System.out.println("数据插入失败!");
}
// 8. 关闭资源
preparedStatement.close();
connection.close();
}
// 更新一条数据
@Test
public void testUpdate() throws ClassNotFoundException, SQLException {
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://10.10.20.235/rpa_database", "root", "root");
// 3.编写sql语句
String sql = "UPDATE rpa_jnhl_customer_table SET id=? WHERE id=?";
// 4.创建PS对象,传入SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 5.占用符赋值
preparedStatement.setObject(1,14);
preparedStatement.setObject(2,27);
// 6.执行sql语句
int rows = preparedStatement.executeUpdate();
// 7.输出结果
if (rows > 0) {
System.out.println("数据更改成功!");
} else {
System.out.println("数据更改失败!");
}
// 8. 关闭资源
preparedStatement.close();
connection.close();
}
// 删除一条数据
@Test
public void testDelete() throws ClassNotFoundException, SQLException {
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://10.10.20.235/rpa_database", "root", "root");
// 3.编写sql语句
String sql = "DELETE FROM rpa_jnhl_customer_table WHERE id=?";
// 4.创建PS对象,传入SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 5.占用符赋值
preparedStatement.setObject(1,14);
// 6.执行sql语句
int rows = preparedStatement.executeUpdate();
// 7.输出结果
if (rows > 0) {
System.out.println("数据删除成功!");
} else {
System.out.println("数据删除失败!");
}
// 8. 关闭资源
preparedStatement.close();
connection.close();
}
// 查看一条数据
@Test
public void testSelect() throws ClassNotFoundException, SQLException {
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://10.10.20.235/rpa_database", "root", "root");
// 3.编写sql语句
String sql = "SELECT id,account,department_code,erp_code FROM rpa_jnhl_customer_table WHERE id=?";
// 4.创建PS对象,传入SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 5.占位符赋值
preparedStatement.setObject(1,14);
// 6.执行sql语句
ResultSet resultSet = preparedStatement.executeQuery();
// 7.输出结果
System.out.println("查询结果:");
while (resultSet.next()) {
int id = resultSet.getInt("id");
String account = resultSet.getString("account");
int department_code = resultSet.getInt("department_code");
String erp_code = resultSet.getString("erp_code");
System.out.println("ID: " + id + ", 账号: " + account + ", 部门编码: " + department_code + ", ERP部门编码: " + erp_code);
}
// 8. 关闭资源
preparedStatement.close();
connection.close();
}
}
- 将查询的数据不进行直接打印输出,而是将其封装到一个
List<Map>
集合对象中。实现数据从数据库——ResultSet——java的流转。存储思路就是一行数据一个Map集合,key取列名,value取数据。整个查询数据再封装到一个List集合。
package com.test.jdbc;
import org.junit.Test;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class PreparedstatementTest {
@Test
public void testSelect1() throws ClassNotFoundException, SQLException {
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.创建连接
Connection connection = DriverManager.getConnection("jdbc:mysql://10.10.20.235/rpa_database", "root", "root");
// 3.SQL语句
String sql = "SELECT * FROM rpa_jnhl_customer_table";
// 4.创建ps对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 5.执行sql
ResultSet resultSet = preparedStatement.executeQuery();
// 6. 结果集解析
List<Map> list = new ArrayList<>();
// 获取列的信息对象
ResultSetMetaData metaData = resultSet.getMetaData();
// 获取总列数
int columnCount = metaData.getColumnCount();
while (resultSet.next()){
Map map = new HashMap();
// 自动遍历列,注意,从1开始,小于等于总列数
for (int i = 1; i <= columnCount; i++) {
// 获取指定列下角标的值
Object value = resultSet.getObject(i);
// 获取指定列下角标的名称
String columnLabel = metaData.getColumnLabel(i);
map.put(columnLabel,value);
}
list.add(map);
// 8. 关闭资源
preparedStatement.close();
connection.close();
}
}
}
- 注意:数据库的链接和关闭等操作是通用重复的,可以进行封装成一个
JDBCUtils
工具类进行调用即可。这里使用Properties
配置文件的方式读取。
/**
* @Description 操作数据库的工具类
*/
public class JDBCUtils {
/**
* @Description 获取数据库的连接
*/
public static Connection getConnection() throws Exception {
// 1.读取配置文件中的4个基本信息
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
// 2.加载驱动
Class.forName(driverClass);
// 3.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
/**
* @Description 关闭连接和Statement的操作
*/
public static void closeResource(Connection conn,Statement ps){
try {
if(ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* @Description 关闭资源操作
*/
public static void closeResource(Connection conn,Statement ps,ResultSet rs){
try {
if(ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(rs != null)
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
使用JDBCUtils工具类实现修改操作。
//修改customers表的一条记录
@Test
public void testUpdate(){
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();
//2.预编译sql语句,返回PreparedStatement的实例
String sql = "update customers set name = ? where id = ?";
ps = conn.prepareStatement(sql);
//3.填充占位符
ps.setObject(1,"莫扎特");
ps.setObject(2, 18);
//4.执行
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}finally{
//5.资源的关闭
JDBCUtils.closeResource(conn, ps);
}
}
说明:针对于表的字段名与类的属性名不相同的情况
- 必须声明sql时,使用类的属性名来命名字段的别名;
- 使用ResultSetMetaData时,需要使用
getColumnLabel()
来替换getColumnName()
获取列的别名。- 如果sql中没有给字段其别名,
getColumnLabel()
获取的就是列名。
3.3.4 ResultSet与ResultSetMetaData
- ResultSet
ResultSet 接口表示数据库查询操作返回的结果集,它是一个包含查询结果的表格,包含了多行数据,每一行数据又包含多个列。可以通过 ResultSet 接口提供的方法来遍历结果集、获取每一行的数据以及对数据进行操作。
ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next()
方法移动到下一行。调用 next()
方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。
【移动光标的方法】
next()
:最为常用的方法。将光标从当前位置向下移动一行,如果下一行存在则返回 true,否则返回 false。常用于遍历结果集。previous()
:将光标向上移动一行,前提是结果集支持可滚动。first()
:将光标移动到结果集的第一行,如果结果集不为空则返回 true。last()
:将光标移动到结果集的最后一行,如果结果集不为空则返回 true。
当指针指向一行时, 可以通过调用 getXxx(int index)
或 getXxx(int columnName)
获取每一列的值。如果不确定数据类型的情况下,可以直接使用 getObject()
。
注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。
- ResultSetMetaData
ResultSetMetaData 接口提供了关于 ResultSet 对象中列的元数据信息,例如列的数量、列名、列的数据类型等。通过 ResultSet 的 getMetaData()
方法可以获取 ResultSetMetaData 对象。
【常用方法】
getColumnCount()
:返回结果集中的列数。getColumnName(int column)
:返回指定列的列名,列索引从 1 开始。getColumnTypeName(int column)
:返回指定列的数据库特定的类型名称。getColumnLabel(int column)
:返回指定列的标签(通常是列名或别名)。
- 两者关系
- ResultSet 用于存储和操作查询结果的数据,而 ResultSetMetaData 用于获取查询结果的元数据信息。
- ResultSet 对象可以通过
getMetaData()
方法获取与之关联的 ResultSetMetaData 对象,从而获取结果集的列信息。
3.3.5 资源的释放
在使用 JDBC(Java Database Connectivity)进行数据库操作时,及时关闭使用的资源是非常重要的,这有助于避免资源泄漏和提高系统性能。
- 关闭顺序
关闭资源时需要遵循一定的顺序,通常是先关闭 ResultSet
,再关闭 Statement
,最后关闭 Connection
。这是因为 ResultSet
依赖于 Statement
,而 Statement
依赖于 Connection
,如果先关闭了 Connection
或 Statement
,ResultSet
就无法正常工作。
通常使用 try-catch-finally
块来确保资源的关闭。以下是一个示例代码:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcResourceClosingOld {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 加载数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立数据库连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb", "root", "password");
// 创建 Statement 对象
stmt = conn.createStatement();
// 执行查询操作
rs = stmt.executeQuery("SELECT * FROM users");
// 处理查询结果
while (rs.next()) {
System.out.println(rs.getString("username"));
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 关闭 ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 关闭 Statement
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 关闭 Connection
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
3.3.6 preparedStatement使用方式总结
- 使用步骤总结
- 注册驱动
- 获取连接
- 编写SQL语句
- 创建preparedstatement并且传入SQL语句结构
- 占位符赋值
- 发送SQL语句,并且获取结果
- 结果集解析
- 关闭资源
- 使用API总结
【注册驱动】
- 方案1:调用静态方法,但是会注册两次。
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
- 方案2:反射触发(常用)
Class .forName ("com.mysql.cj.jdbc.Driver");
【获取连接】
获取连接的方法参数填报有3种方式可以选择。
Connection connection = DriverManager.getConnection(参数填报);
- 3个参数:
(String url, String user, string password)
;- 2个参数:
(String url, Properties info(user password))
;- 1个参数:
(String url?user=账号&password=密码)
;
【创建Statement】
- 静态:
Statement statement = connection.createstatement();
- 预编译(推荐):
Preparedstatement preparedstatement = connection.preparedStatement(sql语句结构);
【占位符赋值】
preparedstatement.set0bject(值);
值的填写按照?
的位置,从1开始,从左到右依次赋值。
【发送sql语司获取结果】
int rows = preparedstatement.executeUpdate(); // 非DQL Resultset resultset = preparedstatement.executeQuery(); // DQL
【查询结果集解析】
移动光标指向行数据
next()
,将结果集的光标从当前位置向下移动一行。通常用于遍历结果集。获取列的数据
getXXX(列名或索引值)
,根据列名或列索引获取不同类型的列值,索引值从1开始,获取类型可以是Int、String等,一般使用getObject()
。获取列的信息
ResultSetMetaData metaData = rs.getMetaData();
:获取结果集的元数据,包含列的数量、列名、列的数据类型等信息。int columnCount = metaData.getColumnCount();
:获取结果集的总列数。String columnName = metaData.getColumnName(i);
:用于返回结果集中指定列的名称,参数 i 表示列的索引,索引从 1 开始。String columnLebal = metaData.getColumnLebal(i);
:用于获取结果集中指定列的标签(label)。这里的列标签通常是在 SQL 查询语句里使用 AS 关键字指定的别名,如果没有使用别名,那么列标签一般就是列的实际名称。参数 i 表示列的索引,索引从 1 开始。
【关闭资源】
- 关闭资源时,都需要调用对象的
close()
方法进行关闭操作。执行时,要遵循先关闭 ResultSet,再关闭 Statement(或 PreparedStatement),最后关闭 Connection 的顺序。
4. 总结
4.1 核心API理解
4.1.1 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
在 Java 中,当使用 JDBC(Java Database Connectivity)连接数据库时,需要加载数据库特定的驱动程序,以便与数据库进行通信。加载驱动程序的目的是为了注册驱动程序,使得 JDBC API 能够识别并与特定的数据库进行交互。
从JDK6开始,不再需要显式地调用
Class.forName()
来加载 JDBC 驱动程序,只要在类路径中集成了对应的jar文件,会自动在初始化时注册驱动程序。
4.1.2 Connection
Connection接口是JDBC API的重要接口,用于建立与数据库的通信通道。换而言之,Connection对象不为空,则代表一次数据库连接。
- 在建立连接时,需要指定数据库URL、用户名、密码参数。
- URL:jdbc:mysql://localhost:3306/atguigu
- jdbc:mysql://IP地址:端口号/数据库名称?参数键值对1&参数键值对2
- URL:jdbc:mysql://localhost:3306/atguigu
Connection
接口还负责管理事务,Connection
接口提供了 commit
和 rollback
方法,用于提交事务和回滚事务。
在使用JDBC技术时,必须要先获取Connection对象,在使用完毕后,要释放资源,避免资源占用浪费及泄漏。
4.1.3 Statement
Statement
接口用于执行 SQL 语句并与数据库进行交互。它是 JDBC API 中的一个重要接口。通过 Statement
对象,可以向数据库发送 SQL 语句并获取执行结果。
- 结果可以是一个或多个结果。
- 增删改:受影响行数单个结果。
- 查询:单行单列、多行多列、单行多列等结果。
但是Statement
接口在执行SQL语句时,会产生SQL注入攻击问题
:当使用 Statement
执行动态构建的 SQL 查询时,往往需要将查询条件与 SQL 语句拼接在一起,直接将参数和SQL语句一并生成,让SQL的查询条件始终为true得到结果。
4.1.4 PreparedStatement
PreparedStatement
是 Statement
接口的子接口,用于执行预编译
的 SQL 查询,作用如下:
- 预编译SQL语句:在创建PreparedStatement时,就会预编译SQL语句,也就是SQL语句已经固定。
- 防止SQL注入:
PreparedStatement
支持参数化查询,将数据作为参数传递到SQL语句中,采用?占位符的方式,将传入的参数用一对单引号包裹起来’',无论传递什么都作为值。有效防止传入关键字或值导致SQL注入问题。 - 性能提升:PreparedStatement是预编译SQL语句,同一SQL语句多次执行的情况下,可以复用,不必每次重新编译和解析。
实际使用都是基于PreparedStatement进行实现,更安全、效率更高!
4.1.5 ResultSet
ResultSet
是 JDBC API 中的一个接口,用于表示从数据库中执行查询语句所返回的结果集
。它提供了一种用于遍历和访问查询结果的方式。
- 遍历结果:ResultSet可以使用
next()
方法将游标移动到结果集的下一行,逐行遍历数据库查询的结果,返回值为boolean类型,true代表有下一行结果,false则代表没有。 - 获取单列结果:可以通过getXxx的方法获取单列的数据,该方法为重载方法,支持索引和列名进行获取。
4.2 常见问题
4.2.1 资源的管理
在使用JDBC的相关资源时,比如Connection、PreparedStatement、ResultSet,使用完毕后,要及时关闭这些资源以释放数据库服务器资源和避免内存泄漏是很重要的。
4.2.2 SQL语句问题
java.sql.SQLSyntaxErrorException
:SQL语句错误异常,一般有几种可能:
- SQL语句有错误,检查SQL语句!建议SQL语句在SQL工具中测试后再复制到Java程序中!
- 连接数据库的URL中,数据库名称编写错误,也会报该异常!
4.2.3 SQL语句未设置参数问题
java.sql.SQLException:No value specified for parameter 1
在使用预编译SQL语句时,如果有
?
占位符,要为每一个占位符赋值,否则报该错误!
4.2.4 用户名或密码错误问题
连接数据库时,如果用户名或密码输入错误,也会报SQLException,容易混淆!所以一定要看清楚异常后面的原因描述
4.2.5 通信异常
在连接数据库的URL中,如果IP或端口写错了,会报如下异常:
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
综上所述,JDBC 技术为 Java 程序与数据库之间的交互搭建了一座坚实的桥梁。通过学习和掌握 JDBC 的基本概念、体系结构以及程序编写步骤,开发者能够更加灵活、高效地进行数据库操作。希望本文能够为使用 JDBC 技术进行数据库开发时提供有益的参考,让在实际项目中能够更加熟练地运用这一强大的工具。