让我们仔细看看是怎么访问数据库的

package sql;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException; public class Conn { // 创建类Conn
Connection con; // 声明Connection对象
public static String user;
public static String password;
public Connection getConnection() { // 建立返回值为Connection的方法
try { // 加载数据库驱动类
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("数据库驱动加载成功");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
user = "root";//数据库登录名
password = "root";//密码
try { // 通过访问数据库的URL获取数据库连接对象
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=gbk", user, password);
System.out.println("数据库连接成功");
} catch (SQLException e) {
e.printStackTrace();
}
return con; // 按方法要求返回一个Connection对象
}
public static void main(String[] args) { // 主方法,测试连接
Conn c = new Conn(); // 创建本类对象
c.getConnection(); // 调用连接数据库的方法
}
}

具体用法

我们直接看下列的代码

package Main;

import java.sql.*;

public class JDBC {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
// 1.加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.用户信息和url
String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=true";
String username="root";
String password="root";
// 3.连接成功,数据库对象 Connection
Connection connection = DriverManager.getConnection(url,username,password);
// 4.执行SQL对象Statement,执行SQL的对象
Statement statement = connection.createStatement();
// 5.执行SQL的对象去执行SQL,返回结果集
String sql = "SELECT *FROM studentinfo;";
ResultSet resultSet = statement.executeQuery(sql);
while(resultSet.next()){
System.out.println("SNo="+resultSet.getString("SNo"));
System.out.println("SName="+resultSet.getString("SName"));
System.out.println("Birth="+resultSet.getString("Birth"));
System.out.println("SPNo="+resultSet.getString("SPNo"));
System.out.println("Major="+resultSet.getString("Major"));
System.out.println("Grade="+resultSet.getString("Grade"));
System.out.println("SInstructor="+resultSet.getString("SInstructor"));
System.out.println("SPwd="+resultSet.getString("SPwd"));
}
// 6.释放连接
resultSet.close();
statement.close();
connection.close();
}
}

加载数据库类

Class.forName("com.mysql.cj.jdbc.Driver");是用来加载数据库驱动的。用于我们的 Java 程序与数据库通信。

Class.forName()函数的作用是用于动态加载一个类,其中的参数自然也是数据库的驱动类com.mysql.cj.jdbc.Driver

连接数据库

我们通常使用DriverManager.getConnection(url,username,passwd)方法来连接数据库,这里面需要我们填入三个参数:

  • url:数据库的URL,格式很重要

    • "jdbc:mysql":这是告诉程序使用 JDBC 驱动来连接 MySQL 数据库。

    • "localhost":指向本地计算机的数据库服务器。

    • "3306":MySQL 服务的默认端口号。

    • "test1":这是要访问的数据库名称。

    • ?useUnicode=true&characterEncoding=gbk:这些是查询参数,用于指定数据库的配置。它们表示:

    • useUnicode=true:启用 Unicode 支持,确保可以存储和读取 Unicode 字符。

    • characterEncoding=gbk:设置字符编码为 GBK,通常用于处理中文字符。

  • username:数据库的用户名

  • passwd :数据库的密码

实例化一个SQL对象Statement

这没啥好说的,就是实例化一个对象,以便于我们能调用其中的各种方法

执行SQL语句,查询数据库

查询

要执行数据库的查询我们直接使用executeQuery(String sql)方法,然后里面写入我们的sql语句就行,之后我们就能从返回值得到查询的结果了ResultSet resultSet = statement.executeQuery(sql);

结果

得到了一个ResultSet结果对象之后,想要的得到字符串直接使用getString()方法就行,除此之外还有getLong()getInt()等,就对应了其中的数据类型。

然后就是这些get方法的参数,有两种参数:

  • 列名称:就是你查询出来后,直接输入列表的名称就给你输出了查询的结果,名称的类型自然就是String类型的
  • 列索引:直接从中输入列的索引就会输入第几列的查询结果,但是注意!!!!这里的所以不再是从0开始,而是从1开始,这是非常值得注意的地方,所以的参数类型自然就是int了

释放链接

为了资源不要浪费,使用完了就应该直接释放了

        resultSet.close();
statement.close();
connection.close();

防止sql注入的改良

发现问题

又细心的人就会发现前面的代码使用Statement拼字符串非常容易引发SQL注入的问题。

什么是SQL注入呢?我们一般查询数据库靠着相对的命令来实现,如果SQL语句是靠要查的字符拼接出来的,一般是没有问题的,但是我们输入一些特定的字符的时候是可能会让sql语句去做其他的事情。总之SQL一般都是因为字符的拼接漏洞实现的。

解决问题

所以我们就得想办法去解决这个问题,有一个方法是转义特定的字符,但这终究是治标不治本的。

前面我们提到,最根本的问题是字符拼接带来的漏洞,如果我们不进行字符拼接,直接传递要查的字符,那问题就引刃而解了

Statement 换成 PreparedStatement可以完全避免SQL注入的问题,因为PreparedStatement始终使用?作为占位符,并且把数据连同SQL本身传给数据库,这样可以保证每次传给数据库的SQL语句是相同的,只是占位符的数据不同,还能高效利用数据库本身对查询的缓存。

//使用prepareStatement查询
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
try (PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?")) {
ps.setObject(1, "M"); // 注意:索引从1开始
ps.setObject(2, 3);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
long id = rs.getLong("id");
long grade = rs.getLong("grade");
String name = rs.getString("name");
String gender = rs.getString("gender");
}
}
}
}
//使用Statement查询
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT id, grade, name, gender FROM students WHERE gender=1")) {
while (rs.next()) {
long id = rs.getLong(1); // 注意:索引从1开始
long grade = rs.getLong(2);
String name = rs.getString(3);
int gender = rs.getInt(4);
}
}
}
}

我们来看看这个例子,上面的是使用的prepareStatement,下面使用的是Statement。虽然两者之间没有太大的区别,但是还有值得我们注意的地方:

  • sql语句插入的函数不同,前者是在prepareStatement就已经插入,而后者是在executeQuery才插入
  • prepareStatement是需要使用setObject方法来指定我们查询的字符的,但是Statement是不用的,至于为什么不行我们后文再说
  • ResultSetnext() 方法用于 移动游标结果集中的下一行,返回的数据类型看代码无疑是一个 boolean

解决完问题带来的思考

我们从代码层面分析完成之后,我想很多人跟我有一样的提问,prepareStatement为什么能够做到防止SQL的注入,这里我们在稍微升入SQL注入一点,在详细剖析SQL注入是怎么完成的。

SQL注入的本质

SQL注入漏洞出现的原因就是用户的输入会直接嵌入到查询语句中,一旦出现精心设计的输入就会改变整个SQL语句的结构

比如现在有这样的语句

String query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";

如果输入了恶意的内容 username = "admin' --"

SELECT * FROM users WHERE username = 'admin' --' AND password = 'password';

后面输入的AND password = 'password';就直接被注释掉了,这样就会只查询前面的username = 'admin'

prepareStatement的防御原理

前面不是说了就是因为用户输入和查询语句不是分离的吗,那思路就很简单了,那将两者分离不就行了

占位符分离

在预编译阶段,SQL 查询的结构被解析并发送到数据库中,这时 占位符?)会被数据库视为参数的占位符,而不是 SQL 语句的一部分。

例如:

String query = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement ps = conn.prepareStatement(query);
ps.setString(1, username);
ps.setString(2, password);

在这里:

  • ? 是占位符,它在查询执行时被替换为实际的参数。
  • ps.setString(1, username)ps.setString(2, password) 将用户输入的值安全地绑定到查询中。
  • 在执行查询时,数据库知道 ? 只是占位符,它不会将用户输入作为 SQL 代码的一部分来解析,而是将其作为数据处理。

即使用户输入恶意的内容,例如:

  • username = "admin' OR 1=1 --"
  • password = "password"

构造的 SQL 查询也不会发生注入,因为数据库会将这些输入当作普通的字符串处理,而不会将其作为 SQL 语句的一部分。执行时的 SQL 查询将是:

SELECT * FROM users WHERE username = 'admin'' OR 1=1 --' AND password = 'password'

这个查询在数据库端仍然会被正确地作为两条字符串值传递,而不会被解析为恶意的 SQL 代码

自动转译用户输入

PreparedStatement 会自动转义参数中的特殊字符,如单引号(')等,使其在数据库中正确地作为字符串处理。这进一步防止了 SQL 注入攻击。

例如,如果用户输入的用户名是:

  • admin' --

PreparedStatement 会自动将这个字符串转义为:

  • 'admin'' --'

这样,即使用户输入恶意的内容,数据库也会将其作为普通字符串处理,而不会被当作 SQL 语句的一部分执行。

JDBC中数据库的连接与查询的更多相关文章

  1. Java对MySQL数据库进行连接、查询和修改(转)

    Java对MySQL数据库进行连接.查询和修改 0. 一般过程: (1) 调用Class.forName()方法加载驱动程序. (2) 调用DriverManager对象的getConnection( ...

  2. python中的MySQL数据库操作 连接 插入 查询 更新 操作

    MySQL数据库 就数据库而言,连接之后就要对其操作.但是,目前那个名字叫做qiwsirtest的数据仅仅是空架子,没有什么可操作的,要操作它,就必须在里面建立“表”,什么是数据库的表呢?下面摘抄自维 ...

  3. JDBC开发,数据库的连接

    JDBC开发步骤 1.建完工程后,右击工程,new,新建一个文件夹Folder,装jar包,        2.将下载好的驱动包打开,找到jar文件,CTRL+C,选中装jar包的文件夹,CTRL+V ...

  4. php中数据库服务器连接类库文件的编写

    <!--数据库服务器连接类库文件的编写--> <?php class mysql{ //连接服务器.数据库以及执行Sql语句的类库 public $database; public ...

  5. jdbc创建数据库的连接

    package com.gylhaut.db; import java.sql.Connection;import java.sql.DriverManager;import java.sql.Res ...

  6. Java对MySQL数据库进行连接、查询和修改【转载】

    一般过程: (1) 调用Class.forName()方法加载驱动程序. (2) 调用DriverManager对象的getConnection()方法,获得一个Connection对象. (3) 创 ...

  7. Linq与数据库的连接显示查询(一)

    使用linq查询sql数据库是首先需要创建一个 linq  to  sql 类文件 创建linq  to  sql的步骤: 1在Visual  Studio 2015开发环境中建立一个目标框架 Fra ...

  8. SSM框架中数据库无法连接的问题

    首先是SSM框架中所有的配置都是没有问题的,而且项目在其他人的环境上也能正常访问数据库:那么最有可能的就是数据库版本的问题导致数据库连接不上,服务器给我的报错是: 15:37:25.902 [C3P0 ...

  9. PHP中数据库的连接

    <?php //1.链接MySQL服务器 $conn = mysql_connect("localhost", "root" , 199452); //2 ...

  10. springboot中数据库的连接

    mysql5.0 1.#mysql数据库连接 2.spring.datasource.driver-class-name=com.mysql.jdbc.Driver   3.spring.dataso ...

随机推荐

  1. 聊一聊SQL优化

    晚上睡不着,脑子里总想着一些问题,试着写一写对于SQL查询优化的见解. 首先,数据库有自己的查询优化器,执行一条查询SQL优化器会选择最优的方式(不走索引.走索引.走哪个索引), 所以索引不是越多越好 ...

  2. MySQL中的char与varchar

    MySQL中的char与varchar char类型为固定长度的字符串 varchar类型是长度可变的字符串 char为固定长度的字符串意思是当我们设置一个字段类型为char时,指定char(100) ...

  3. 线性dp:最长公共子序列

    最长公共子序列 本文讲解的题与leetcode1143.最长公共子序列这题一样,阅读完可以挑战一下. 力扣题目链接 题目叙述: 给定两个字符串,输出其最长公共子序列,并输出它的长度 输入: ADABE ...

  4. js_问题记录2022年6月24日19:35:12

    小问题中的大问题 新建子js脚本一定记得创建函数,不然写什么都无法实现 比如 新建了new1.js脚本,首先需要创建function后才能在里面进行参数操作和调用 js实现的功能: 获取到对应的id= ...

  5. Mac 打开软件提示‘“xxx”已损坏,无法打开。您应该将它移到废纸篓。’解决方法

    产生错误的原因是软件没有签名.使用下面的命令给软件签名就好了. sudo xattr -rd com.apple.quarantine /Applications/xxx.app

  6. 使用 nuxi build-module 命令构建 Nuxt 模块

    title: 使用 nuxi build-module 命令构建 Nuxt 模块 date: 2024/8/31 updated: 2024/8/31 author: cmdragon excerpt ...

  7. HTML & CSS – dir, direction, writing-mode, ltr (left to rigth), rtl (right to left)

    前言 世界上有很多语言的阅读方向是不同的. 英文 中文 (以前才有竖排文字, 现在中文和英语一样了) 阿拉伯文 (Arabic) 面对不同的语言, HTML 和 CSS 就需要不同的写法. 虽然我没有 ...

  8. CSS – z-index

    介绍 z-index 是用来设置 element 层次高低的 (当 element 重叠的时候) 参考: 4 reasons your z-index isn't working (and how t ...

  9. QT原理与源码分析之QT反射机制原理

    QT反射机制原理 本文将介绍QT反射机制创建QT对象实例的原理和流程以及源代码. 文章目录 QT反射机制创建QT对象实例 原理 流程 源码 QT反射机制创建QT对象实例 QT框架提供的基于元对象的反射 ...

  10. 一条 SQL 语句在 MySQL 中是如何执行的?

    本篇文章会分析下一个 SQL 语句在 MySQL 中的执行流程,包括 SQL 的查询在 MySQL 内部会怎么流转,SQL 语句的更新是怎么完成的. 在分析之前我会先带着你看看 MySQL 的基础架构 ...