jdbc的概念以及与数据库的交互流程
😄生命不息,写作不止
🔥 继续踏上学习之路,学之分享笔记
👊 总有一天我也能像各位大佬一样
🏆 博客首页 @怒放吧德德 To记录领地
🌝分享学习心得,欢迎指正,大家一起学习成长!
引言
人总是向前走的,目标是更上一层楼。原本笔者奔着一颗炙热的心去学习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包。
2、jdbc的一些概念
接下来就了解一些主要的概念,包括驱动、连接、语句、结果集等。
数据库驱动程序(Database Driver):数据库驱动程序是实现JDBC接口的软件组件,它允许Java应用程序与特定类型的数据库进行通信。不同的数据库供应商提供不同的驱动程序实现。 连接(Connection):连接是通过JDBC与数据库建立的会话。它表示Java应用程序与数据库之间的通信通道。使用连接,应用程序可以执行SQL语句并获取结果。 语句(Statement):语句是在数据库上执行的SQL命令。JDBC提供了多种语句类型,包括普通语句(Statement)、预编译语句(PreparedStatement)和可调用语句(CallableStatement)等。应用程序可以使用这些语句来执行查询、更新、插入和删除等操作。 结果集(ResultSet):结果集是从数据库检索到的数据集合。它是通过执行查询语句获得的,并且提供了一种迭代的方式来遍历和访问结果。 批处理(Batch Processing):JDBC支持批处理操作,它允许一次执行多个SQL语句。这对于批量插入、更新或删除数据非常有用,可以提高性能和效率。 事务(Transaction):事务是一组数据库操作,它们要么全部成功执行,要么全部回滚(撤销)。JDBC支持事务管理,可以通过开始事务、提交事务或回滚事务来确保数据的一致性和完整性。
使用核心API - statement
在JDBC中,statement(语句)是用于执行SQL命令的接口之一。它用于执行静态SQL语句1,即在编译时已经确定的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()
方法来关闭资源。关闭资源可以释放数据库连接和相关的系统资源。
以上这张图简单的画出了jdbc与数据库通信的流程步骤。
2、用代码实现jdbc与数据库通信步骤
接下来就简单使用一个demo来实现以上jdbc与数据的通信步骤,主要是通过驱动管理器DriverManager来注册驱动和建立连接,在建立连接的时候需要传入数据库服务器url和用户名、密码、数据库信息,与navicat的连接是差不多的,只是使用java代码来实现,然后创建statement对象去执行sql语句,并且能获得一个ResultSet对象的结果集,里面有next()方法用来移动游标输出数据,当开始是指向第一行数据之前,next()将游标往下移动一行,接着通过getString(columnLabel)等方法获取每行的列数据。
准备数据
需要准备数据库,这里用一张tb_user的表来做测试,里面就只有id、name、address三个字段。
代码如下
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();
}
}
执行结果
通过控制台输出获取得到的数据
详细介绍使用核心API - statement
接下来详细介绍以下流程步骤中的一些小问题与小细节。
1、注册驱动
注册驱动,可以通过驱动管理(DriverManager)的 DriverManager.registerDriver(new Driver());
方法来实现。这样的注册驱动就会出现驱动被注册了两次,我们看一下DriverManager.registerDriver()方法内容
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
再看一下new Driver()的时候,在Driver.class里面的静态代码块也是会注册一次驱动。
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我们就要导入对应的包,就显得不太灵活。
换种方式,我们可以使用反射的机制来实现。当使用不同的数据库,就只要更换一下驱动的全类名,这个字符串是可以提出来作为一种可配置的参数,这样就比较灵活了。
Class.forName("com.mysql.cj.jdbc.Driver");
2、获取连接
我们可以看到DriverManager.getconnection()重载了三个方法。三个方法的效果是一样的,只是携带的参数写法并不相同。
1)、getConnection(String url, String user, String password)
我们需要将数据库服务器的地址,用户名,密码传入就能建立连接。用户名和密码没什么可说的,就是登录凭证。就是这个url地址,这个地址的组成是:
jdbc:数据库管理软件名称://ip地址|主机名:port端口号/数据库?key=value&key=value,后面是可选参数。
如果是localhost:3306可以替换成 jdbc:数据库管理软件名称:///数据库
这个方法实现,传进来的用户名密码,最后也是放到了java.util.Properties这个对象之中。
@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这个对象之中,
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里头了,其组成如下:
jdbc:数据库管理软件名称://ip地址|主机名:port端口号/数据库?user=value&password=value
3、创建Statement对象
通过连接connection去创建,就能够获得一个statement对象。并且需要一条sql语句的字符串。
Statement statement = connection.createStatement();
4、发送SQL语句
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。
学习一下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,代表是具体数据的上一行。
我们通过while循环去遍历数据,并且使用next将游标移动到下行,如果有数据,此时结果集就指向了数据行。
通过resultSet.getLong(columnIndex|columnLabel)来获取对应类型的数据,这个get属性可以使用两种方式来获取:
①、通过列数据的下标,这个下标是从1开始。
②、通过列标签,这里的列标签可以是列名也可以是别名,当sql语句中使用了AS
给字段取别名时,这就需要使用别名了。
while (resultSet.next()) {
long aLong = resultSet.getLong(1);
String username = resultSet.getString("username");
System.out.println("id: " + aLong + ", username: " + username);
}
6、关闭资源
当使用完之后就需要将资源进行销毁
// 6、关闭资源
resultSet.close();
statement.close();
connection.close();
总结
使用jdbc与数据库进行通信,简单来说就只有6个步骤,首先需要注册驱动,获取连接,接着创建statement对象,用来存放sql语句并且执行,然后将获得到的数据进行解析,最后就是要把资源进行销毁。这里只是基本的使用jdbc,像mybatis、jpa、ibatis等持久层框架,都是对jdbc进行封装,万变不离其宗,想要学习底层知识,就需要把这学基础知识给学好!