5. MySQL - JDBC SQL 注入 博客系统(万字详解)

目录

1. 介绍

2. 使用 JDBC 连接数据库

2.1 如何使用 JDBC 连接数据库

2.2 导入的各个类

2.3 DataSource 对象的创建

2.4 从 DataSource 对象中得到 Connection 对象

2.5 创建 Statement 对象

2.6 从 ResultSet 中遍历每行结果,从每行中获取每列的值

2.7 代码汇总

3. PrepareStatement 详解

3.1 动态 SQL 执行

3.2 SQL 类型

3.3 SQL 注入(Inject)

4. 关于 SQL 的执行

4.1 带查询结果的执行

4.2 不带查询结果的执行

4.3 关于 ResultSet 读取列

5. 实践应用:博客系统

发表文章

删除文章ByBid

批量删除文章ByBid


1. 介绍

什么是 JDBC 呢?

JDBC 代表 Java 数据库连接(JavaDatabaseConnectivity),它是用于 Java 编程语言和数据库之间的数据库无关连接的标准 Java API。

我们写的所有程序的代码来自:官方提供(JDK)、我们写的(App)。

为了减少工作量,引入一些别人(不是官方和我们)写的代码,一般把这类代码称为第三方。以库(library lib)。

在 Java 中,一般是以一组类文件(*.class)提供。把这组类文件打出一个文件(采用 zip 压缩格式,Java 称为 Jar 包:Java ARchive)。

所以,我们要使用别人的第三方库,需要:

  1. 拿到一个 jar 包

  2. 配置工程(IDEA)让工程在编写、编译、运行阶段,都可以找到第三方库中的类

我们要使用一个 MySQL 官方提供的,进行 SQL 查询的代码库 "mysql-connector-java-5.1.47.jar"

描述:我们的应用 依赖于(depends on)"mysql-connector-java-5.1.47.jar"

为了处理好这些依赖关系,引入 Java 构建阶段的工具:maven/gradle。

这类工具,为了定位每个库,定义了一个坐标(coordinate)的概念。

这个库属于哪个组织(公司):groupId

这个库自己的专有名字:artifactId

这个库的当前版本:version

groupId + artifactId + version 唯一确定一个库。

pom.xml 关于 Maven 工具的配置文件,我们的依赖关系要在这里写清楚。

POM 的主体结构:

在新建一个 maven 项目后,我们需要添加依赖,使用以下代码即可。

<properties><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><encoding>utf-8</encoding>
</properties><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency>
</dependencies>

如下图所示:

2. 使用 JDBC 连接数据库

2.1 如何使用 JDBC 连接数据库

  • DataSource(数据源)

  • Connection(连接)

  • Statement(SQL 语句)/ PreparedStatement

  • ResultSet(结果集)

对比打电话的例子:

DataSource 类比 联系方式 or 通讯录

Connection 类比打通电话,需要 DataSource 作为前提

Statement:客户向服务器发送的 SQL 语句

ResultSet:服务器向客户回的结果

2.2 导入的各个类

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

2.3 DataSource 对象的创建

DataSource 只是 JDBC 提供的一个接口,要创建的是 MysqlDataSource 对象。

数据库的相关信息         

1. 数据库所在的主机:127.0.0.1 / localhost 

2. 数据库所在的端口:3306 / 3307 ... 

3. 数据库的用户名: ... 

4. 数据库的密码: ...

5. 额外的配置选项

        5.1. 连接使用的字符集编码 characterEncoding=utf8  

        5.2. 不使用加密连接: useSSL=false  

        5.3. 指定时区: serverTimezone=Asia/Shanghai

第一种创建方式:

MysqlDataSource dataSource = new MysqlDataSource();dataSource.setServerName("127.0.0.1");
dataSource.setPort(3306);
dataSource.setUser("debian-sys-maint");
dataSource.setPassword("*********");
dataSource.setDatabaseName("learn");
dataSource.setCharacterEncoding("utf8");
dataSource.setUseSSL(false);
dataSource.setServerTimezone("Asia/Shanghai");

第二种创建方式:

MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUser("debian-sys-maint");
dataSource.setPassword("*********");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/learn?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai");

2.4 从 DataSource 对象中得到 Connection 对象

// try-with-resource
// 利用这种写法,不用自己写 con.close(),代码结构看起来干净
try (Connection con = dataSource.getConnection()) {// 利用 con 对象执行 SQL
} catch (SQLException exc) {// 处理异常
}

2.5 创建 Statement 对象

// -- 列出当前所在库的所有表名称
String sql = "show tables";try (PreparedStatement ps = con.prepareStatement(sql)) {// execute: 执行// query: 查询try (ResultSet rs = ps.executeQuery()) {// 结果就可以从 ResultSet 对象中获取}
}

2.6 从 ResultSet 中遍历每行结果,从每行中获取每列的值

// hasNext() + next()
while (rs.next()) {// rs 代表当前遍历的行// 第一列的结果是表的名称,所以是字符串 StringString tableName = rs.getString(1);    // 下标是 1System.out.println(tableName);
}

2.7 代码汇总

package com.peixinchen;import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class Main {public static void main(String[] args) throws SQLException {MysqlDataSource dataSource = new MysqlDataSource();dataSource.setUser("debian-sys-maint");dataSource.setPassword("***************");dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/learn?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai");// try-with-resource// 利用这种写法,不用自己写 con.close(),代码结构看起来干净try (Connection con = dataSource.getConnection()) {// 利用 con 对象执行 SQL// -- 列出当前所在库的所有表名称String sql = "show tables"; // 只有 1 列try (PreparedStatement ps = con.prepareStatement(sql)) {try (ResultSet rs = ps.executeQuery()) {// 结果就可以从 ResultSet 对象中获取// hasNext() + next()while (rs.next()) {// rs 代表当前遍历的行// 第一列的结果是表的名称,所以是字符串 StringString tableName = rs.getString(1);    // 下标是 1System.out.println(tableName);}}}}}
}

进程的执行结果 = 程序代码 + 进程执行时的环境

同样的代码,不同的环境是可能得到不同的结果的,所以如果遇到了运行结果不符合预期,除了检查代码的问题之外,现在应该把更多精力放到检查周边环境是否符合预期上了。

3. PrepareStatement 详解

3.1 动态 SQL 执行

// 静态 SQL
String sql = "show tables";
String sql = "select * from oj_records where oj_id = 1"

我们在之前使用的都是静态的 SQL ,但是在实际的应用中,大多都是动态的 SQL ,比如用户登陆界面都是等待用户输入数据,并不是固定的数据。因此,我们来了解一下动态 SQL。

PreparedStatement 通过占位符(placeholder)和动态绑定的方式做到。

// ? 作为占位符,先把位置占住,等待用户输入
String sql = "select * from users where username = ? and password = ?";PreparedStatement ps = conn.preparedStatement(sql);String username = scanner.nextLine();
String password = scanner.nextLine();// 用实际得到的用户名和密码替换占位符 —— 动态绑定
ps.setString(1, username);    // 用用户名替换第一个 ?
ps.setString(2, password);    // 用密码替换第二个 ?

接下来,我们运行以下完整的代码(密码和库更改为自己的即可):

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;public class PreparedStatementDemo {public static void main(String[] args) throws SQLException{Scanner scanner = new Scanner(System.in);System.out.println("请输入要查询的难度: ");String difficulty = scanner.nextLine();MysqlDataSource dataSource = new MysqlDataSource();dataSource.setUser("debian-sys-maint");dataSource.setPassword("*****************");dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/learn?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai");try (Connection con = dataSource.getConnection()) {// 利用 con 对象执行 SQL,建立连接String sql = "select * from oj_records where difficulty = ?";// 动态 SQLtry (PreparedStatement ps = con.prepareStatement(sql)) {ps.setString(1, difficulty);System.out.println(ps);try (ResultSet rs = ps.executeQuery()) {// 结果就可以从 ResultSet 对象中获取// hasNext() + next()while (rs.next()) {// rs 代表当前遍历的行// 第一列的结果是表的名称,所以是字符串 StringString id = rs.getString(1);    // 下标是 1System.out.println(id);}}}}}
}

3.2 SQL 类型

// 针对 PreparedStatement 执行动态绑定时
int x = ...;
ps.setInt(1, x);String s = "...";
ps.setString(1, s);String datetime = "2023-04-24 20:54:00"
ps.setString(1, datetime);// 从 ResultSet 获取值时
int x = rs.getInt(1);
String s = rs.getString(1);
String datetime = rs.getString(1);
Java 代码类型MySQL 类型举例

int

setInt(..)

getInt(..)

int

ps.getInt(1,x);

x = rs.getInt(1);

String

varchar(..)

char(..)

text

longtext

String

datetime

date

time

3.3 SQL 注入(Inject)

什么是 SQL 注入呢?

SQL注入是一种非常常见的数据库攻击手段,SQL注入漏洞也是网络世界中最普遍的漏洞之一。大家也许都听过某某学长通过攻击学校数据库修改自己成绩的事情,这些学长们一般用的就是SQL注入方法。

SQL注入其实就是恶意用户通过在表单中填写包含SQL关键字的数据来使数据库执行非常规代码的过程。,SQL数据库的操作是通过SQL语句来执行的,而无论是执行代码还是数据项都必须写在SQL语句之中,这就导致如果我们在数据项中加入了某些SQL语句关键字(比如说SELECT、DROP等等),这些关键字就很可能在数据库写入或读取数据时得到执行。

我们来从代码角度看看是如何进行 SQL 注入的。

String username = scanner.nextLine();String sql = "select * from users where username = '%s'";
sql = String.format(sql, username);// 执行

当用户输入用户名是类型 " ' or 1 = 1 or 1 = ' "

String sql = "select * from users where username = '' or 1 = 1 or 1 = ''";

由于 name = ' ' 为假,1 = 1 为真,1 = ' ' 为假,因此 where 后面的语句最终执行结果为真,因此,会将数据库中的所有信息显示出来,从而导致数据泄露。

4. 关于 SQL 的执行

4.1 带查询结果的执行

select ...;
-- 查询..
show ...;
-- 显示..
ResultSet rs = ps.executeQuery();

4.2 不带查询结果的执行

create database ...;
-- 创建库
create table ...;
-- 创建表
drop database ...;
-- 删除库
drop table ...;
-- 删除表
insert into ...;
-- 增加数据
update ...;
-- 更新数据
delete ...;
-- 删除数据// number 本次执行成功多少行
int number = ps.executeUpdate();

4.3 关于 ResultSet 读取列

1)读取第 n 列
rs.getInt(n);    // 读取第 n 列2) 根据列名称读取
rs.getInt("oj_id");

5. 实践应用:博客系统

只有一张表          博客文章(blogs)0) bid    int   PK  AI
1) author varchar(30)  NN         作者
2) published_at datetime NN       发表时间
3) content text NN                正文 create table blogs (bid int primary key auto_increment,author varchar(20) not null comment '作者',title varchar(100) not null comment '标题',published_at datetime not null comment '发表时间',content text not null comment '正文'
);
-- 新增文章
insert into blogs (author, title, published_at, content) values (?, ?, ?, ?);-- 删除一篇文章-- by bid
delete from blogs where bid = ?;-- 批量 by bid
delete from blogs where bid in (...);-- 根据标题(只能删自己的)
delete from blogs where author = ? and title = ?;-- 给出文章列表(bid/作者/发表时间/标题)
-- 根据发表从新到旧
select bid, author, published_at, title from blogs order by published_at desc;
-- 增加分页(每页共 3 篇),需要第 x 页
select bid, author, published_at, title from blogs order by published_at desc limit 3 offset 3 * (x - 1)
x == 1 : offset 0
x == 2 : offset 3
x == 3 : offset 6

创建一个 DBUtil 类用来存放配置信息:

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;// Util: 工具
public class DBUtil {private static final DataSource dataSource;static {MysqlDataSource mysqlDataSource = new MysqlDataSource();mysqlDataSource.setUser("debian-sys-maint");mysqlDataSource.setPassword("***********");mysqlDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/learn?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai");dataSource = mysqlDataSource;}public static Connection connection() throws SQLException {return dataSource.getConnection();}
}

发表文章

接下来实现发表文章的功能:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Scanner;public class PublishArticle {public static void main(String[] args) throws SQLException {Scanner sc = new Scanner(System.in);System.out.println("请输入用户名:");String author = sc.nextLine();System.out.println("请输入文章标题:");String title = sc.nextLine();System.out.println("请输入正文内容:");String content = sc.nextLine();// 获取当前时间ZoneId zone = ZoneId.of("Asia/Shanghai");LocalDateTime now = LocalDateTime.now(zone);DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String publishedAt = now.format(formatter);String sql = "insert into blogs (author, title, published_at, content) values (?, ?, ?, ?)";try (Connection c = DBUtil.connection()) {try (PreparedStatement ps = c.prepareStatement(sql)) {// 1. 进行动态绑定ps.setString(1, author);ps.setString(2, title);ps.setString(3, publishedAt);ps.setString(4, content);// 此处是不带结果集的查询System.out.println(ps);ps.executeUpdate();System.out.println("文章发表成功");}}}
}

删除文章ByBid

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;public class DeleteByBid {public static void main(String[] args) throws SQLException {Scanner scanner = new Scanner(System.in);System.out.print("请输入要删除的 bid: ");int bid = scanner.nextInt();String sql = "delete from blogs where bid = ?";try (Connection c = DBUtil.connection()) {try (PreparedStatement ps = c.prepareStatement(sql)) {ps.setInt(1, bid);System.out.println(ps);ps.executeUpdate();System.out.println("删除成功");}}}
}

批量删除文章ByBid

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;public class BulkDeleteByBid {public static void main(String[] args) throws SQLException {Scanner scanner = new Scanner(System.in);List<Integer> bidList = new ArrayList<>();System.out.print("请输入要删除的 bid: ");while (scanner.hasNextInt()) {int bid = scanner.nextInt();bidList.add(bid);System.out.print("请输入要删除的 bid: ");}System.out.println("DEBUG: 要删除的 bid 列表为: " + bidList);// bidList -> "1, 3, 7// List<Integer> -> List<String>// String.join(", ", list of string)// Stream 流式写法List<String> bidListString = bidList.stream().map(i -> String.valueOf(i)).collect(Collectors.toList());String inClause = String.join(", ", bidListString);String sql = String.format("delete from blogs where bid in (%s)", inClause);try (Connection c = DBUtil.connection()) {try (PreparedStatement ps = c.prepareStatement(sql)) {System.out.println(ps);ps.executeUpdate();System.out.println("批量删除成功");}}}
}

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

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

相关文章

EIK+Filebeat+Kafka

目录 Kafka 概述 为什么需要消息队列&#xff08;MQ&#xff09; 使用消息队列的好处 消息队列的两种模式 Kafka 定义 Kafka 简介 Kafka 的特性 Kafka 系统架构 Partation 数据路由规则&#xff1a; 分区的原因 部署 kafka 集群 1.下载安装包 2.安装 Kafka 修改配…

B072-项目实战-用户模块--前台登录 三方登录

目录 前台登录-账号登录前端完成左上角显示用户信息配置前置拦截器、后置拦截器和不受限资源拦截器 三方登录-微信登录概述流程图用法代码实现步骤分析:实现准备代码前端login.htmlcallback.html 后端LoginController-微信登录LoginServiceImpl-微信登录解决回调域名不能跨域绑…

安达发|某大厂使用APS计划排程真实成功案例

在很多群里、朋友圈、公众号上可以看到&#xff0c;很多精益咨询老师认为&#xff0c;不仅ERP不啥用&#xff0c;APS更是无聊之举&#xff0c;而且肯定是用不好的。但&#xff0c;事实上可能还真不是这样的。 一个深圳的客户&#xff0c;用了APS以后&#xff0c;不仅装配的齐套…

桥梁监测需要哪些设备?

随着我国经济的发展&#xff0c;我国桥梁建设也迈上了新的台阶。截至2022年底&#xff0c;我国的公路桥梁总数达到了103.32万座。然而&#xff0c;随着在役桥梁使用时间的增长&#xff0c;承载能力受到荷载、环境以及结构退化等因素的影响&#xff0c;桥梁安全问题日益凸显。桥…

vue3和gin框架实现简单的断点续传

vue3和gin框架实现简单的断点续传 前端代码 Test.vue <template><div><inputtype"file"ref"uploadRef"change"upload"multiple/><templatev-for"item in fileList":key"item.key"><br><…

spring复习:(39)注解方式的ProxyFactoryBean

一、定义接口 package cn.edu.tju.study.service;public interface MyService {void myMethod(); }二、定义实现类&#xff1a; package cn.edu.tju.study.service;public class MyServiceImpl implements MyService{Overridepublic void myMethod() {System.out.println(&qu…

Redis 读写分离 使用redisTemplate执行lua脚本时,报错处理

项目框架说明 项目配置 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.4</version></parent>....<dependency><groupId>org.springfra…

(学习笔记-TCP基础知识)TCP与UDP区别

UDP UDP不提供复杂的控制机制&#xff0c;利用IP提供面向[无连接]的通信服务。 UDP协议非常简单&#xff0c;头部只有8个字节(位)&#xff0c;UDP的头部格式如下&#xff1a; 目标和源端口&#xff1a;主要是告诉UDP协议应该把报文发给哪个进程包长度&#xff1a;该字段保存了…

TinyKv流程梳理三

split流程 处理协程启动 func (bs *Raftstore) startWorkers(peers []*peer) {ctx : bs.ctxworkers : bs.workersrouter : bs.routerbs.wg.Add(2) // raftWorker, storeWorkerrw : newRaftWorker(ctx, router)go rw.run(bs.closeCh, bs.wg)sw : newStoreWorker(ctx, bs.store…

基于Web API drap事件的简单拖拽功能

基于Web API drap事件的简单拖拽功能 效果示例图代码示例 效果示例图 代码示例 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style type"text/css">* {padding: 0px;margin: 0px;box-s…

uniapp动态获取列表中每个下标的高度赋值给另一个数组(完整代码附效果图)

uniapp实现动态获取列表中每个下标的高度&#xff0c;赋值给另一个数组。 先看效果图&#xff1a; 完整代码&#xff1a; <template><div class""><div class"">我是A列表&#xff0c;我的高度不是固定的</div><div class&qu…

MySQL 坐标批量计算及优化

文章目录 1、坐标计算2、优化 现在有一个需求&#xff0c;就是找出距离某用户最近的一些点&#xff0c;一种实现方法就是调用地图的api来计算筛选&#xff0c;另外一种就是在数据库中计算&#xff0c;考虑到地图api有并发量限制&#xff0c;所以选用数据库计算的方式。 1、坐标…

ThunderScope开源示波器

简介 4CH&#xff0c;1GSa/S 开源示波器。前端很简洁&#xff0c;BUF802LMH6518&#xff0c;ADC是HMCAD1511&#xff0c;用Xilinx A7 FPGA进行控制&#xff0c;数据通过PCIE总线传输到上位机处理。目前这个项目已经被挂到了Xilinx官网&#xff0c;强。 设计日志&#xff1a;h…

Unity自定义后处理——Vignette暗角

大家好&#xff0c;我是阿赵。   继续说一下屏幕后处理的做法&#xff0c;这一期讲的是Vignette暗角效果。 一、Vignette效果介绍 Vignette暗角的效果可以给画面提供一个氛围&#xff0c;或者模拟一些特殊的效果。 还是拿这个角色作为底图 添加了Vignette效果后&#xff0…

软件测试银行项目面试过程

今天参加了一场比较正式的面试&#xff0c;汇丰银行的视频面试。在这里把面试的流程记录一下&#xff0c;结果还不确定&#xff0c;但是面试也是自我学习和成长的过程&#xff0c;所以记录下来大家也可以互相探讨一下。 请你做一下自我介绍&#xff1f;&#xff08;汇丰要求英…

第108天:免杀对抗-Python混淆算法反序列化打包生成器Py2exeNuitka

知识点 #知识点&#xff1a; 1、Python-对执行代码做文章 2、Python-对shellcode做文章 3、Python-对代码打包器做文章#章节点&#xff1a; 编译代码面-ShellCode-混淆 编译代码面-编辑执行器-编写 编译代码面-分离加载器-编写 程序文件面-特征码定位-修改 程序文件面-加壳花指…

webpack打包之 copy-webpack-plugin

copy-webpack-plugin 打包复制文件插件。 1、什么时候要使用&#xff1f; 在离线应用中&#xff0c;前端所有文件都需在在本地&#xff0c;有些文件&#xff08;比如iconFont以及一些静态img)需要转为离线文件&#xff0c;这些文件可以直接引用更方便些&#xff0c;这就需要在打…

6.6Jmeter远程调度Linux机器Jmeter测试

1、配置Agent和启动 1.1、打开jmeter/bin目录下的jmeter.properties 1、server_port1099取消注释 2、remote_hosts127.0.0.1 改为remote_hosts127.0.0.1:1099 或者是remote_hostsAgent机的ip:1099 3、server.rmi.localport1099 4、server.rmi.ssl.disablefalse改为true&#x…

基于springboot+Redis的前后端分离项目之分布式锁(四)-【黑马点评】

&#x1f381;&#x1f381;资源文件分享 链接&#xff1a;https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwdeh11 提取码&#xff1a;eh11 分布式锁 分布式锁1 、基本原理和实现方式对比2 、Redis分布式锁的实现核心思路3 、实现分布式锁版本一4 、Redis分布式锁误删情况…

基于SpringBoot+Vue+微信小程序的电影平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 研究背景&#xff1a;…