JDBC 编程详解
- 一. 概念
- 二. JDBC 工作原理
- 三. JDBC 使用
- 1. 创建项目
- 2. 引入依赖
- 3. 编写代码
- (1). 创建数据源
- (2). 建立数据库连接
- (3). 创建 SQL
- (4). 执行 SQL
- (5). 遍历结果集
- (6). 释放连接
- 4. 完整的代码
- 5. 如何不把 sql 写死 ?
- 6. 获取连接失败的情况
- 四. JDBC常用接口和类
- 1. 数据库连接 Connection
- 2. Statement 对象
- 3. PreparedStatement 的预编译
一. 概念
JDBC: 即Java Database Connectivity,java数据库连接。是一种用于执行SQL语句的Java API,
它是Java中的数据库连接规范。这个API由 java.sql.*,javax.sql.* 包中的一些类和接口组成,
它为Java开发人员操作数据库提供了一个标准的API,可以为多种关系数据库提供统一访问。
产生的原因:
MySQL、Oracle、SQL server 这些数据库都提供自己的 API 来支持程序员实现自己的客户端来完成一些增删改查功能, 没有业界统一的标准,Java 当然不乐意了, 因为 Java 诞生就是为了跨平台的。
所以 Java 就搞出了 JDBC,约定一组 API 为 JDBC,里面包含一些类和方法, 通过这些类和方法实现数据库的基本操作,由各厂商提供各自的 “数据库驱动包” 来和 JDBC 的 API 对接。
所以,只要掌握这一套 JDBC API 就可以操作各种数据库了。
二. JDBC 工作原理
JDBC 为多种关系数据库提供了统一访问方式,作为特定厂商数据库访问API的一种高级抽象,它主要包含一些通用的接口类。
JDBC 访问数据库层次结构:
JDBC 优势:
- Java 语言访问数据库操作完全面向抽象接口编程
- 开发数据库应用不用限定在特定数据库厂商的 API
- 程序的可移植性大大增强
三. JDBC 使用
注意:
JDBC 只支持关系型数据库, 不支持非关系型数据库。
1. 创建项目
创建一个项目即可。
2. 引入依赖
- 准备数据库驱动包,并添加到项目的依赖中:
在项目中创建文件夹 lib,并将依赖包(MySQL 使用的是哪个系列就用哪个系列的驱动)如 mysql-connector-java-5.1.47.jar (下载地址:Maven 中央仓库) 复制到 lib 中。
右键上面的目录,选择 add as library 选项,这样才能把 jar 包导入到项目中,项目才能读取到里面的 .class 文件。
3. 编写代码
(1). 创建数据源
// 创建数据源
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("你的用户名");
((MysqlDataSource)dataSource).setPassword("你的数据库密码");
注意:
-
DataSource 对象描述了数据库对象在哪。
-
MySQL 数据连接的 URL 参数格式如下:
jdbc:mysql://服务器地址:端口/数据库名?参数名=参数值&参数名=参数值&…
服务器地址:我们写 的 127.0.0.1 是因为我们连接的是 本机的数据库, 所以用的是环回 IP 127.0.0.1,如果连接的是服务器上的数据库就得写对应的 IP 地址。
端口:MySQL 的默认端口号为 3306
数据库名:代码中的 URL 中的 test 要换为自己的数据库的名字。 -
setUser、setPassword 都是自己的用户名(MySQL 默认用户为 root )和密码。
-
为什么 使用向上转型,DataSource 引用 MysqlDataSource 对象,然后设置参数时 先强转(向下转型)回来,为什么不直接使用 MysqlDataSource 对象,这样不就不用来回转了嘛 ?
为了使代码低耦合:
后面代码中若需要用到 DataSource 类型,使用的相关参数也是 dataSource , 未来如果 不适用 MySQL 做数据库了, 使用其他数据库了 如 PostgreSQL 了,代码几乎不用怎么改动。
但是如果直接写成 :
MysqlDataSource mysqlDataSource = new MysqlDataSource();
这样不需要来回转换, 但是代码中充斥着 MysqlDataSource,到时候一旦更换数据库,代码要改动的地方就非常多了, 基本凉凉。
(2). 建立数据库连接
// 建立连接
Connection connection = dataSource.getConnection(); // 注意处理异常
(3). 创建 SQL
JDBC 中构造的 sql 不需要带上 ; 符号
// 创建 sql
// 更新的 sql
String sql = "insert into student values ('wangwu', 90)";
PreparedStatement statement = connection.prepareStatement(sql);
// 查询的 sql
// String sql2 = "select * from student"; // 不用加上 ;
// PreparedStatement statement2 = connection.prepareStatement(sql2);
只一个 String 类型的 sql 还不行,需要把这个 String 包装成一个语句对象 PreparedStatement 。
(4). 执行 SQL
// 执行 sql
int ret = statement.executeUpdate();
// 执行查询的 sql
// ResultSet resultSet = statement2.executeQuery();
执行 增删改 的 sql 使用 executeUpdate 方法, 返回值是受影响的行数。
执行 查询 的 sql 使用 executeQuery 方法,并使用 ResultSet 接收结果。
(5). 遍历结果集
// 执行 查询的 sql 时需要遍历结果集
while (resultSet.next()) {
int age = resultSet.getInt("age");
String name = resultSet.getString("name");
System.out.println(" 姓名: " + name + " 年龄: " + age);
}
可以根据 列名 获取对应的值, 也可以根据列的下标(从 1 开始)获取,但是不推荐。
(6). 释放连接
// resultSet.close(); // 如果查询的话需要释放
statement.close();
connection.close();
// 也要注意处理异常
先释放 statement 再 释放 connection
类似于关冰箱,先把抽屉关了, 再把冰箱门关了。
4. 完整的代码
以查询为例:
public static void main(String[] args) throws SQLException {
// 1. 创建数据源
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("123456");
// 2. 建立数据库连接
Connection connection = dataSource.getConnection(); // 注意处理异常
// 3. 创建 sql
String sql = "select * from student"; // 不用加上 ;
PreparedStatement statement = connection.prepareStatement(sql);
// 4. 执行查询的 sql
ResultSet resultSet = statement.executeQuery();
// 5. 遍历结果集 (查询的话需要)
while (resultSet.next()) {
int age = resultSet.getInt("age");
String name = resultSet.getString("name");
System.out.println(" 姓名: " + name + " 年龄: " + age);
}
// 6. 释放连接
resultSet.close(); // 如果查询的话需要释放
statement.close();
connection.close();
}
使用步骤总结:
六个步骤:
- 创建数据源: DataSourse
- 建立数据库连接: Connection
- 创建 sql: PreparedStatement
- 执行 sql: executeUpdate/executeQuery
- 遍历结果集(如果是 查询的话): ResultSet
- 释放连接 ResultSet / PreparedStatement / Connection close()
注意:
整个写代码的时候注意处理异常,要么抛给上层调用者,要么自己处理掉。
5. 如何不把 sql 写死 ?
使用 PreparedStatement 并使用占位符
String name = "zhouba";
int age = 88;
String sql = "insert into student values(?,?)"; // 不用加上 ;
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, name);
statement.setInt(2, age);
statement.executeUpdate();
使用 ? 先占一个位置,再使用 PreparedStatement 的 setXXX 方法进行替换,注意类型要一致。
注意该方法的第一个参数 是 ? 的位置, 下标从 1 开始,第二个参数是要填上去的值。
如果想一次插入多条数据, 就使用多个 ?, 对应替换上去就行了。
String sql = "insert into student values(?,?),(?,?),(?,?),(?,?)"; // 不用加上 ;
不要使用字符串拼接实现动态 sql, 因为会引入 sql 注入问题
比如: 可能我们如果使用 字符串拼接的话, 会像下面这么写
Scanner scanner = new Scanner(System.in);
String name = scanner.nextLine();
String password = scanner.nextLine();
String sql = "select * from user where name=" + name + " and password=" + password;
PreparedStatement statement = connection.prepareStatement(sql);
正常情况下没有什么问题, 但是, 有一个巨大的漏洞,
当用户输入:
'zhangsan'
'' or 1=1
(或者输入一些其他的恶意代码)
拼接成的 sql 为
select * from user where name='zhangsan' and password='' or 1=1
最后出现了一个 or 1=1
这在什么条件下都会成立的,所以说这个 sql 完全能执行成功,
在恶意用户不用知道 正确的用户名和密码的情况下,能查询数据库中的所有内容, 这是相当可怕的,尤其在一些银行等数据库内容非常机密的情况下。
所以, 我们不能使用 字符串拼接来实现动态的 sql ,
而 使用 PreparedStatement 的占位符则不会出现这种情况。
6. 获取连接失败的情况
- 数据库地址不对
- 端口号不对
- 数据库名不对
- 用户名不对
- 密码不对
- 其他情况
四. JDBC常用接口和类
1. 数据库连接 Connection
Connection 接口实现类由数据库提供,获取 Connection 对象通常有两种方式:
- 一种是通过 DriverManager(驱动管理类)的静态方法获取:
// 加载JDBC驱动程序:反射,这样调用初始化com.mysql.jdbc.Driver类,即将该类加载到JVM方法区,并执行该类的静态方法块、静态属性。
Class.forName("com.mysql.jdbc.Driver");
// 创建数据库连接
Connection connection = DriverManager.getConnection(url,user,password);
- 一种是通过 DataSource(数据源)对象获取。实际应用中会使用 DataSource 对象。
DataSource ds = new MysqlDataSource();
((MysqlDataSource) ds).setUrl("jdbc:mysql://localhost:3306/test");
((MysqlDataSource) ds).setUser("root");
((MysqlDataSource) ds).setPassword("123456");
Connection connection = ds.getConnection();
以上两种方式的区别是:
- DriverManager 类来获取的 Connection 连接,是无法重复利用的,每次使用完以后释放资源时,通过 connection.close() 都是关闭物理连接。
- DataSource 提供连接池的支持。连接池在初始化时将创建一定数量的数据库连接,这些连接是可以复用的,每次使用完数据库连接,释放资源调用 connection.close() 都是将 Conncetion 连接对象重新初始化然后回收,不用关闭物理连接。
非常不建议使用 DriverManager :
- 使用了反射,非常影响代码的可读性,也不利于 IDEA 对代码解析校验。
- DataSourse 内置连接池,在频繁创建/断开连接时,DataSourse 比 DriverManager 的方式更高效。
2. Statement 对象
Statement 对象主要是将SQL语句发送到数据库中。JDBC API 中主要提供了三种 Statement 对象。
实际开发中最常用的是 PreparedStatement 对象:
- 可以参数化 SQL 查询。
- 占位符为 ?,下标从 1 开始,占位符不能使用多值。
- 使用 SQL 预编译。
- 可以阻止常见的 SQL 注入攻击。
- 性能比 Statement 高。
PreparedStatement 与 Statement 的区别:
- Statement 是 PreparedStatement 的父接口。
- 语法不同:PreparedStatement 使用预编译,使用动态 sql 时,相同的 sql 语句,除了参数不同,只需发送一次 sql,后面的只发送了参数,共用一个 sql 语句。(同构)
而 Statement 只能使用静态 sql。(异构) - 效率不同:PreparedStatement 使用了缓冲区,效率比 Statement 高。
- 安全性不同: PreparedStatement 可有效防止 sql 注入,Statement 不能。
3. PreparedStatement 的预编译
- 只有数据库服务器支持预编译,JDBC 驱动才能使用数据库预编译功能,预编译在比较新的 JDBC 驱动中默认是关闭的,需要配置才能打开。
- PreparedStatement 预编译是数据库进行的,编译后 函数的 key 缓存在 PreparedStatement 中,函数本身 缓存在 数据库服务器中。
- 预编译前检查 sql 语法是否正确。
- PreparedStatement 需要使用带 占位符的 sql, 如果使用静态 sql, 也还是会编译多次。Statement 本身就只支持静态的 sql。
- 数据库服务器对 sql 模板进行编译,且 PreparedStatement 存储了函数 的 key, 所以 PreparedStatement 做的就是把参数转义后直接传参数到数据库服务器中,然后让函数执行,所以 PreparedStatement 能防止 sql 注入。
- PreparedStatement 存储的 key 和 数据库存储的函数都建立在数据库连接的基础上,连接断开,key 和函数都清空。
- 各个连接之间的预编译都是相互独立的。
- Statement 不缓存函数的key, 数据库也不缓存函数, 所以多次执行相同一条 sql 时,还是会先检查 sql 的语法,再编译执行。
好啦,以上就是对 JDBC 编程的讲解,希望能帮到你 !
评论区欢迎指正 !