JDBC 常用类介绍
# 4.JDBC 常用类介绍
上一节我们快速入门了下 JDBC,并使用它做了一个小案例,现在对各个常用的类做进一步的说明:
- DriverManager:驱动管理对象
- Connection:数据库连接对象
- Statement、PreparedStatement:执行 sql 的对象
- ResultSet:结果集对象,封装查询结果
严格来说,以上这些都是接口,位于 java.sql
包下;
javax.sql
是数据库扩展包,提供数据库额外的功能,如连接池
# DriverManager:驱动管理对象
DriverManager 主要用两个功能:
- 注册驱动
- 获取数据库连接对象
# 功能 1:注册驱动
注册驱动,告诉程序该使用哪一个数据库驱动 jar。
DriverManager 是一个接口,在 API 文档中有这样一个方法:static void registerDriver(Driver driver)
,作用是注册与给定的驱动程序。调用这个方法,实际上才算告诉了应用程序我们要使用哪个数据库驱动类。
但我们写代码的时候,直接通过反射加载驱动类:Class.forName("com.mysql.cj.jdbc.Driver")
,好像也没有执行上面的注册方法?
这是因为驱动类有个 static 静态代码块,其中就会执行 registerDriver
方法,注册驱动,因此我们只要一加载这个类,就会自动注册。
说起一加载类就执行的代码,读者们想到了什么?static (opens new window) 代码块。通过查看 MySQL 驱动类,com.mysql.cj.jdbc.Driver
源码,可以看到有 static 代码块,会执行 register 方法:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
2
3
4
5
6
7
注意点 1:读者可以直接在 IDEA 里查看 jar 包中的源码
注意点 2:mysql5 之后的驱动 jar 包可以省略注册驱动的步骤 Class.forName(.....)
,因为会自动注册驱动,所以可以如下省略注册的代码:
// MySQL5驱动类注册方法
Class.forName("com.mysql.jdbc.Driver");
// MySQL8驱动类注册方法
Class.forName("com.mysql.cj.jdbc.Driver");
2
3
4
5
自动注册的原理:寻找 jar 包的 META-INF/services/java.sql.Driver
文件,并读取里面配置的类然后加载。所以如果读者后续更换其他数据库驱动类后,想要注册但不知道驱动类的全类名,可以通过这个方法找到。
注意,MySQL 驱动 5.1.6 版本之前的 jar 包中,没有 META-INF/services 目录,因此如果用的是早期的 JDBC 驱动,还是得用 Class.forName(“com.mysql.jdbc.Driver”)
# 功能 2: 获取数据库连接对象
DriverManager 还有一个重要的方法,获取 Connection 对象:static Connection getConnection(String url, String user, String password)
参数说明:
url:指定连接的路径。注意,不同数据库的 URL 语法可能不一样。以 MySQL 为例:
- 语法:
jdbc:mysql://ip地址(或域名):端口号/数据库名称
- 例子:
jdbc:mysql://localhost:3306/db3
- 如果连接的是本机 mysql 服务器,并且 mysql 服务默认端口是 3306,则 url 可以简写为:
jdbc:mysql:///数据库名称
- 语法:
user:用户名
password:密码
# Connection:数据库连接对象
功能:
获取执行 sql 的对象
Statement createStatement()
PreparedStatement prepareStatement(String sql)
管理事务:
- 开启事务:
void setAutoCommit(boolean autoCommit)
:调用该方法设置参数为 false,即开启事务 - 提交事务:
void commit()
- 回滚事务:
void rollback()
- 开启事务:
后续我们会介绍事务。
# Statement:执行 sql 的对象
查询文档,相关说明是:用于执行静态 SQL 语句并返回其生成的结果的对象(参数是给定值的)
PreparedStatement 对象则是执行预编译的 SQL 语句对象(参数是后续设置的)
执行 sql 相关方法:
boolean execute(String sql)
:可以执行任意的 sql (了解即可,较少使用),返回值是 Boolean 类型,有结果集则是 true,没有则是 falseint executeUpdate(String sql)
:执行 DML 语句(例如 insert、update、delete)、DDL 语句(例如 create,alter、drop)。不能执行查询语句 返回值:影响的行数,可以通过这个影响的行数判断 DML 语句是否执行成功 。返回值 > 0 的则执行成功,反之,则失败。执行 DDL 语句则返回 0。ResultSet executeQuery(String sql)
:执行 DQL(select)语句,返回 ResultSet 对象
之前我们仅仅使用了 executeQuery
,后续我们介绍 executeUpdate
方法。
# PreparedStatement 对象:防止 SQL 注入
PreparedStatement 继承了 Statement 接口。这个类能解决 SQL 注入问题
执行的是预编译的 SQL,SQL 中参数使用 ?
作为占位符
使用步骤:
定义 sql。注意:sql 的参数使用?作为占位符。如:
string sql = select * from user where username = ? and password = ?;
1获取执行 sql 语句的对象 PreparedStatement
pstm = Connection.prepareStatement(String sql)
1给占位符 ?赋值,用方法: setXXX(参数 1, 参数 2),例如
setDouble() ,setString()
等一系列方法- 参数 1:?的位置编号 从 1 开始
- 参数 2:?的值
- 举例:
setString(1, "PeterJXL")
执行 sql,接受返回结果,不需要传递 sql 语句
# ResultSet:结果集对象,封装查询结果
ResultSet:返回结果集对象(可以看作是一个数据库表)后,默认会有个游标(或者叫指针),游标的初始位置位于第一行之前;
第一次调用 next()
方法将会把第一行设置为当前行。
第二次调用 next()
方法游标移动到第二行,以此类推。
常用方法
boolean next()
: 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回 false,如果不是则返回 true。getXXX(参数)
: 获取某一列的数据XXX
代表数据类型 如:int getInt(),String getString(),double getDouble()
等等参数
:- 如果是 int 类型:代表列的编号, 从 1 开始 如:getString(1)
- 如果是 String 类型:代表列名称。 如: getDouble("balance")
使用步骤总结:
- 使用
next()
方法使游标向下移动一行 - 通过
next()
方法的返回值判断是否还有数据,如果有则返回 true,如果没有则返回 false(可以理解为游标到了最后一行了,再往下就没数据了) - 通过
getXXX(参数)
方法获取某一列的数据
如果 ResultSet 的游标已经到了最后一行,再调用一次 next()
,会抛出异常:
java.sql.SQLException: After end of result set
at com.mysql.jdbc.SQLError.createsQLException(SQLError.java: 959)
at com.mysql.jdbc.SOLError.createSQLException(SOLError.java: 898)
com.mysql.jdbc.SQLError.createSQLException(SOLError.java: 887)
com.mysql.jdbc.SoLError.createSQLException(SOLError.java: 862)
com.mysql.jdbc.ResultsetImpl.checkRowPos(ResultSetImpl.java: 790)
com.mysql.jdbc.ResultSetImpl.getInt(ResultSetImpl.iava: 2469)
.....
2
3
4
5
6
7
8
所以我们遍历 ResultSet 时通常是使用循环,每次判断是否到达了最后一行:
while(rs.next()){ //循环判断游标是否是最后一行末尾(判断是否有数据)
//获取数据并处理
int id = rs.getInt(1);
// .....省略其他代码
}
2
3
4
5