06-Web后端基础(java操作数据库)

news2025/6/1 11:01:43

1. 前言

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

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

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

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

而这些技术,目前的市场占有份额如下图所示:

从上图中,我们也可以看到,目前最为主流的就是Mybatis,其次是MybatisPlus。

所以,在我们的课程体系中呢,这两种主流的操作数据库的框架我们都要学习。 而我们在学习这两个主流的框架之前,还需要学习一下操作数据库的基础基础 JDBC。 然后接下来,再来学习Mybatis。 而在我们后面的课程中,我们还要学习MybatisPlus框架。 那么今天呢,我们就先来学习 JDBC 和 Mybatis。

今天课程安排:

  1. JDBC
  2. Mybatis
  3. SpringBoot配置文件

2. JDBC

2.1 介绍

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

本质:

  • sun公司官方定义的一套操作所有关系型数据库的规范,即接口。
  • 各个数据库厂商去实现这套接口,提供数据库驱动jar包。
  • 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

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

2.2 查询数据

2.2.1 需求

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

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

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 代码实现

AI提示词(prompt):

你是一名java开发工程师,帮我基于JDBC程序来操作数据库,执行如下SQL语句:

select * from user where username = 'daqiao' and password = '123456'

具体的代码为:

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程序, 查询数据
     */
    @Test
    public void testJdbc() throws Exception {
        // 获取连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web", "root", "1234");
        // 创建预编译的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中的参数化测试进行单元测试,代码改造如下:

版本1:

@Test
public void testSelect() throws Exception {
    Connection connection = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    try {
        String url = "jdbc:mysql://localhost:3306/web01";
        String username = "root";
        String password = "fu921400521";

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

        connection = DriverManager.getConnection(url, username, password);
        preparedStatement = connection
                .prepareStatement("select * from user where username = ? and password = ?");
        preparedStatement.setString(1, "daqiao");
        preparedStatement.setString(2, "123456");

        resultSet = preparedStatement.executeQuery();

        while (resultSet.next()) {
            User user = new User(
                    resultSet.getInt("id"),
                    resultSet.getString("username"),
                    resultSet.getString("password"),
                    resultSet.getString("name"),
                    resultSet.getInt("age")
            );

            System.out.println(user);
        }
    } catch (SQLException se) {
        se.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        try {
            if (resultSet != null) resultSet.close();
            if (preparedStatement != null) preparedStatement.close();
            if (connection != null) connection.close();
        }catch (SQLException se) {
            se.printStackTrace();
        }
    }

}

版本2:

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", "1234");
        // 创建预编译的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(…):获取数据,可以根据列的编号获取,也可以根据列名获取(推荐)。

结果解析步骤:

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语句。主要的作用有两个:

  • 防止SQL注入
  • 性能更高

那接下来,我们就来介绍一下这两点。

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

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

注入演示:

1). 打开课程资料中的文件夹 资料/02. SQL注入演示,运行其中的jar包 sql_Injection_demo-0.0.1-SNAPSHOT.jar,进入该目录后,执行命令:

java -jar sql_Injection_demo-0.0.1-SNAPSHOT.jar

2). 打开浏览器访问 http://localhost:9090/ ,必须登录后才能访问到系统。我们先测试正常的用户名和密码

3). 接下来,我们再来测试一下错误的用户名和密码 。

我们看到,如果用户名密码错误,是不能进入到系统中进行访问的,会提示 用户名和密码错误

4). 那接下来,我们就要演示一下SQL注入现象,我们可以通过控制表单输入,来修改事先定义好的SQL语句的含义。 从而来攻击服务器。

点击登录后,我们看到居然可以成功进入到系统中。

为什么会出现这种现象呢?

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

而出现上述现象,原因就是因为,我们我们编写的SQL语句是基于字符串进行拼接的 。 我们输入的用户名无所谓,比如:shfhsjfhja ,而密码呢,就是我们精心设计的,如:' or '1' = '1

那最终拼接的SQL语句,如下所示:

我们知道,or 连接的条件,是或的关系,两者满足其一就可以。 所以,虽然用户名密码输入错误,也是可以查询返回结果的,而只要查询到了数据,就说明用户名和密码是正确的。

2.2.4.2.2 SQL注入解决

而通过预编译SQL(select * from user where username = ? and password = ?),就可以直接解决上述SQL注入的问题。 接下来,我们再来演示一下,通过预编译SQL是否能够解决SQL注入问题。

1). 打开课程资料中的文件夹 资料/02. SQL注入演示,运行其中的jar包 sql_prepared_demo-0.0.1-SNAPSHOT.jar,进入该目录后,执行命令:

java -jar sql_prepared_demo-0.0.1-SNAPSHOT.jar

2). 打开浏览器访问 http://localhost:9090/ ,必须登录后才能访问到系统 。我们先测试正常的用户名和密码

3). 那接下来,我们就要演示一下是否可以基于上述的密码 ' or '1' = '1,来完成SQL注入 。

通过控制台,可以看到输入的SQL语句,是预编译SQL语句。

而在预编译SQL语句中,当我们执行的时候,会把整个' or '1'='1作为一个完整的参数,赋值给第2个问号(' or '1'='1进行了转义,只当做字符串使用)

那么此时再查询时,就查询不到对应的数据了,登录失败。

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

2.2.4.2.3 性能更高

2.3 增删改数据

2.3.1 需求
  • 需求:基于JDBC程序,执行如下update语句。
  • SQL:
update user set password = '123456', gender = 2 where id = 1;
2.3.2 代码实现

AI提示词(prompt):

你是一名java开发工程师,帮我基于JDBC程序来操作数据库,执行如下SQL语句:

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

代码实现如下:

@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(); //返回值是查询结果集

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

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

相关文章

什么是单片机?

众所周知&#xff0c;人类行为受大脑调控&#xff0c;正如视觉、听觉、味觉、嗅觉、触觉及运动功能等感官与肢体活动均受其指挥&#xff1b;换言之&#xff0c;大脑作为人体的中枢神经系统&#xff0c;负责管理所有可控制的生理功能。 在电子设备领域&#xff0c;单片机…

Ubuntu的shell脚本

关于shell脚本 • shell脚本是文本的一种。 • shell脚本是可以运行的文本。 • shell脚本的内容是由说辑和数据组成。 • shell 脚本是解释型语言。 shell脚本存在的意义 Shell脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具 Linux/UNIX系统…

从抄表到节能,电费管理系统如何重构公寓运营场景——仙盟创梦IDE

租房公寓电费管理系统是集智能计量、自动化计费、线上缴费、数据管理于一体的综合性解决方案&#xff0c;旨在解决传统电费管理中人工抄表误差大、收费效率低、纠纷频发等痛点。系统通过部署智能电表实时采集用电数据&#xff0c;结合云计算与大数据分析技术&#xff0c;实现电…

记一次前端逻辑绕过登录到内网挖掘

前言 在测试一个学校网站的时候&#xff0c;发现一个未授权访问内网系统&#xff0c;但是这个未授权并不是接口啥的&#xff0c;而是对前端 js 的审计和调试发现的漏洞&#xff0c;这里给大家分享一下这次的漏洞的过程。 进入内网的过程 可以看到是一个图书馆的网站&#xff…

Springboot 整合 WebSocket 实现聊天室功能

目录 前言一、WebSocket原理二、Spring Boot集成WebSocket2.1. 引入依赖2.2 配置类WebSocketConfig2.3 WebSocketServer 类2.4 前端代码 index.html2.5 Controller访问首页 前言 WebSocket概述&#xff1a; 在日常的web应用开发中&#xff0c;常见的是前端向后端发起请求&…

用 Trae IDE 打造一个桌面小爬虫:从 PyQt5 开始,轻松采集掘金首页内容

很多程序员都有这样的经历&#xff1a;刷掘金、看文章、找灵感、追热点。但你有没有想过&#xff0c;有一天让“爬虫”代替你去浏览这些内容&#xff1f;自动提取标题、作者、点赞数、评论数&#xff0c;一键生成你的专属“技术热点日报”。 今天我们就用 Trae IDE PyQt5 来完…

python和风api获取天气(JSON Web Token)

下载安装openssl 默认安装目录&#xff0c;添加C:\Program Files\OpenSSL-Win64\bin到用户Path环境变量 打开cmd&#xff0c;执行命令&#xff0c;会生成两个文件ed25519-private.pem&#xff0c;ed25519-public.pem openssl genpkey -algorithm ED25519 -out ed25519-privat…

52、C# 泛型 (Generics)

泛型是 C# 2.0 引入的一项强大功能&#xff0c;它允许你编写可以处理多种数据类型的代码&#xff0c;而无需为每种类型重复编写相同的逻辑。泛型提高了代码的重用性、类型安全性和性能。 基本概念 泛型类 public class GenericClass<T> {private T _value;public Gene…

Allegro X PCB设计小诀窍--05.如何在Allegro X中实现隐藏电源飞线效果

背景介绍&#xff1a;在PCB设计过程中&#xff0c;布线初期印制板上的飞线错综复杂&#xff0c;信号线和电源线混合交错&#xff0c;但是实际上对于多层板来说&#xff0c;电源的网络一般是通过电源层铺铜连接的&#xff0c;很少需要走线&#xff0c;这样混乱的情况会严重影响设…

一篇文章教会你ESP8266串口WIFI无线模块实现物联网无线收发,附STM32代码示例

目录 一、ESP-01S无线模块: &#xff08;1&#xff09;特点&#xff1a; &#xff08;2&#xff09;管脚定义&#xff1a; &#xff08;3&#xff09;启动模式&#xff1a; 二、ESP-01S出厂固件烧录&#xff1a; &#xff08;1&#xff09;引脚接线&#xff1a; &#xff0…

算法-基础算法

一、枚举算法 也称为穷举算法&#xff0c;指的是按照问题本身的性质&#xff0c;一一列举出该问题所有可能的解&#xff0c;并在逐一列举的过程中&#xff0c;将它们逐一与目标状态进行比较以得出满足问题要求的解。在列举的过程中&#xff0c;既不能遗漏也不能重复 1. 问题 …

Reactor模式详解:高并发场景下的事件驱动架构

文章目录 前言一、Reactor模式核心思想二、工作流程详解2.1 服务初始化阶段2.2 主事件循环2.3 子Reactor注册流程2.4 IO事件处理时序2.5 关键设计要点 三、关键实现技术四、实际应用案例总结 前言 在现代高性能服务器开发中&#xff0c;如何高效处理成千上万的并发连接是一个关…

项目日记 -Qt音乐播放器 -设置任务栏图标与托盘图标

博客主页&#xff1a;【夜泉_ly】 本文专栏&#xff1a;【Qt音乐播放器】 欢迎点赞&#x1f44d;收藏⭐关注❤️ 代码仓库&#xff1a;MusicPlayer v1.0版视频展示&#xff1a;Qt -音乐播放器(仿网易云)V1.0 前言 本文的目标&#xff1a; 一是设置任务栏的图标&#xff0c; 二…

国产 BIM 软件万翼斗拱的技术突破与现实差距 —— 在创新与迭代中寻找破局之路

万翼斗拱在国产BIM领域迈出重要一步&#xff0c;凭借二三维一体化、参数化建模及AI辅助设计等功能形成差异化竞争力&#xff0c;在住宅设计场景中展现效率优势&#xff0c;但与国际主流软件相比&#xff0c;在功能完整性、性能稳定性和生态成熟度上仍有显著差距&#xff0c;需通…

Golang|etcd服务注册与发现 策略模式

etcd 是一个开源的 分布式键值存储系统&#xff08;Key-Value Store&#xff09;&#xff0c;主要用于配置共享和服务发现。 ETCD是一个键值&#xff08;KV&#xff09;数据库&#xff0c;类似于Redis&#xff0c;支持分布式集群。ETCD也可以看作是一个分布式文件系统&#xff…

STM32的OLED显示程序亲测可用:适用于多种场景的稳定显示解决方案

STM32的OLED显示程序亲测可用&#xff1a;适用于多种场景的稳定显示解决方案 【下载地址】STM32的OLED显示程序亲测可用 这是一套专为STM32设计的OLED显示程序&#xff0c;经过实际测试&#xff0c;运行稳定可靠。支持多种OLED屏幕尺寸和类型&#xff0c;提供丰富的显示效果&am…

【AI News | 20250529】每日AI进展

AI Repos 1、WebAgent 阿里巴巴通义实验室近日发布了WebDancer&#xff0c;一款旨在实现自主信息搜索的原生智能体搜索推理模型。WebDancer采用ReAct框架&#xff0c;通过分阶段训练范式&#xff0c;包括浏览数据构建、轨迹采样、监督微调和强化学习&#xff0c;赋予智能体自主…

Day12 - 计算机网络 - HTTP

HTTP常用状态码及含义&#xff1f; 301和302区别&#xff1f; 301&#xff1a;永久性移动&#xff0c;请求的资源已被永久移动到新位置。服务器返回此响应时&#xff0c;会返回新的资源地址。302&#xff1a;临时性性移动&#xff0c;服务器从另外的地址响应资源&#xff0c;但…

Linux驱动学习笔记(十)

热插拔 1.热插拔&#xff1a;就是带电插拔&#xff0c;即允许用户在不关闭系统&#xff0c;不切断电源的情况下拆卸或安装硬盘&#xff0c;板卡等设备。热插拔是内核和用户空间之间&#xff0c;通过调用用户空间程序实现交互来实现的&#xff0c;当内核发生了某种热拔插事件时…

TI dsp FSI (快速串行接口)

简介 快速串行接口&#xff08;FSI - Fast Serial Interface &#xff09;模块是一种串行通信外设&#xff0c;能够在隔离设备之间实现可靠的高速通信。在两个没有共同电源和接地连接的电子电路必须交换信息的情况下&#xff0c;电气隔离设备被使用。 虽然隔离设备促进了信号通…