JDBC 详解

JDBC 概述

  • **JDBC(Java DataBase Connectivity,java 数据库连接)**是一种用于执行 SQL 语句的 Java API。JDBC 是 Java 访问数据库的标准规范,可以为不同的关系型数据库提供统一访问,它由一组用 Java 语言编写的接口和类组成。
  • JDBC 需要连接驱动,驱动是两个设备要进行通信,需满足一定通信数据格式,数据格式由设备提供商规定。设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信。

JDBC 原理

Java提供访问数据库规范称为JDBC,而生产厂商提供规范的实现类称为驱动。

JDBC 是接口,驱动是接口的实现,没有驱动将无法完成数据库连接,从而不能操作数据库!每个数据库厂商都需要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生成厂商提供。

JDBC 入门案例

准备数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建数据库
create database mydb;

# 使用数据库
use mydb;

# 创建表
create table account(
id int primary key auto_increment,
name varchar(20),
money float
);

# 初始化数据
insert into account(name,money)
values
('aaa',1000),
('bbb',2000),
('ccc',3000);

添加依赖

在 Maven 项目中添加 mysql 连接依赖

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
</dependencies>

实现

注意:mysql 8 与之前版本的操作有所不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void main(String[] args)
throws ClassNotFoundException, SQLException {
// 1.注册驱动,使用反射
// Class.forName("com.mysql.jdbc.Driver");
// mysql 8
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接
// String url = "jdbc://mysql://localhost:3306/mydb";
// mysql 8
String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
Connection conn = DriverManager.getConnection(url,"root", "1234");
// 3.获取语句执行对象
Statement statement = conn.createStatement();
// 4.执行 SQL 语句
ResultSet rs = statement.executeQuery("select * from account");
// 5.处理结果集
while (rs.next()) {
// 获得一行数据
int id = rs.getInt("id");
String name = rs.getString("name");
float money = rs.getFloat("money")
System.out.println(id + "." + name + " has $" + money);
}
// 6.释放资源
rs.close();
statement.close();
conn.close();
}

API 详解

注册驱动

  • 代码:Class.forName("com.mysql.cj.jdbc.Driver");
  • JDBC 规范定义驱动接口:java.sql.Driver
  • mysql 8 驱动包提供了实现类:com.mysql.cj.jdbc.Driver
  • 使用 Class.forName() 加载一个使用字符串描述的驱动类。

如果使用 Class.forName() 将类加载到内存,该类的静态代码将自动执行。通过查询 com.mysql.jdbc.Driver 源码,我们发现 Driver 类“自动”将自己进行注册。

1
2
3
4
5
6
7
8
9
10
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
……
}

另一种方法:DriverManager 工具类提供注册驱动的方法 registerDriver(),方法的形参是 java.sql.Driver,因此可以通过语句 DriverManager.registerDriver(new com.mysql.jdbc.Driver());进行注册;但不推荐使用,因为存在以下不足:

  1. 程序耦合;
  2. 硬编码,后期不易于程序扩展和维护;
  3. 驱动被注册两次。

获取连接

  • 代码:Connection conn = DriverManager.getConnection(“jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC”, ”root”, ”1234”);
  • 获取连接需要方法 DriverManager.getConnection(url, username, password),三个参数分别表示:
    • url:需要连接数据库的位置(地址)
    • user:用户名
    • password:密码

获取执行对象

代码:Statement statement = conn.createStatement();

执行 SQL 语句

常用方法:

  • int executeUpdate(String sql); - 执行 insert, update, delete 语句
  • ResultSet executeQuery(String sql); - 执行 select 语句
  • boolean execute(String sql); - 执行 select 语句时返回 true,执行其他的语句返回 false

处理结果集

ResultSet 实际上就是一张二维的表格,我们可以调用其 boolean next(); 方法指向下一行记录;再使用 T getT(int colIndex / String colLabel) 方法(列下标从1开始)来获取指定列的数据;

1
2
rs.next(); // 指向下一行
rs.getInt(1); // 获取该行第一列的整型数据

常用方法:

  • Object getObject(int colIndex / String colLabel) - 获得任意对象
  • String getString(int colIndex / String colLabel) - 获得字符串
  • int getInt(int colIndex / String colLabel) - 获得整型
  • double getDouble(int colIndex / String colLabel) - 获得双精度浮点型

释放资源

与 IO 流一样,使用后的资源需要关闭!关闭的顺序是 先得到的后关闭,后得到的先关闭

JDBC 工具类

对数据库进行”增删改查“都需要先”获取数据库连接“,可以封装一个工具类 JdbcUtils,提供获取连接对象的方法,从而实现代码复用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class JdbcUtils {
private static String driver = "com.mysql.cj.jdbc.Driver";
private static String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
private static String username = "root";
private static String password = "1234";

static {
try {
// 注册驱动
Class.forName(driver);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* 获取连接
* @return 数据库的连接
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
}

JDBC 增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String[] args) throws SQLException {
// 使用JDBC 工具类获取数据库连接
Connection conn = JdbcUtils.getConnection();
// 获取语句执行对象
Statement statement = conn.createStatement();
// 增
int r = statement.executeUpdate("insert into account(name,money) values('ddd',500)");
System.out.println("插入操作返回值:" + r);
// 删
r = statement.executeUpdate("delete from account where id = 1");
System.out.println("删除操作返回值:" + r);
// 改
r = statement.executeUpdate("update account set name='cat' where id = 3");
System.out.println("修改操作返回值:" + r);
// 查
ResultSet rs = statement.executeQuery("select * from account where id = 3");
if(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
float money = rs.getFloat("money");
System.out.println(id + "." + name + " has $" + money);
} else{
System.out.println("没有数据");
}
// 释放资源
rs.close();
statement.close();
conn.close();
}

预处理对象

SQL注入问题:用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义。

例如登录案例 SQL 语句如下:

1
select * from 用户表 where name=账号 and password=密码;

此时,当用户输入正确的账号与密码后,查询到了信息则让用户登录;但是当用户输入的账号为 xxx,密码为 'xxx' or 'a'='a'时,则真正执行的 SQL 语句变为:

1
select * from 用户表 where name='xxx' and password='xxx' or 'a'='a';

此时,上述查询语句 where 后条件始终为真,会查询出数据库中全部记录的,用户就直接登录成功了,这便是SQL注入问题。

如何解决?使用预处理对象 PreparedStatement

PreparedStatement 处理的每条 sql 语句中所有的实际参数都必须使用占位符 ? 来替换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws SQLException {
// 使用JDBC 工具类获取数据库连接
Connection conn = JdbcUtils.getConnection();
// 含占位符的 sql 语句
String sql = "update account set name=? where id=?";
// 获取预处理对象
PreparedStatement psmt = conn.prepareStatement(sql);
// 设置实际参数,序号从1开始
psmt.setString(1, "dog");
psmt.setInt(2, 4);
// 执行
int r = psmt.executeUpdate();
System.out.println("修改操作返回值:" + r);
// 释放资源
psmt.close();
conn.close();
}
0%