jdbc的概念以及与数据库的交互流程

jdbc1.jpg

引言

人总是向前走的,目标是更上一层楼。原本笔者奔着一颗炙热的心去学习mybatis底层原理,无奈看了一章感觉我还是太菜了,没有对jdbc有个基础的了解,看起来就比较吃力,因此这次就来学习一下jdbc。本次所学习的大致内容是jdbc的概念与理解,以及如何去使用jdbc来实现Java程序和数据库进行打交道。

JDBC的概念

JDBC(Java Database Connectivity),它是Java编程语言中用于连接和操作数据库的API。JDBC提供了一组类和接口,允许Java应用程序与各种关系型数据库进行通信。它提供了一种标准化的方式来与数据库交互。

1、jdbc与数据的交互

Java程序通过jdbc这个规范(接口)来与第三方数据库厂商进行交互,根据jdbc规范根据具体的实现驱动代码,不同厂商数据库的驱动代码不同,但是方法是相同的。jdbc是一组接口,最终实现是由各个数据库厂商(jar)来实现的,是一种面向接口编程。无论是MySQL、Oracle、SQL Server还是其他关系型数据库系统,只需要切换对应的数据库驱动jar包。

image-20230715220756994.png

2、jdbc的一些概念

接下来就了解一些主要的概念,包括驱动、连接、语句、结果集等。

  1. 数据库驱动程序(Database Driver):数据库驱动程序是实现JDBC接口的软件组件,它允许Java应用程序与特定类型的数据库进行通信。不同的数据库供应商提供不同的驱动程序实现。
  2. 连接(Connection):连接是通过JDBC与数据库建立的会话。它表示Java应用程序与数据库之间的通信通道。使用连接,应用程序可以执行SQL语句并获取结果。
  3. 语句(Statement):语句是在数据库上执行的SQL命令。JDBC提供了多种语句类型,包括普通语句(Statement)、预编译语句(PreparedStatement)和可调用语句(CallableStatement)等。应用程序可以使用这些语句来执行查询、更新、插入和删除等操作。
  4. 结果集(ResultSet):结果集是从数据库检索到的数据集合。它是通过执行查询语句获得的,并且提供了一种迭代的方式来遍历和访问结果。
  5. 批处理(Batch Processing):JDBC支持批处理操作,它允许一次执行多个SQL语句。这对于批量插入、更新或删除数据非常有用,可以提高性能和效率。
  6. 事务(Transaction):事务是一组数据库操作,它们要么全部成功执行,要么全部回滚(撤销)。JDBC支持事务管理,可以通过开始事务、提交事务或回滚事务来确保数据的一致性和完整性。

使用核心API - statement

在JDBC中,statement(语句)是用于执行SQL命令的接口之一。它用于执行静态SQL语句[1],即在编译时已经确定的SQL语句,不允许参数化。

[1] 静态SQL语句是在编译时已经确定的SQL语句,其结构和内容在程序运行时不会改变。它是指那些SQL查询或数据操作语句的文本内容在编译时就已经确定,并且不需要在运行时进行参数化或动态修改的语句。

1、jdbc是如何进行与数据库通信的呢?

jdbc是Java程序与数据库交互的一个桥梁,完成通信需要通过六个步骤:

①、加载驱动程序,需要加载适用于特定数据库的驱动程序,本次是使用mysql8.0,所以需要加载mysql-connector-java-8.0.27.jar包。

②、建立连接,通过驱动去建立连接,可以理解为这样就会有一条与数据库服务器交互的管道。实际上就是使用数据库连接URL指定数据库的地址、端口和其他连接参数,通过TCP/IP协议建立与数据库服务器的连接。

③、创建Statement或PreparedStatement对象,这个对象是存储sql语句,可以理解为是一个搭载sql语句的盒子,需要被送到数据库服务器中执行。

④、执行SQL语句,需要将statement对象通过连接通道送到数据库服务器执行sql语句并且返回结果集。

⑤、处理结果,将返回的结果集resultSet进行解析处理。

⑥、关闭资源,使用完Statement、PreparedStatement、ResultSet和连接等资源后,需要调用它们的close()方法来关闭资源。关闭资源可以释放数据库连接和相关的系统资源。

image-20230715225147420.png

以上这张图简单的画出了jdbc与数据库通信的流程步骤。

2、用代码实现jdbc与数据库通信步骤

接下来就简单使用一个demo来实现以上jdbc与数据的通信步骤,主要是通过驱动管理器DriverManager来注册驱动和建立连接,在建立连接的时候需要传入数据库服务器url和用户名、密码、数据库信息,与navicat的连接是差不多的,只是使用java代码来实现,然后创建statement对象去执行sql语句,并且能获得一个ResultSet对象的结果集,里面有next()方法用来移动游标输出数据,当开始是指向第一行数据之前,next()将游标往下移动一行,接着通过getString(columnLabel)等方法获取每行的列数据。

准备数据

需要准备数据库,这里用一张tb_user的表来做测试,里面就只有id、name、address三个字段。

image-20230716131453492.png

代码如下

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package cn.lyd.jdbc;
import com.mysql.cj.jdbc.Driver;
import java.sql.*;
/**
* @Author: lyd
* @Description: Jdbc - statement使用
* @Date: 2023/7/15
*/
public class JdbcDemo {
/**
* DriverManager
* Connection
* Statement
* ResultSet
* @param args
*/
public static void main(String[] args) throws SQLException {
// 1、注册驱动
/**
* 注册驱动,如果是8.0+就使用com.mysql.cj.jdbc.Driver
*/
DriverManager.registerDriver(new Driver());
// 2、获取连接
/**
* 连接数据库需要填入数据库ip地址,端口号,用户名,密码,连接的数据库名称
*/
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/cloud_user", "root", "12356");

// 3、创建statement
Statement statement = connection.createStatement();

// 4、发送sql语句并执行
String sql = "select * from tb_user;";
ResultSet resultSet = statement.executeQuery(sql);
// 5、解析结果集
/**
* 有没有下行数据
*/
while (resultSet.next()) { // 最开始指定的是数据的上一行即起始位置,并无数据,.next方法会将游标往下移动一行。
long id = resultSet.getLong("id");
String username = resultSet.getString("username");
String address = resultSet.getString("address");
System.out.println(id + " - " + username + " - " + address);
}

// 6、关闭资源
resultSet.close();
statement.close();
connection.close();
}
}

执行结果

通过控制台输出获取得到的数据

image-20230716131544439.png

详细介绍使用核心API - statement

接下来详细介绍以下流程步骤中的一些小问题与小细节。

1、注册驱动

注册驱动,可以通过驱动管理(DriverManager)的DriverManager.registerDriver(new Driver());方法来实现。这样的注册驱动就会出现驱动被注册了两次,我们看一下DriverManager.registerDriver()方法内容

1
2
3
4
5
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {

registerDriver(driver, null);
}

再看一下new Driver()的时候,在Driver.class里面的静态代码块也是会注册一次驱动。

1
2
3
4
5
6
7
8
9
10
11
12
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!");
}
}
}

对于这个问题,我们只要让Driver的静态代码块执行就行了。这里我们就要先了解以下静态代码块是何时触发的呢?

静态代码块的执行时期:

类的加载机制:在类的加载的时候会执行静态代码块。简单的介绍一下步骤:

​ 1、加载(Loading):将类的字节码文件加载到内存中。

​ 2、链接(Linking):链接又分了三个阶段,验证(Verification):对加载的字节码进行验证,确保其符合Java虚拟机规范,不会造成安全或运行时错误;准备(Preparation):为类的静态变量分配内存空间,并设置默认初始值;解析(Resolution):将符号引用(Symbolic References)解析为直接引用(Direct References),即将类、方法、字段等符号引用解析为内存中的实际数据。

​ 3、初始化(Initialization):对类进行初始化,包括静态变量的赋值和静态代码块的执行。

触发类加载:

​ 1、通过new关键字;2、调用了静态方法;3、调用了静态属性;4、jdk1.8的接口加上了default关键字;5、反射;6、子类触发父类;7、程序的入口main。

驱动重复注册的解决

显然我们就不能直接使用DriverManager.registerDriver(new Driver()); 来直接注册驱动,我们如果想不使它重复注册,可以让他只是执行一次静态代码块直接new Driver() ,当然这样做的也有弊端,如果我们使用的不是mysql我们就要导入对应的包,就显得不太灵活。

image-20230716144510821.png

换种方式,我们可以使用反射的机制来实现。当使用不同的数据库,就只要更换一下驱动的全类名,这个字符串是可以提出来作为一种可配置的参数,这样就比较灵活了。

1
Class.forName("com.mysql.cj.jdbc.Driver");

2、获取连接

我们可以看到DriverManager.getconnection()重载了三个方法。三个方法的效果是一样的,只是携带的参数写法并不相同。

image-20230716145004432.png

1)、getConnection(String url, String user, String password)

我们需要将数据库服务器的地址,用户名,密码传入就能建立连接。用户名和密码没什么可说的,就是登录凭证。就是这个url地址,这个地址的组成是:

1
2
jdbc:数据库管理软件名称://ip地址|主机名:port端口号/数据库?key=value&key=value,后面是可选参数。
如果是localhost:3306可以替换成 jdbc:数据库管理软件名称:///数据库

这个方法实现,传进来的用户名密码,最后也是放到了java.util.Properties这个对象之中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();

if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}

return (getConnection(url, info, Reflection.getCallerClass()));
}

2)、getConnection(String url, java.util.Properties info)

这个方法与上一个不一样的是,将用户和密码在调用的时候直接放入java.util.Properties这个对象之中,

1
2
3
4
Properties properties = new Properties();
properties.put("user", "root");
properties.put("password", "root");
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/cloud_user", properties);

3)、getConnection(String url)

这个方法就是把所有的需要的参数都放在这个url里头了,其组成如下:

1
jdbc:数据库管理软件名称://ip地址|主机名:port端口号/数据库?user=value&password=value

3、创建Statement对象

通过连接connection去创建,就能够获得一个statement对象。并且需要一条sql语句的字符串。

1
Statement statement = connection.createStatement();

4、发送SQL语句

1
2
3
String sql = "SELECT * FROM tb_user WHERE username = 'lyd';";
int i = statement.executeUpdate(sql);
ResultSet resultSet = statement.executeQuery(sql);

当使用的是executeUpdate就会返回一个int数值,使用executeQuery将会返回一个结果集。

我们需要根据不用的SQL类型去使用对应的方法,在statement接口中已经标明了注释。

使用executeQuery:如果发送的sql语句是静态sql SELECT语句,返回的结果就是包含给定查询产生的数据的ResultSet对象,如果是没有数据,那就返回为null。

使用executeUpdate:如果语句是SQL数据操作语言(DML)语句,(INSERT、UPDATE或DELETE);或者是不返回任何结果的SQL语句,比如DDL语句。返回的结果就是数据操作语言(DML)语句的行数,没有就是返回0。

image-20230716160924816.png

学习一下SQL主要的分类:

数据定义语言(Data Definition Language,DDL):DDL用于创建和管理数据库对象,如表、视图、索引等。常见的DDL语句包括CREATE(创建)、ALTER(修改)、DROP(删除)等。

数据操作语言(Data Manipulation Language,DML):DML用于查询和操作数据库中的数据记录。它包括SELECT(查询)、INSERT(插入)、UPDATE(更新)、DELETE(删除)等语句,用于从数据库中检索、插入、更新和删除数据。

数据控制语言(Data Control Language,DCL):DCL用于管理数据库的访问权限和安全性。它包括GRANT(授权)和REVOKE(撤销授权)等语句,用于控制用户对数据库对象的访问权限。

事务控制语言(Transaction Control Language,TCL):TCL用于控制数据库中的事务处理。它包括BEGIN TRANSACTION(开始事务)、COMMIT(提交事务)、ROLLBACK(回滚事务)等语句,用于确保数据库操作的原子性、一致性、隔离性和持久性。

除了上述常见的分类,SQL还可以根据具体的数据库系统和实现方式进行进一步分类,如存储过程语言(Stored Procedure Language)、游标语言(Cursor Language)等。每种类型的SQL语句都有其特定的语法和功能,开发人员可以根据需要选择合适的语句来进行数据库操作。

5、解析结果集

当查询找到了所需要的数据,就会将找到的数据封装到ResultSet对象,结果集ResultSet对象维护一个指向其当前数据行的游标。最初,游标位于第一行之前。next方法将光标移动到下一行,因为当ResultSet对象中没有更多的行时,它返回false,所以可以在while循环中使用它来遍历结果集。

我们通过断点可以看到,刚开始结果集指向的当前行是-1,代表是具体数据的上一行。

image-20230716162104620.png

我们通过while循环去遍历数据,并且使用next将游标移动到下行,如果有数据,此时结果集就指向了数据行。

通过resultSet.getLong(columnIndex|columnLabel)来获取对应类型的数据,这个get属性可以使用两种方式来获取:

①、通过列数据的下标,这个下标是从1开始。

②、通过列标签,这里的列标签可以是列名也可以是别名,当sql语句中使用了AS 给字段取别名时,这就需要使用别名了。

1
2
3
4
5
6
while (resultSet.next()) {
long aLong = resultSet.getLong(1);
String username = resultSet.getString("username");

System.out.println("id: " + aLong + ", username: " + username);
}

6、关闭资源

当使用完之后就需要将资源进行销毁

1
2
3
4
// 6、关闭资源
resultSet.close();
statement.close();
connection.close();

总结

使用jdbc与数据库进行通信,简单来说就只有6个步骤,首先需要注册驱动,获取连接,接着创建statement对象,用来存放sql语句并且执行,然后将获得到的数据进行解析,最后就是要把资源进行销毁。这里只是基本的使用jdbc,像mybatis、jpa、ibatis等持久层框架,都是对jdbc进行封装,万变不离其宗,想要学习底层知识,就需要把这学基础知识给学好!