1 基本介绍
1.1 什么是 JDBC?
在Java中,我们势必会使用到访问数据库的操作,而现有的数据库种类又十分的多,比较常用的如MySQL、Oracle、SQL Server等等,不同的数据库设计时本身就不一样,因此在访问上,也会有所不同,为了便于开发者使用,Java提供了一套标准的、统一的访问接口,JDBC,全称 Java DataBase Connectivity,即Java数据库连接。
1.2 基本概念
- 注册驱动:其实就是使用
Class.forName()
方法,使用反射去获取 jar 包内的东西。 - 连接对象 connection:使用这个对象才能进行后续的数据库操作,connection 的主要功能有:
- 获取执行 SQL 语句的对象:Statement 和 PrepareStatement
- 管理事务,三个基本的方法,提交,回滚,详情请看
- Statement 对象,执行 SQL 语句的对象,在 JDBC 中, 想要执行 SQL 语句,使用 Statement 是一种方式;
- prepareStatement 对象:执行 SQL 语句的另一种方式,其有两种功能,其一就是执行 SQL 语句,另一个就是预编译 SQL;
- ResultSet 对象:结果集对象,在查询语句时,会返回查询的结果,JDBC 会将查询到的数据放入这个对象中,可以遍历该对象获取查询的结果;
2 代码实践
会用到一些数据库的信息,但是仅仅是为了测试,所以数据库的特别简单,脚本如下
/*
为学习JDBC准备的数据库环境的脚本:
1.db1 学习的数据库
1.1 account 一个测试数据表,无特殊说明
*/
# 创建数据库
CREATE DATABASE if not exists db1;
USE db1;
SELECT DATABASE();
CREATE TABLE account
(
`id` INTEGER primary key auto_increment comment '主键',
`name` varchar(20) comment '账户名',
`money` INTEGER comment '账户余额'
) comment '银行账户表';
-- 插入数据
INSERT INTO account (name, money) values ('张三', 100), ('李四', 50);
2.1 JDBC 的基本使用
2.2.1 可运行代码示例
package cn.edu.njust.jdbcdemo;
import java.sql.*;
public class TestJDBCDemo01 {
// MySQL 5.x 版本的驱动加载
// private final static String DRIVER = "com.mysql.jdbc.Driver";
// MySQL8.0 版本的驱动加载
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
/*
* 以下是数据库连接的信息,包括连接地址,用户名和密码
* */
private static final String URL = "jdbc:mysql://localhost:3306/db1";
private static final String USERNAME = "root";
private static final String PASSWORD = "root";
public static void main(String[] args) throws Exception {
/*
* 注册驱动
* 这一步使用反射来注册驱动
* 而且在MySQL5.0以后,这一句代码不用自己书写,在jar包中的META-INF/services/java.sql.Driver
* Java会自动扫描这个文件,注册驱动
* */
Class.forName(DRIVER);
/*
* 获取一个数据库连接
* connection对象有两个功能
* 1. 获取执行SQL语句的对象
* 2. 对JDBC事务进行管理
* */
Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 自定义的SQL语句
String querySql = "SELECT * FROM account";
String updateSql = "UPDATE account set money = 0 WHERE name = '张三'";
/*
* 获取执行SQL的statement对象
* 该方式执行SQL语句时,不会采用预编译,在执行大量的SQL语句时效率较低
* */
Statement stmt = con.createStatement();
/*
* 第二种执行SQL语句的方式
* 这种方式会采用预编译的方式,提高SQL执行效率,同时还能预防SQL注入
* */
PreparedStatement ps = con.prepareStatement(updateSql);
try {
// 关闭MySQL的自动提交事务
con.setAutoCommit(false);
// 执行SQL语句
ResultSet res = stmt.executeQuery(querySql);
// update方法返回的是收到影响的行数
int update = ps.executeUpdate();
System.out.println(res);
System.out.println(update);
// 提交MySQL事务
con.commit();
} catch (SQLException e) {
// 如果出现异常,回滚事务
con.rollback();
throw new RuntimeException(e);
}
// 关闭资源
stmt.close();
con.close();
}
}
2.1.2 补充说明
在 JDBC 中,使用 Class.forName()
方法,这个方法是之前在 Java 基础中常用的反射,那么这里自己就有一个疑问?为什么没有使用一个对象去接收调用方法后的返回值?JDBC 是怎么获得这个返回对象的呢?
在 MySQL5.0 以后,也不需要调用这个方法了,查看引入的 jar 包,发现其下有一个目录文件,当运行项目的时候会扫描这个文件并自动加载这句话。如下:
- 加载的类
- 自动加载的配置文件
2.2 ResultSet 的基本使用
2.2.1 代码示例
package cn.edu.njust.jdbcdemo;
import java.sql.*;
public class TestJDBCDemo02 {
// MySQL 5.x 版本的驱动加载
// private final static String DRIVER = "com.mysql.jdbc.Driver";
// MySQL8.0 版本的驱动加载
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
/*
* 以下是数据库连接的信息,包括连接地址,用户名和密码
* */
private static final String URL = "jdbc:mysql://localhost:3306/db1";
private static final String USERNAME = "root";
private static final String PASSWORD = "root";
public static void main(String[] args) throws Exception {
/*
* 注册驱动
* 这一步使用反射来注册驱动
* 而且在MySQL5.0以后,这一句代码不用自己书写,在jar包中的META-INF/services/java.sql.Driver
* Java会自动扫描这个文件,注册驱动
* */
Class.forName(DRIVER);
/*
* 获取一个数据库连接
* connection对象有两个功能
* 1. 获取执行SQL语句的对象
* 2. 对JDBC事务进行管理
* */
Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 自定义的SQL语句
String querySql = "SELECT * FROM account";
/*
* 获取执行SQL的statement对象
* 该方式执行SQL语句时,不会采用预编译,在执行大量的SQL语句时效率较低
* */
Statement stmt = con.createStatement();
// 查询的结果集
ResultSet resultSet1 = stmt.executeQuery(querySql);
/*
* 遍历结果集
* 第一种方式,需要知道某列的属性和位于第几列
* */
while (resultSet1.next()) {
// 获取第一列
int aid = resultSet1.getInt(1);
// 获取第二列
String aname = resultSet1.getString(2);
// 获取第三列
int amoney = resultSet1.getInt(3);
System.out.println(aid + ' ' + aname + ' ' + amoney);
}
System.out.println("====================");
ResultSet resultSet2 = stmt.executeQuery(querySql);
/*
* 另一种获取结果的方式
* */
while (resultSet2.next()) {
// 获取id
int id = resultSet2.getInt("id");
// 获取name
String name = resultSet2.getString("name");
// 获取money
int money = resultSet2.getInt("money");
System.out.println(id + ' ' + name + ' ' + money);
}
resultSet1.close();
resultSet2.close();
// 关闭资源
stmt.close();
con.close();
}
}
2.2.2 问题补充
在测试示例代码的过程中,发现一个问题,使用同一个 stmt 执行方法获得 ResultSet 结果集后,需要不能连续、交替的访问,具体是:
- 起初像这样书写代码
Statement stmt = con.createStatement();
// 查询的结果集
ResultSet resultSet1 = stmt.executeQuery(querySql);
ResultSet resultSet2 = stmt.executeQuery(querySql);
/*
* 遍历结果集
* 第一种方式,需要知道某列的属性和位于第几列
* */
while (resultSet1.next()) {
// 获取第一列
int aid = resultSet1.getInt(1);
// 获取第二列
String aname = resultSet1.getString(2);
// 获取第三列
int amoney = resultSet1.getInt(3);
System.out.println(aid + ' ' + aname + ' ' + amoney);
}
/*
* 另一种获取结果的方式
* */
while (resultSet2.next()) {
// 获取id
int id = resultSet2.getInt("id");
// 获取name
String name = resultSet2.getString("name");
// 获取money
int money = resultSet2.getInt("money");
System.out.println(id + ' ' + name + ' ' + money);
}
即在前面使用同一个 stmt 执行方法获得 ResultSet 结果集后,先访问 resultSet1,紧接着访问 resultSet2,看似代码没有问题,但是运行会报错,如下:
查找原因和处理如下:
错误:Operation not allowed after ResultSet closed_关闭的resultset-CSDN博客
修正后的代码书写顺序如:代码示例,先获取一个 ResultSet 访问完后,再获取另一个 resultSet。
2.3 prepareStatement 的基本使用
2.3.1 代码示例
package cn.edu.njust.jdbcdemo;
import java.sql.*;
public class TestJDBCDemo03 {
// MySQL 5.x 版本的驱动加载
// private final static String DRIVER = "com.mysql.jdbc.Driver";
// MySQL8.0 版本的驱动加载
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
/*
* 以下是数据库连接的信息,包括连接地址,用户名和密码
* 开启预编译功能
* */
private static final String URL = "jdbc:mysql://localhost:3306/db1?useServerPrepStmts=true";
private static final String USERNAME = "root";
private static final String PASSWORD = "root";
public static void main(String[] args) throws Exception {
// 自定义的SQL语句
String querySql = "SELECT * FROM account WHERE name = ?";
/*
* 注册驱动
* 这一步使用反射来注册驱动
* 而且在MySQL5.0以后,这一句代码不用自己书写,在jar包中的META-INF/services/java.sql.Driver
* Java会自动扫描这个文件,注册驱动
* */
Class.forName(DRIVER);
/*
* 获取一个数据库连接
* connection对象有两个功能
* 1. 获取执行SQL语句的对象
* 2. 对JDBC事务进行管理
* */
Connection con = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 获取 PreparedStatement 对象,用于执行SQL语句
PreparedStatement ps = con.prepareStatement(querySql);
// 传递参数
ps.setString(1, "张三");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int money = rs.getInt("money");
System.out.println(id + " " + name + " " + money);
}
rs.close();
ps.close();
con.close();
}
}
2.3.2 补充说明
想要开启预编译的功能,必须在 URL 连接数据库的时候指定属性useServerPrepStmts=true
,如果没有设置这个参数,那么使用PreparedStatement 也不会预编译;
具体查看预编译,需要选择配置 MySQL 的日志,查看日志文件可以观察预编译效果。
3 常用方法汇总
3.1 基本方法
** 方法原型** | 说明 |
---|---|
public static Connection getConnection(String url, String user, String password) | 获取 connection 对象 |
Statement createStatement() | 获取执行 SQL 语句的普通对象 |
PreparedStatement prepareStatement(String sql) | 获取另一个执行 SQL 语句的对象 |
ResultSet executeQuery(String sql) | 执行查询语句 |
int executeUpdate() | 执行更新语句,返回受影响的行数 |
3.2 connection 的事务管理
** 方法原型 ** | ** 说明 ** |
---|---|
void setAutoCommit(boolean autoCommit) | 设置是否自动提交事务 |
void commit() | 提交事务 |
void rollback() | 回滚事务 |
3.3 ResultSet
** 方法原型** | 说明 |
---|---|
boolean next() | 检查下一行是否有数据 |
int getInt(String columnLabel) | 根据列名获取某一列的值,且该字段在数据库中是 int 类型 |
String getString(String columnLabel) | 根据列名获取某一列的值,且该字段在数据库中是字符串 |
int getInt(int columnIndex) | 获取某一列的值,且该字段在数据库中是 int 类型 |
说明:
在 ResultSet 的方法中,获取返回值的有两种方式:
- 一种是在参数里面传递字符串,该字符串匹配数据库数据表中相应的字段名,较为直观;
- 在参数中传递入“第几列”,列数重 1 开始,这种方式不太直观,不推荐使用。
所有的方法都是形如:getXxx() 类型的,其中 Xxx 表示某种数据类型,与该字段在数据库数据表中定义的类型相匹配。
具体的使用可以看ResultSet代码示例