当前位置: 首页 > news >正文

【JavaWeb】JDBC

1 引言

在前面我们学习MySQL数据库时,都是利用图形化客户端工具(如:idea、datagrip),来操作数据库的。

我们做为后端程序开发人员,通常会使用Java程序来完成对数据库的操作。Java程序操作数据库的技术有很多,而最为底层、最为基础的就是JDBC。

JDBC:(Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API。 【是操作数据库最为基础、底层的技术】

但是使用JDBC来操作数据库会比较繁琐,所以现在在企业项目开发中呢,一般都会使用基于JDBC的封装的高级框架,比如:Mybatis、MybatisPlus、Hibernate、SpringDataJPA。

目前最为主流的就是Mybatis,其次是MybatisPlus。

这两种主流的操作数据库的框架我们都要学习。 而我们在学习这两个主流的框架之前,还需要学习一下操作数据库的基础框架 JDBC。

2 JDBC

2.1 介绍

JDBC:(Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API。

本质:

sun公司官方定义的一套操作所有关系型数据库的规范,即接口

各个数据库厂商去实现这套接口,提供数据库驱动jar包。

我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

有了JDBC之后,就可以直接在java代码中来操作数据库了,只需要编写这样一段java代码,就可以来操作数据库中的数据。 示例代码如下:

@Test
public void testUpdate() throws Exception {//准备工作Class.forName("com.mysql.cj.jdbc.Driver");String url = "jdbc:mysql://localhost:3306/web";Connection connection = DriverManager.getConnection(url, "root", "root@1234");Statement statement = connection.createStatement();//执行SQLstatement.executeUpdate("update user set password = '1234567890' where id = 1");//释放资源statement.close();connection.close();
}

2.2 查询数据

2.2.1 需求

需求:基于JDBC实现用户登录功能。

本质:其本质其实就是基于JDBC程序,执行如下select语句,并将查询的结果输出到控制台。

select * from user where username = 'linchong' and password = '123456';

2.2.2 准备工作

1) 创建一个maven项目

2) 创建一个数据库 web,并在该数据库中创建user表

create table user(id int unsigned primary key auto_increment comment 'ID,主键',username varchar(20) comment '用户名',password varchar(32) comment '密码',name varchar(10) comment '姓名',age tinyint unsigned comment '年龄'
) comment '用户表';insert into user(id, username, password, name, age) values (1, 'daqiao', '123456', '大乔', 22),(2, 'xiaoqiao', '123456', '小乔', 18),(3, 'diaochan', '123456', '貂蝉', 24),(4, 'lvbu', '123456', '吕布', 28),(5, 'zhaoyun', '12345678', '赵云', 27);

2.2.3 代码实现

1) 在 pom.xml 文件中引入依赖

<dependencies><!-- MySQL JDBC driver --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.9.3</version><scope>test</scope></dependency>
</dependencies>

2) 在 src/main/test/java 目录下编写测试类,定义测试方法

public class JDBCTest {/*** 编写JDBC程序, 查询数据*/@Testpublic void testJdbc() throws Exception {// 获取连接Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web", "root", "****");// 创建预编译的PreparedStatement对象PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM user WHERE username = ? AND password = ?");// 设置参数pstmt.setString(1, "daqiao"); // 第一个问号对应的参数pstmt.setString(2, "123456"); // 第二个问号对应的参数// 执行查询ResultSet rs = pstmt.executeQuery();// 处理结果集while (rs.next()) {int id = rs.getInt("id");String uName = rs.getString("username");String pwd = rs.getString("password");String name = rs.getString("name");int age = rs.getInt("age");System.out.println("ID: " + id + ", Username: " + uName + ", Password: " + pwd + ", Name: " + name + ", Age: " + age);}// 关闭资源rs.close();pstmt.close();conn.close();}}

而上述的单元测试中,我们在SQL语句中,将 用户名 和密码的值都写死了,而这两个值应该是动态的,是将来页面传递到服务端的。 那么,我们可以基于前面所讲解的JUnit中的参数化测试进行单元测试,代码改造如下:

public class JDBCTest {/*** 编写JDBC程序, 查询数据*/@ParameterizedTest@CsvSource({"daqiao,123456"})public void testJdbc(String _username, String _password) throws Exception {// 获取连接Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web", "root", "****");// 创建预编译的PreparedStatement对象PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM user WHERE username = ? AND password = ?");// 设置参数pstmt.setString(1, _username); // 第一个问号对应的参数pstmt.setString(2, _password); // 第二个问号对应的参数// 执行查询ResultSet rs = pstmt.executeQuery();// 处理结果集while (rs.next()) {int id = rs.getInt("id");String uName = rs.getString("username");String pwd = rs.getString("password");String name = rs.getString("name");int age = rs.getInt("age");System.out.println("ID: " + id + ", Username: " + uName + ", Password: " + pwd + ", Name: " + name + ", Age: " + age);}// 关闭资源rs.close();pstmt.close();conn.close();}}

如果在测试时,需要传递一组参数,可以使用  @CsvSource 注解。

2.2.4 代码剖析

2.2.4.1 ResultSet

ResultSet(结果集对象):封装了DQL查询语句查询的结果。

        next():将光标从当前位置向前移一行并判断当前行是否为有效行,返回值为boolean。

                true:有效行,当前行有数据

                false:无效行,当前行没有数据

        getXxx(…):获取数据,可以根据列的编号获取,也可以根据列名获取(推荐)。

结果解析步骤:

while (resultSet.next()){int id = resultSet.getInt("id");//...
}
2.2.4.2 预编译SQL

其实我们在编写SQL语句的时候,有两种风格:

静态SQL(参数硬编码)

conn.prepareStatement("SELECT * FROM user WHERE username = 'daqiao' AND password = '123456'");
ResultSet resultSet = pstmt.executeQuery();

这种就是参数值,直接拼接在SQL语句中,参数值是写死的。

预编译SQL(参数动态传递)

conn.prepareStatement("SELECT * FROM user WHERE username = ? AND password = ?");
pstmt.setString(1, "daqiao");
pstmt.setString(2, "123456");
ResultSet resultSet = pstmt.executeQuery();

这种并未将参数值在SQL语句中写死,而是使用 ? 进行占位,然后再指定每一个占位符对应的值是多少,而最终在执行SQL语句的时候,程序会将SQL语句(SELECT * FROM user WHERE username = ? AND password = ?),以及参数值("daqiao", "123456")都发送给数据库,然后在执行的时候,会使用参数值,将?占位符替换掉。

那这种预编译的SQL,也是在项目开发中推荐使用的SQL语句。主要的作用有两个:

1.防止SQL注入

2.性能更高

2.2.4.2.1 SQL注入

SQL注入:通过控制输入来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。

SQL注入最典型的场景,就是用户登录功能。

举个静态SQL的例子:

比如我们的代码是这个:

conn.prepareStatement("SELECT * FROM user WHERE username = 'daqiao' AND password = '123456'");
ResultSet resultSet = pstmt.executeQuery();

在进行登录操作时,怎么样才算登录成功呢? 如果我们查询到了数据,就说明用户名密码是对的。 如果没有查询到数据,就说明用户名或密码错误。 

如果在登录界面输入正确密码时,显然可以正常成功地登录,但如果输入错误的密码呢?

我们随便输入一个用户名,然后在密码中输入以下代码:

' or '1' = '1

注意到,此时,提供给后端的代码在运行时,变成了

SELECT * FROM user WHERE username = 'dadvasdn' AND password = '' or '1' = '1'

发现代码逻辑发生了变化,输入的用户名是乱码,密码为空,但后面多了一个逻辑或运算符or,且条件为真。

也就是说,这条语句会成功运行,而用户就成功登录了。

2.2.4.2.2 SQL注入解决

而通过预编译SQL(select * from user where username = ? and password = ?),就可以直接解决上述SQL注入的问题。 因为在使用预编译SQL时,后端收到的只是一堆字符串,通过进行这些字符串的对比来运行,而且输入的转义字符在进入源码时其破坏作用也会大幅下降。

因此在以后的项目开发中,我们使用的基本全部都是预编译SQL语句。

2.2.4.2.3 性能更高

在MySQL进行数据对比的过程中,首先在缓存中进行SQL语句的语法解析检查、优化和编译,然后再去执行。

那么如果我们用的是静态SQL语句,那么由于每次的SQL语句都不同,在进行他们之间的对比时,SQL语句都存在缓存中了,利用率很低,而且在缓存中进行查询速度更快,效率更高,静态SQL语句显然无法发挥这个优势。

而如果我们用的是预编译的SQL语句,那么在进行上述操作时,显然由于代码框架不是死板的了,具有更强的复用性,用户第一次输入数据时缓存中就存储了优化后的预编译的SQL语句,之后再有数据进来,缓存中就能找到相应的SQL语句,从而进行更加快捷高效的查询。

2.3 增删改数据

2.3.1 需求

需求:基于JDBC程序,执行如下update语句。

update user set password = '123456', gender = 2 where id = 1;

2.3.2 代码实现

@ParameterizedTest
@CsvSource({"1,123456,25"})
public void testUpdate(int userId, String newPassword, int newAge) throws Exception {// 建立数据库连接Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web", "root", "1234");// SQL 更新语句String sql = "UPDATE user SET password = ?, age = ? WHERE id = ?";// 创建预编译的PreparedStatement对象PreparedStatement pstmt = conn.prepareStatement(sql);// 设置参数pstmt.setString(1, newPassword); // 第一个问号对应的参数pstmt.setInt(2, newAge);      // 第二个问号对应的参数pstmt.setInt(3, userId);         // 第三个问号对应的参数// 执行更新int rowsUpdated = pstmt.executeUpdate();// 输出结果System.out.println(rowsUpdated + " row(s) updated.");// 关闭资源pstmt.close();conn.close();
}

JDBC程序执行DML语句:int rowsUpdated = pstmt.executeUpdate(); //返回值是影响的记录数

JDBC程序执行DQL语句:ResultSet resultSet = pstmt.executeQuery(); //返回值是查询结果集

相关文章:

  • 如何利用内网穿透实现Cursor对私有化部署大模型的跨网络访问实践
  • java中sleep()和wait()暂停线程的区别
  • [Java实战]Spring Boot整合Elasticsearch(二十六)
  • 大模型微调步骤整理
  • 第9章 组件及事件处理
  • Mac 在恢复模式下出现 旋转地球图标 但进度非常缓慢
  • Oracle 内存优化
  • java中的Servlet3.x详解
  • sparkSQL读入csv文件写入mysql
  • 10.8 LangChain三大模块深度实战:从模型交互到企业级Agent工具链全解析
  • 多模态大语言模型arxiv论文略读(八十一)
  • SuperYOLO:多模态遥感图像中的超分辨率辅助目标检测之论文阅读
  • 贪心算法应用:最大匹配问题详解
  • 算法岗实习八股整理——深度学习篇(不断更新中)
  • 软件工程各种图总结
  • MySQL开发规范
  • 互联网大厂Java面试:从Spring到微服务的深度探讨
  • 大模型deepseek与知识图谱的实践
  • 【数据结构】2-3-3单链表的查找
  • 离散文本表示
  • 王毅同丹麦外交大臣拉斯穆森会谈
  • 家庭医生可提前5天预约三甲医院号源,上海常住人口签约率达45%,
  • 香港今年新股集资额已超600亿港元,暂居全球首位
  • 国际博物馆日|航海博物馆:穿梭于海洋神话与造船工艺间
  • 全中国最好的十个博物馆展陈选出来了!
  • 15年全免费,内蒙古准格尔旗实现幼儿园到高中0学费