JDBC——概述与JDBC的使用

引言

一直希望深入学习一下数据库持久化技术,接触过Hibernate、Mybatis,也使用过Spring事务管理来控制回滚操作,但是越发觉得底层知识有一定的知识盲区和空洞。

很多ORM框架都是基于JDBC规范来进行构建的,因此,学习JDBC的基础知识势在必行。虽然不建议在实际开发中使用 JDBC API,但了解其技术背景和使用过程无疑会更好的理解构建于其上的高级框架。

本篇博客总结自尚硅谷宋红康老师的视频教程,旨在记录和总结JDBC API的使用步骤和常见问题,方便未来面试和深入理解其他框架。

一、JDBC概述

1.1 JDBC 介绍

JDBC,Java Database Connectivity,Java数据库连接技术。这是JDK原生的独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API)。

这套API包含在 java.sql. 和 javax.sql 包下。使用这套API 可以以一种标准的方法访问数据库资源。

JDBC产生的原因是由于各个数据库厂商,如MySQL、Oracle、SQL Server、DB2(IBM)等,都有属于自己数据库特有的连接和访问方式,从连接驱动到增删改查,都存在各种差异。

JDBC的目标是使Java程序员无需对特定的数据库系统的特点有过多了解,也能够快速完成数据库访问等开发工作。

1.2 JDBC体系结构

JDBC接口包括两个层次:

  • 面向应用的API:Java API,抽象接口,供应用开发者使用,包括连接数据库、执行SQL语句、获得结果等。
  • 面向数据库的API:Java Driver API,供数据库服务商实现特定于自家数据库的驱动程序。MySQL的驱动就是我们经常需要引入的mysql-connector-java.jar 。

1.3 JDBC的编程步骤

jdbc编程步骤有以下几步固定操作:

1、添加数据库驱动依赖,例如:MySQL的 mysql-connector-java.jar,这是一种纯Java实现的驱动程序,除此之外,微软的SQL Server,需要JDBC-ODBC桥方式。

2、加载并注册驱动程序

3、创建 Connection 对象

4、创建 Statement 对象

5、执行 SQL 语句

6、若为查询操作,需要额外处理 ResultSet 结果集

7、关闭 Statement 对象

8、关闭 Connection 对象

补充:ODBC(Open Database Connectivity 开放式数据库连接),是微软在 Windows 平台下推出的。

二、JDBC编程实践

在使用 JDBC 进行编程前,有一些不可或缺的要素:

1、Driver 接口实现:是JDBC API中对数据库驱动程序的接口定义,不同的数据库厂商会自定义实现,但都需要符合Driver接口标准。一般使用MySQL,就需要引入mysql-connector-java驱动包。

包中提供的 com.mysql.jdbc.Driver(高版本已经废弃,改为com.mysql.cj.jdbc.Driver) 就是java.sql.Driver接口的实现类。

2、数据库连接信息:URL、用户名、密码等。

JDBC URL的标准由三部分组成,各部分用冒号分隔,例如:

jdbc:mysql://localhost:3306/test?serverTimezone=UTC

jdbc是主协议名,mysql是子协议,JDBC URL的主协议始终是 jdbc。

以下代码为 jdbc 实现入库操作的最终版本,后面的章节将会进一步探讨可以优化或需要注意的点,但是以下代码作为学习范例,已经足够展现JDBC 编程的绝大部分内容。

import java.sql.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;public class PreparedStatementUpdTest {public static void main(String[] args) {jdbcTest();}public static void jdbcTest() {// 创建驱动Connection connection = null;PreparedStatement ps = null;try {Class.forName("com.mysql.cj.jdbc.Driver");// 声明连接信息String url = "jdbc:mysql://localhost:3306/learn_mysql?serverTimezone=UTC";String username = "root";String password = "123456";// 创建连接connection = DriverManager.getConnection(url, username, password);String sql = "INSERT INTO user(name, birth_day) VALUES(?, ?)";// SQL预编译ps = connection.prepareStatement(sql);ps.setObject(1, "Lisa");DateFormat format = new SimpleDateFormat("yyyy-MM-dd");java.util.Date birthday = format.parse("2021-02-12");// util.Date和sql.Date的共同点是毫秒数一样ps.setObject(2, new Date(birthday.getTime()));// 执行SQL语句,executeUpdate()方法可以返回 int 受影响行数ps.execute();} catch (Exception e) {e.printStackTrace();} finally {// 关闭资源try {if (ps != null)ps.close();} catch (SQLException e) {e.printStackTrace();}try {if (connection != null)connection.close();} catch (SQLException e) {e.printStackTrace();}}}
}

三、JDBC编程实践的优化与思考

3.1 创建驱动实例

在程序编写之初,我们已经通过依赖管理,将mysql-connector-java.jar 包引入类路径下,那么 JDBC 的 Driver 接口就有了针对于MySQL的驱动器实现——com.mysql.cj.jdbc.Driver。

实际上,我们可以通过最原始的方式 new 一个出来

// 直接 new 创建驱动实例,不要这么做!
Driver driver = new com.mysql.cj.jdbc.Driver();
// 声明连接信息(略)
// 从 Driver 中获取 Connection
Connection connection = driver.connect(连接信息);

但是通过 Class.forName(..) 获得驱动对象的好处就是可以将类名作为配置放到程序外,方便替换其他的数据库驱动,有更好的移植性:

// 通过反射获取驱动,有更强的可移植性
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
// 声明连接信息(略)
// 从 Driver 中获取 Connection
Connection connection = driver.connect(连接信息);

3.2 DriverManager与Connection获取方式的

Driver 接口有获取Connection的方法,这是最初获取Connection的方式。

Connection connection = driver.connect(连接信息);

JDBC API提供了一个名为 DriverManager 的基础服务类,可以管理 Driver 驱动程序,并创建 Connection 对象,于是有了通过 DriverManager 获取 Connection的方式,但前提是需要将驱动对象注册到 DriverManager 中

// 创建驱动
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
// 注册驱动
DriverManager.registerDriver(driver);
// 连接信息(略)
// 从驱动管理器中获取连接
Connection connection = DriverManager.getConnection(url, username, password);

其实,“注册驱动”的操作并不需要应用开发者来完成,在 Driver 类加载时,就会自动注册,原因就是以下静态代码块:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}}
}

因此,在 Class.forName(..) 完成后,mysql Driver 类已经成功加载,即已经完成了驱动注册步骤,因此我们可以更加简化我们获取Connection的代码:

Class.forName("com.mysql.cj.jdbc.Driver");
// 声明数据库连接信息(略)
Connection connection = DriverManager.getConnection(url, user, password);

另外,值得一提的是,Class.forName("com.mysql.cj.jdbc.Driver") 加载驱动这一步也可以省略,

这是因为当 mysql-connector-java.jar 引入类路径后,会默认自动加载 META-INF/services/java.sql.Driver中配置的驱动实现类,但是建议不要省略,因为如果迁移了其他数据库驱动,可能不会有这样的默认操作。

3.3 配置化数据库连接信息

在前面的例子中,其实已经可以轻松的获取到数据库连接:

Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/learn_mysql?serverTimezone=UTC";
String user = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url, user, password);

我们可以进一步将驱动类信息、连接信息转移到程序之外,以配置的形式存在:

然后通过 Properties 获取配置数据:

InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties props = new Properties();
props.load(is);
String url = props.getProperty("url");
String username = props.getProperty("username");
String password = props.getProperty("password");
String driverName = props.getProperty("driverName");
// 加载驱动类
Class.forName(driverName);
// 获取连接
Connection connection = DriverManager.getConnection(url, username, password);

通过系统类加载器获取配置文件数据,默认读取 src 目录下的文件,以 properties 结尾的文件可以直接被 load 进 Properties 对象中。

3.4 Statement与PreparedStatement

数据库连接被用于向数据库服务器发送命令和SQL语句,并接收数据库返回的结果。其实,一个数据库连接就是一个Socket连接。

在java.sql包中有 3 个接口分别定义了对数据库的调用方式:

  • Statement:用于执行静态SQL语句并返回它所生成结果的对象。
  • PreparedStatement:SQL语句被预编译并存储在此对象中,可以使用此对象多次高效地执行SQL语句。
  • CallableStatement:用于执行SQL存储过程。

 原始的Statement存在一定的弊端,由于只能处理静态SQL语句,因此只能将变量直接拼接到SQL中才可以使用,除此之外,更严重的问题还是SQL注入

例如下面这条拼接的SQL语句:

String sql = "SELECT user, password FROM user_table WHERE user='" + username + "' AND password='" + password + "'";

如果username和password分别是:

String username = "1' OR ";
String password = " = 1 OR '1' = '1";

那么sql 的WHERE条件就是一个恒成立的情况,这就是SQL注入:

SELECT user, password FROM user_table WHERE user='1' OR ' AND password=' = 1 OR '1' = '1'

PreparedStatement表示一个可以预编译SQL语句的对象,使用“?”占位符来明确区分 SQL语法与参数,可以有效防止SQL注入问题。

因此,Statement 已不再使用了,取而代之的是可以进行预编译SQL的PreparedStatement。 

总结

JDBC是数据库访问的公共规范,它的编程步骤主要分为几点:

1、引入对应数据库厂商的驱动包

2、加载、注册驱动(类加载的同时即完成注册)

3、从驱动管理器 DriverManager 中直接通过连接信息创建一个 Connection 对象

4、使用 Connection 对象以预编译方式创建 PreparedStatement 对象

5、填充 PreparedStatement 属性值

6、使用PreparedStatement 执行SQL 操作(查询操作需要处理结果集)

7、以资源创建的先后顺序,逆向关闭资源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558679.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JDBC——实现通用的查询

引言 上一篇jdbc的文章《JDBC——概述与JDBC的使用》介绍了JDBC的概念和背景知识&#xff0c;同时也讨论了获取数据库连接的方式&#xff0c;以及简单的实现了入库操作&#xff08;更新、删除同理&#xff09;。 本篇博客将会聚焦 PreparedStatement 的查询操作、以及 Result…

JDBC——编程式事务的实现逻辑

引言 数据库事务的概念和基础&#xff0c;总结在《MySQL 基础 ————事务与隔离级别总结》。 本篇博客通过“JDBC 纯编码”方式实现事务控制&#xff0c;完成一个 A 给 B 转账的小功能&#xff0c;在进一步熟练JDBC的编程流程的同时&#xff0c;重点关注 Java 语言如何操作…

排序算法——插入排序

一、算法思想 给定一个无序数列&#xff0c;模拟一个指针从第0位开始向后&#xff0c;始终保持当前位置左边的数列是有序的。 指针位置上的元素依次与前面的元素比较&#xff0c;当遇到小于自己的数或右边已经没有元素时&#xff0c;都停止比较&#xff0c;开始下一轮。 生活…

Linux 底层原理 —— epoll 与多路复用

引言 epoll 是 Linux 系统下高性能网络服务的必备技术&#xff0c;很多面试中高频出现的 Nginx、Redis 都使用了这一技术&#xff0c;本文总结 linux 多路复用模型的演变过程&#xff0c;看一看epoll 是如何实现高性能的。 一、相关基础知识 1.1 文件描述符 文件描述符&…

排序算法——对数器

引言 所谓“对数器”就是校验算法的工具程序&#xff0c;我们使用Math.random() 方法可以生成一个随机乱序数组&#xff0c;并通过Arrays.sort 来比较我们的算法是否正确。 代码实现 对数器主要是使用对照来校验是否正确&#xff0c;除了一些比较方法、拷贝方法&#xff0c;…

查找算法——二分法

引言 二分法&#xff0c;顾名思义&#xff0c;即一分为二的方法&#xff0c;通常用于判断在某个有序数列中是否存在某个数&#xff0c;由于其优秀的算法思想&#xff0c;时间复杂度一般都是 O(logN) &#xff0c;通常要 O(N) 的遍历方式更加优秀。 一、经典二分法查找 最常见…

异或运算的应用

一、基础知识 异或运算&#xff0c;相异为1。 异或运算是一种常用的位运算&#xff0c;在算法题中&#xff0c;对于避免额外的空间复杂度有独特的用处。 异或运算也被称为“无进位相加”&#xff0c;它具有以下特性&#xff1a; 特性1&#xff1a;0 ^ N N 特性2&#xff1a…

链表的基本操作——反转与删除

引言 链表相关的问题几乎都是coding问题&#xff0c;以下是两个简单的链表问题。 一、单链表或双链表如何反转 1.1 单链表的反转操作 给定一个 Node 结构&#xff1a; public static class Node {public int value;public Node next;public Node(int data) {this.value d…

单向队列、双端队列、栈的模型实现

引言 自己实现简单的队列、栈的逻辑结构。 队列都包含头和尾两个指针&#xff0c;简单的单向队列只能在一端&#xff08;如&#xff1a;head端&#xff09;入列&#xff0c;在另一端&#xff08;如&#xff1a;tail 端&#xff09;出列&#xff1b;双端队列可以在 head 进出&…

递归算法及其时间复杂度分析

引言 “递归” 一词是比较专业的计算机术语&#xff0c;在现实生活中&#xff0c;有一个更可爱的词——“套娃”。如果把“递归算法”叫做“套娃算法”&#xff0c;或许可以减少一些恐惧程度。 套娃是有限的&#xff0c;同样&#xff0c;递归也是有限的&#xff0c;这和我们经…

算法设计中的基础常用代码

引言 本篇博客旨在记录一些基础算法知识的常见组合用法&#xff0c;以及何时使用&#xff0c;需要注意的问题等&#xff0c;长期更新。 为什么要这样总结呢&#xff1f;难道掌握了位运算、常用算法工具API的定义还不够吗&#xff1f; 这是因为某些知识比如 &、 |、 ~、 …

Redis —— 常用命令一览

引言 参考《菜鸟教程 Redis 常用命令》&#xff0c;其中红色为极其重要&#xff0c;蓝色为重要。 一、总览 二、key相关命令 三、String 相关命令 四、Hash 相关命令 五、List 相关命令 六、Set 相关命令 七、ZSet 相关命令

Redis 实用技术——Pipeline

引言 Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。 这意味着通常情况下一个请求会遵循以下步骤&#xff1a; 客户端向服务端发送一个查询请求&#xff0c;并监听Socket返回&#xff0c;通常是以阻塞模式&#xff0c;等待服务端响应。服务端处理命令&#x…

Redis 实用技术——消息发布和订阅

引言 发布订阅模型是redis的重要功能&#xff0c;它可以像网站动态一样&#xff0c;将消息发送到多个订阅者的主页里。 一、常用命令 二、消息格式 消息是一个有三个元素的多块响应&#xff1a; 如上图&#xff0c;发布者向 mysub 频道发送了一条消息&#xff0c;redis会返回…

Redis 实用技术——事务

引言 redis的事务不像关系型数据库的事务那样完整。 “快”是redis的特征&#xff0c;在事务管理的过程中&#xff0c;使用muti命令开启事务块&#xff0c;当输入多条命令后&#xff0c;再使用exec命令执行事务块中的全部命令。 Redis事务可以保证两件事&#xff1a; 1、隔…

排序算法——归并排序的相关问题

一、小和问题 问题描述&#xff0c;给定一个数组&#xff0c;如[1, 3, 2, 6, 5]&#xff0c;计算每个数左边小于自己的所有数的和&#xff0c;并累加。例如&#xff1a; 1左边没有数 3左边有一个小于自己的数 1 2左边有一个小于自己的数 1 6左边有三个小于自己的数 1 3 2 6…

排序算法——随机快速排序

引言 随机快排是一个非常有意思的排序排序算法&#xff0c;它的算法思想用到了如递归、荷兰国旗问题等诸多元素&#xff0c;还意外的引入了随机性的概念。 以下将逐步总结三个版本的快速排序&#xff0c;由浅入深总结快速排序的经典实现过程。 荷兰国旗问题参考&#xff1a;…

Redis 基础——五大类型与数据结构

引言 Redis 区分于 memcahced 的一个重要不同就是它具有明确的类型概念&#xff0c;在Redis 的使用过程中&#xff0c;都离不开这些类型的学习&#xff0c;它不仅是 Redis 能力的基础&#xff0c;同时也是一些重要数据结构和算法思想的体现。 本博客总结了五大类型的书面重点…

比较器的使用

一、TreeMap 实现排序 TreeMap是一个有序结构&#xff0c;TreeSet也是类似。 他们可以实现对元素的排序。TreeMap 是针对 key进行排序。 如果TreeMap的key 是Integer类型&#xff0c;可以无需指定任何特殊条件&#xff0c;默认即按照升序进行排序&#xff0c;如&#xff1a;…

经典数据结构——堆的实现

一、完全二叉树 堆是一种完全二叉树&#xff0c;什么是完全二叉树&#xff1f; 简单的说&#xff0c;一棵满二叉树表示的是所有节点全部饱和&#xff0c;最后一层全部占满&#xff1a; 而完全二叉树指的是满二叉树的最后一层&#xff0c;所有叶子节点都从左往顺序排满&#x…