JDBC-

news/2024/4/26 5:19:19/文章来源:https://blog.csdn.net/hza419763578/article/details/129201338

文章目录

    • JDBC
    • 1,JDBC概述
      • 1.1 JDBC概念
      • 1.2 JDBC本质
      • 1.3 JDBC好处
    • 2,JDBC快速入门
      • 2.1 编写代码步骤
      • 2.2 具体操作
    • 3,JDBC API详解
      • 3.1 DriverManager
      • 3.2 Connection (事务归我管)
        • 3.2.1 获取执行对象
        • 3.2.2 事务管理
      • 3.3 Statement
        • 3.3.1 概述
        • 3.3.2 代码实现
      • 3.4 ResultSet
        • 3.4.1 概述
        • 3.4.2 代码实现
      • 3.5 案例
      • 3.6 PreparedStatement
        • 3.6.1 SQL注入
        • 3.6.2 代码模拟SQL注入问题
        • 3.6.3 PreparedStatement概述
        • 3.6.4 使用PreparedStatement改进
        • 3.6.5 PreparedStatement原理
    • 4,数据库连接池

JDBC

今日目标

  • 掌握JDBC的的CRUD
  • 理解JDBC中各个对象的作用
  • 掌握Druid的使用

1,JDBC概述

在开发中我们使用的是java语言,那么势必要通过java语言操作数据库中的数据。这就是接下来要学习的JDBC。

1.1 JDBC概念

JDBC 就是使用Java语言操作关系型数据库的一套API

全称:( Java DataBase Connectivity ) Java 数据库连接

在这里插入图片描述

我们开发的同一套Java代码是无法操作不同的关系型数据库,因为每一个关系型数据库的底层实现细节都不一样。如果这样,问题就很大了,在公司中可以在开发阶段使用的是MySQL数据库,而上线时公司最终选用oracle数据库,我们就需要对代码进行大批量修改,这显然并不是我们想看到的。我们要做到的是同一套Java代码操作不同的关系型数据库,而此时sun公司就指定了一套标准接口(JDBC),JDBC中定义了所有操作关系型数据库的规则。众所周知接口是无法直接使用的,我们需要使用接口的实现类,而这套实现类(称之为:驱动)就由各自的数据库厂商给出。

1.2 JDBC本质

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

1.3 JDBC好处

  • 各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发
  • 可随时替换底层数据库,访问数据库的Java代码基本不变

以后编写操作数据库的代码只需要面向JDBC(接口),操作哪儿个关系型数据库就需要导入该数据库的驱动包,如需要操作MySQL数据库,就需要再项目中导入MySQL数据库的驱动包。如下图就是MySQL驱动包
所谓驱动,就是各厂商对jdbc这套接口的实现类

在这里插入图片描述

2,JDBC快速入门

先来看看通过Java操作数据库的流程

在这里插入图片描述

第一步:编写Java代码

第二步:Java代码将SQL发送到MySQL服务端

第三步:MySQL服务端接收到SQL语句并执行该SQL语句

第四步:将SQL语句执行的结果返回给Java代码

2.1 编写代码步骤

  • 创建工程,导入驱动jar包

在这里插入图片描述

  • 注册驱动

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

    获取字节码,其实还有一个天然的作用,就是将类加载进内存

  • 获取连接

    Connection conn = DriverManager.getConnection(url, username, password);
    

    Java代码需要发送SQL给MySQL服务端,就需要先建立连接

  • 定义SQL语句

    String sql =update…” ;
    
  • 获取执行SQL对象

    执行SQL语句需要SQL执行对象,而这个执行对象就是Statement对象

    Statement stmt = conn.createStatement();
    
  • 执行SQL

    stmt.executeUpdate(sql);  
    
  • 处理返回结果

  • 释放资源

2.2 具体操作

  • 创建新的空的项目。定义项目的名称,并指定位置

在这里插入图片描述

  • 对项目进行设置,JDK版本、编译版本


可以装多个jdk,然后选择哪个即可

  • 创建模块,指定模块的名称及位置

在这里插入图片描述

  • 导入驱动包

    将mysql的驱动包放在模块下的lib目录(随意命名)下,并将该jar包添加为库文件
    lib目录和src同级别 (不然也创建不了目录,只能创建包)

在这里插入图片描述

  • 在添加为库文件的时候,有如下三个选项
    • Global Library : 全局有效
    • Project Library : 项目有效
    • Module Library : 模块有效

在这里插入图片描述
改用全局有效试试

  • 在src下创建类
    在这里插入图片描述

  • 编写代码如下

/*** JDBC快速入门** 注意 驱动包 我导入成全局的了*/
public class JDBCDemo {public static void main(String[] args) throws Exception {//1. 注册驱动//Class.forName("com.mysql.jdbc.Driver");//将类加载到内存中//mysql5之后 可以省略注册这一步了 自动注册//2. 获取连接String url = "jdbc:mysql://127.0.0.1:3306/db1";String user = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, user, password);//3. 定义sqlString sql = "update account set money = 2000 where id = 1";//4. 获取执行sql的对象 StatementStatement st = conn.createStatement();//5. 执行sql 修改语句对应的方法int n = st.executeUpdate(sql); //返回受影响的行数//6. 处理结果 (此处就打印下行数了)System.out.println(n);//7. 释放资源 (先开后放)st.close();conn.close();}
}

3,JDBC API详解

3.1 DriverManager

DriverManager(驱动管理类)作用:

  • 注册驱动

    在这里插入图片描述

    registerDriver方法是用于注册驱动的,但是我们之前做的入门案例并不是这样写的。而是如下实现

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

    我们查询MySQL提供的Driver类,看它是如何实现的,源码如下:

    在这里插入图片描述
    加载Driver类的时候静态代码块会执行,也就会registerDriver()注册驱动程序了

    在该类中的静态代码块中已经执行了 DriverManager 对象的 registerDriver() 方法进行驱动的注册了,那么我们只需要加载 Driver 类,该静态代码块就会执行。而 Class.forName("com.mysql.jdbc.Driver"); 就可以加载 Driver 类。

    提示:

    • MySQL 5之后的驱动包,可以省略注册驱动的步骤
    • 自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类

在这里插入图片描述

  • 获取数据库连接

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6NtgYq0r-1677226449633)(assets/image-20210725171355278.png)]

    参数说明:

    • url : 连接路径

      语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…

      示例:jdbc:mysql://127.0.0.1:3306/db1

      细节:

      • 如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对

      • 配置 useSSL=false 参数,禁用安全连接方式,解决警告提示

    • user :用户名

    • poassword :密码

代码简化1:

/*** JDBC API详解:DriverManger**/
public class JDBCDemo2_DriverManager {public static void main(String[] args) throws Exception {//1. 注册驱动//Class.forName("com.mysql.jdbc.Driver");//将类加载到内存中 //Driver类的静态代码块注册的//mysql5之后 可以省略注册这一步了 自动注册//2. 获取连接// 1) 如果连接的是本机的mysql 并且端口是默认的3306 可以简化书写// 2) 配置 useSSL=false 参数,禁用安全连接方式,解决警告提示 (输出就没有警告了)String url = "jdbc:mysql:///db1?useSSL=false";String user = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, user, password);//3. 定义sqlString sql = "update account set money = 2000 where id = 1";//4. 获取执行sql的对象 StatementStatement st = conn.createStatement();//5. 执行sql 修改语句对应的方法int n = st.executeUpdate(sql); //返回受影响的行数//6. 处理结果 (此处就打印下行数了)System.out.println(n);//7. 释放资源 (先开后放)st.close();conn.close();}
}

3.2 Connection (事务归我管)

Connection(数据库连接对象)作用:

  • 获取执行 SQL 的对象
  • 管理事务

3.2.1 获取执行对象

  • 普通执行SQL对象

    Statement createStatement()
    

    入门案例中就是通过该方法获取的执行对象。

  • 预编译SQL的执行SQL对象:防止SQL注入

    PreparedStatement  prepareStatement(sql)
    

    通过这种方式获取的 PreparedStatement SQL语句执行对象是我们一会重点要进行讲解的,它可以防止SQL注入。

  • 执行存储过程的对象

    CallableStatement prepareCall(sql)
    

    通过这种方式获取的 CallableStatement 执行对象是用来执行存储过程的,而存储过程在MySQL中不常用,所以这个我们将不进行讲解。

3.2.2 事务管理

先回顾一下MySQL事务管理的操作:

  • 开启事务 : BEGIN; 或者 START TRANSACTION;
  • 提交事务 : COMMIT;
  • 回滚事务 : ROLLBACK;

MySQL默认是自动提交事务

接下来学习JDBC事务管理的方法。

Connection几口中定义了3个对应的方法:

  • 开启事务

    在这里插入图片描述

    参与autoCommit 表示是否自动提交事务,true表示自动提交事务,false表示手动提交事务。而开启事务需要将该参数设为为false。

  • 提交事务

    在这里插入图片描述

  • 回滚事务

    在这里插入图片描述

具体代码实现如下:

public class JDBCDemo3_Connection {public static void main(String[] args) throws Exception {//1. 注册驱动//Class.forName("com.mysql.jdbc.Driver");//将类加载到内存中 //Driver类的静态代码块注册的//mysql5之后 可以省略注册这一步了 自动注册//2. 获取连接// 1) 如果连接的是本机的mysql 并且端口是默认的3306 可以简化书写// 2) 配置 useSSL=false 参数,禁用安全连接方式,解决警告提示 (输出就没有警告了)String url = "jdbc:mysql:///db1?useSSL=false";String user = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, user, password);//3. 定义sqlString sql1 = "update account set money = money + 500 where id = 1";String sql2 = "update account set money = money - 500 where id = 2";//4. 获取执行sql的对象 StatementStatement st = conn.createStatement();try {//S1: 执行sql之前开启事务conn.setAutoCommit(false);//5. 执行sql 修改语句对应的方法int n1 = st.executeUpdate(sql1); //返回受影响的行数System.out.println(n1);//6.处理结果int i = 3/0; //自己造异常 模拟int n2 = st.executeUpdate(sql2); //返回受影响的行数System.out.println(n2);//6.处理结果//S2: 结果处理完,说明执行成功,提交事务conn.commit();}catch (Exception e){//S2: 一旦发生异常就回滚conn.rollback();e.printStackTrace();}finally {//7. 释放资源 (先开后放)  不管有没有异常st.close();conn.close();}}
}

3.3 Statement

3.3.1 概述

Statement对象的作用就是用来执行SQL语句。而针对不同类型的SQL语句使用的方法也不一样。

  • 执行DDL、DML语句

    在这里插入图片描述

  • 执行DQL语句

    在这里插入图片描述

    该方法涉及到了 ResultSet 对象,而这个对象我们还没有学习,一会再重点讲解。

在这里插入图片描述

3.3.2 代码实现

  • 执行DML语句
	@Testpublic void testDML() throws SQLException {//1. 注册驱动 省略//2. 获取连接String url = "jdbc:mysql:///db1?useSSL=false";String user = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, user, password);//3. 定义sqlString sql = "update account set money = 3000 where id = 1";//4. 获取执行sql的对象 StatementStatement st = conn.createStatement();//5. 执行sql 修改语句对应的方法int n = st.executeUpdate(sql); //执行DML语句后,受影响的行数//6. 处理结果 (此处就打印下行数了)if(n>0){System.out.println("修改成功!");}else {System.out.println("失败~");}//7. 释放资源 (先开后放)st.close();conn.close();}
  • 执行DDL语句
    @Testpublic void testDDL() throws SQLException {//1. 注册驱动 省略//2. 获取连接String url = "jdbc:mysql:///db1?useSSL=false";String user = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, user, password);//3. 定义sqlString sql = "create database db2";//4. 获取执行sql的对象 StatementStatement st = conn.createStatement();//5. 执行sql 修改语句对应的方法int n = st.executeUpdate(sql); //执行DML语句后,受影响的行数//6. 处理结果 (此处就打印下行数了)if(n>0){System.out.println("创建成功!");}else {System.out.println("失败~");}//7. 释放资源 (先开后放)st.close();conn.close();}@Testpublic void testDDL2() throws SQLException {//1. 注册驱动 省略//2. 获取连接String url = "jdbc:mysql:///db1?useSSL=false";String user = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, user, password);//3. 定义sqlString sql = "drop database if exists db2";//4. 获取执行sql的对象 StatementStatement st = conn.createStatement();//5. 执行sql 修改语句对应的方法int n = st.executeUpdate(sql); //执行DML语句后,受影响的行数//6. 处理结果 (此处就打印下行数了)System.out.println(n);//DDL 删除操作 执行成功 也返回0 不一定返回1 此时不能通过n>1判断是否成功了//7. 释放资源 (先开后放)st.close();conn.close();}

DDL执行成功,结果也可能是0

注意:

  • 以后开发很少使用java代码操作DDL语句

3.4 ResultSet

3.4.1 概述

ResultSet(结果集对象)作用:

  • 封装了SQL查询语句的结果。

而执行了DQL语句后就会返回该对象,对应执行DQL语句的方法如下:

ResultSet  executeQuery(sql):执行DQL 语句,返回 ResultSet 对象

那么我们就需要从 ResultSet 对象中获取我们想要的数据。ResultSet 对象提供了操作查询结果数据的方法,如下:

boolean next()

  • 将光标从当前位置向前移动一行
  • 判断当前行是否为有效行

方法返回值说明:

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

xxx getXxx(参数):获取数据

  • xxx : 数据类型;如: int getInt(参数) ;String getString(参数)
  • 参数
    • int类型的参数:列的编号,从1开始
    • String类型的参数: 列的名称

如下图为执行SQL语句后的结果

在这里插入图片描述

一开始光标指定于第一行前,如图所示红色箭头指向于表头行。当我们调用了 next() 方法后,光标就下移到第一行数据,并且方法返回true,此时就可以通过 getInt("id") 获取当前行id字段的值,也可以通过 getString("name") 获取当前行name字段的值。如果想获取下一行的数据,继续调用 next() 方法,以此类推。

3.4.2 代码实现

    @Testpublic void testDML() throws SQLException {//1. 注册驱动 省略//2. 获取连接String url = "jdbc:mysql:///db1?useSSL=false";String user = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, user, password);//3. 定义sqlString sql = "select * from account";//4. 获取Statement对象Statement st = conn.createStatement();//5. 执行sqlResultSet rs = st.executeQuery(sql);//6. 处理结果/* while (rs.next()){//根据列标访问  注意从1开始int id = rs.getInt(1);String name = rs.getString(2);double money = rs.getDouble(3);System.out.println(id+" "+name+" "+money);}*///根据列名访问最好while (rs.next()){int id = rs.getInt("id");String name = rs.getString("name");double money = rs.getDouble("money");System.out.println(id+" "+name+" "+money);}//7. 千万记得释放资源rs.close();st.close();conn.close();}

在这里插入图片描述

3.5 案例

  • 需求:查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中

    在这里插入图片描述

  1. 定义实体类Account
  2. 查询数据,封装到Account对象中
  3. 将Account对象存入ArrayList集合中
  • 代码实现
/*** 查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中*  1. 定义实体类Account*  2. 查询数据,封装到Account对象中*  3. 将Account对象存入ArrayList集合中*/
@Test
public void testResultSet2() throws SQLException {//1. 注册驱动 省略//2. 获取连接String url = "jdbc:mysql:///db1?useSSL=false";String user = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, user, password);//3. 定义sqlString sql = "select * from account";//4. 获取Statement对象Statement st = conn.createStatement();//5. 执行sqlResultSet rs = st.executeQuery(sql);//6. 处理结果//根据列名访问最好//准备好ArrayListList<Account> list = new ArrayList<>();while (rs.next()){int id = rs.getInt("id");String name = rs.getString("name");double money = rs.getDouble("money");Account account = new Account(id, name, money);list.add(account);}for (Account account : list) {System.out.println(account);}//7. 千万记得释放资源rs.close();st.close();conn.close();
}

在这里插入图片描述

3.6 PreparedStatement

PreparedStatement作用:

  • 预编译SQL语句并执行:预防SQL注入问题

对上面的作用中SQL注入问题大家肯定不理解。那我们先对SQL注入进行说明.

3.6.1 SQL注入

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

根据要求修改 application.properties 文件中的用户名和密码,文件内容如下:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=1234

在MySQL中创建名为 test 的数据库

create database test;

在命令提示符中运行今天资料下的 day03-JDBC\资料\2. sql注入演示\sql.jar 这个jar包。
直接双击sql.jar也行
在这里插入图片描述

此时我们就能在数据库中看到user表

在这里插入图片描述

接下来在浏览器的地址栏输入 localhost:8080/login.html 就能看到如下页面

在这里插入图片描述

我们就可以在如上图中输入用户名和密码进行登陆。用户名和密码输入正确就登陆成功,跳转到首页。用户名和密码输入错误则给出错误提示,如下图

在这里插入图片描述

但是我可以通过输入一些特殊的字符登陆到首页。

用户名随意写,密码写成 ' or '1' ='1

在这里插入图片描述
也能登陆成功!

这就是SQL注入漏洞,也是很危险的。当然现在市面上的系统都不会存在这种问题了,所以大家也不要尝试用这种方式去试其他的系统。

那么该如何解决呢?这里就可以将SQL执行对象 Statement 换成 PreparedStatement 对象。

3.6.2 代码模拟SQL注入问题

  • 准备工作
use db1;
-- 删除tb_user表
drop table if EXISTS tb_user;
-- 创建tb_user表
create table tb_user(id int,username varchar(20),password varchar(32)
);-- 添加数据
insert into tb_user VALUES
(1,'zhangsan','123'),
(2,'lisi','234');SELECT * from tb_user;
@Test
public void testResultSet() throws SQLException {String url = "jdbc:mysql:///db1?useSSL=false";String user = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, user, password);//模拟用户输入:用户名和密码String name = "hackdshjksdhdjh";String pwd = "' or '1'='1"; //可怕的注入  直接修改了sql语句的含义//sql语句String sql = "select * from tb_user where username = '"+name+"' and password = '"+pwd+"'";System.out.println(sql);//获取statement对象Statement st = conn.createStatement();//执行sqlResultSet rs = st.executeQuery(sql);//判断登陆是否成功if(rs.next()){//根据name和pwd能查到数据就说明登陆成功System.out.println("登陆成功!");}else {System.out.println("失败~");}//千万记得释放资源rs.close();st.close();conn.close();
}

在这里插入图片描述

上面代码是将用户名和密码拼接到sql语句中,拼接后的sql语句如下

select * from tb_user where username = 'hackdshjksdhdjh' and password = '' or '1'='1'

从上面语句可以看出条件 username = 'hackdshjksdhdjh' and password = '' 不管是否满足,而 or 后面的 '1' = '1' 是始终满足的,最终条件是成立的,就可以正常的进行登陆了。

更为可怕的是,这条sql语句会查出tb_user表的所有数据
在这里插入图片描述

接下来我们来学习PreparedStatement对象.

3.6.3 PreparedStatement概述

PreparedStatement作用:

  • 预编译SQL语句并执行:预防SQL注入问题
  • 获取 PreparedStatement 对象

    // SQL语句中的参数值,使用?占位符替代
    String sql = "select * from user where username = ? and password = ?";
    // 通过Connection对象获取,并传入对应的sql语句
    PreparedStatement pstmt = conn.prepareStatement(sql);
    
  • 设置参数值

    上面的sql语句中参数使用 ? 进行占位,在执行之前肯定要设置这些 ? 的值。

    PreparedStatement对象:setXxx(参数1,参数2):给 ? 赋值

    • Xxx:数据类型 ; 如 setInt (参数1,参数2)

    • 参数:

      • 参数1: ?的位置编号,从1 开始

      • 参数2: ?的值

  • 执行SQL语句

    executeUpdate(); 执行DDL语句和DML语句

    executeQuery(); 执行DQL语句

    注意:

    • 调用这两个方法时不需要传递SQL语句,因为获取SQL语句执行对象时已经对SQL语句进行预编译了。

3.6.4 使用PreparedStatement改进

@Test
public void testPreparedStatement() throws SQLException {String url = "jdbc:mysql:///db1?useSSL=false";String user = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, user, password);//模拟用户输入:用户名和密码String name = "hackdshjksdhdjh";String pwd = "' or '1'='1"; //可怕的注入  直接修改了sql语句的含义/*name = "lisi";pwd = "234";*/ //当然也成功啦//sql语句String sql = "select * from tb_user where username = ? and password = ?";//获取statement对象PreparedStatement ps = conn.prepareStatement(sql);//预编译sql(然后插入值,这样就不用拼接了)//设置?值ps.setString(1,name);//设置第一个?的值ps.setString(2,pwd);//设置第二个?的值//执行sqlResultSet rs = ps.executeQuery(); //不用传入sql了 前面传过了//也打印下sqlSystem.out.println(((JDBC4PreparedStatement)ps).asSql());//发现整个pwd当做一个字符串处理了('号会被转义),再也不会修改sql语句了//判断登陆是否成功if(rs.next()){//根据name和pwd能查到数据就说明登陆成功System.out.println("登陆成功!");}else {System.out.println("失败~");}//千万记得释放资源rs.close();ps.close();conn.close();
}

在这里插入图片描述
特殊字符如'被转义了,再也不会修改sql语句的逻辑了
在这里插入图片描述

3.6.5 PreparedStatement原理

PreparedStatement 好处:

  • 预编译SQL,性能更高
  • 防止SQL注入:将敏感字符进行转义

在这里插入图片描述

Java代码操作数据库流程如图所示:

  • 将sql语句发送到MySQL服务器端

  • MySQL服务端会对sql语句进行如下操作

    • 检查SQL语句

      检查SQL语句的语法是否正确。

    • 编译SQL语句。将SQL语句编译成可执行的函数。

      检查SQL和编译SQL花费的时间比执行SQL的时间还要长。如果我们只是重新设置参数,那么检查SQL语句和编译SQL语句将不需要重复执行。这样就提高了性能。
      sql编译一次,下次若只是参数不同就不需要重新编译,直接换参执行即可,大大提高效率

    • 执行SQL语句

接下来我们通过查询日志来看一下原理。

  • 开启预编译功能

    在代码中编写url时需要加上以下参数。而我们之前根本就没有开启预编译功能,只是解决了SQL注入漏洞。
    (默认是不开的,得手动开才行,否则就是纸上谈兵)

    useServerPrepStmts=true
    
  • 配置MySQL执行日志(重启mysql服务后生效)

    在mysql配置文件(my.ini)中添加如下配置

    log-output=FILE
    general-log=1
    general_log_file="D:\mysql.log"
    slow-query-log=1
    slow_query_log_file="D:\mysql_slow.log"
    long_query_time=2
    

在这里插入图片描述

没有权限就先复制到桌面,修改保存好后再复制回去

在这里插入图片描述
重启看看日志目录下是否有这两个.log文件
在这里插入图片描述

  • java测试代码如下
/*** PreparedStatement原理*/
@Test
public void testPreparedStatement2() throws SQLException {String url = "jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true";String user = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, user, password);//模拟用户输入:用户名和密码String name = "hackdshjksdhdjh";String pwd = "' or '1'='1"; //可怕的注入  直接修改了sql语句的含义//sql语句String sql = "select * from tb_user where username = ? and password = ?";//获取statement对象PreparedStatement ps = conn.prepareStatement(sql);//预编译sql(然后插入值,这样就不用拼接了)//设置?值ps.setString(1, name);//设置第一个?的值ps.setString(2, pwd);//设置第二个?的值//执行sqlResultSet rs = ps.executeQuery(); //不用传入sql了 前面传过了if (rs.next()) System.out.println("登陆成功!");else System.out.println("失败~");//再执行一次sqlps.setString(1, "lisi");//设置第一个?的值ps.setString(2, "234");//设置第二个?的值rs = ps.executeQuery(); //不用传入sql了 前面传过了//判断登陆是否成功if (rs.next()) {//根据name和pwd能查到数据就说明登陆成功System.out.println("登陆成功!");} else {System.out.println("失败~");}//千万记得释放资源rs.close();ps.close();conn.close();
}

上面的代码执行了两次sql: 一次注入失败,一次正常登陆成功
在这里插入图片描述

  • 执行SQL语句(执行上面java代码就是执行sql),查看 E:\log\mysql.log 日志如下:
MySQL, Version: 5.7.24-log (MySQL Community Server (GPL)). started with:
TCP Port: 0, Named Pipe: (null)
Time                 Id Command    Argument
2023-02-24T14:12:46.862195Z	    2 Connect	root@localhost on db1 using TCP/IP
2023-02-24T14:12:46.864956Z	    2 Query	/* mysql-connector-java-5.1.48 ( Revision: 29734982609c32d3ab7e5cac2e6acee69ff6b4aa ) */SELECT  @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client, @@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results, @@character_set_server AS character_set_server, @@collation_server AS collation_server, @@collation_connection AS collation_connection, @@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout, @@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet, @@net_buffer_length AS net_buffer_length, @@net_write_timeout AS net_write_timeout, @@performance_schema AS performance_schema, @@query_cache_size AS query_cache_size, @@query_cache_type AS query_cache_type, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone, @@transaction_isolation AS transaction_isolation, @@wait_timeout AS wait_timeout
2023-02-24T14:12:46.875712Z	    2 Query	SET character_set_results = NULL
2023-02-24T14:12:46.875885Z	    2 Query	SET autocommit=1
2023-02-24T14:12:46.886850Z	    2 Prepare	select * from tb_user where username = ? and password = ?
2023-02-24T14:12:46.887324Z	    2 Execute	select * from tb_user where username = 'hackdshjksdhdjh' and password = '\' or \'1\'=\'1'
2023-02-24T14:12:46.887585Z	    2 Execute	select * from tb_user where username = 'lisi' and password = '234'
2023-02-24T14:12:46.887825Z	    2 Close stmt	
2023-02-24T14:12:46.889170Z	    2 Quit	

上面日志中,前面是访问数据库连接的一些步骤
后面, 倒数第五行中的 Prepare 是对SQL语句进行预编译。倒数第四行和倒数第三行是执行了两次SQL语句,而第二次执行前并没有对SQL进行预编译。

(去掉预编译的代码&useServerPrepStmts=true 日志里就没有Prepare了)

小结:

  • 在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查,编译(这些步骤很耗时)
  • 执行时就不用再进行这些步骤了,速度更快
  • 如果sql模板一样,则只需要进行一次检查、编译

4,数据库连接池

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

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

相关文章

12.STM32系统定时器-SysTick

目录 1.系统定时器-SysTick 2.SysTick定时时间的计算 3.SysTick结构体 4.SysTick固件库函数 5.SysTick中断优先级 1.系统定时器-SysTick SysTick:24位系统定时器&#xff0c;只能递减&#xff0c;存在于内核嵌套在NVIC中。所有的Cortex-M中都有这个系统定时器。 重装载值…

interrupt多线程设计模式

1. 两阶段终止-interrupt Two Phase Termination 在一个线程T1中如何“优雅”终止线程T2&#xff1f;这里的【优雅】指的是给T2一个料理后事的机会。 错误思路 ● 使用线程对象的stop()方法停止线程&#xff08;强制杀死&#xff09; —— stop&#xff08;&#xff09;方法…

会声会影2023电脑版下载及系统配置要求

平时大家可能会经常听到有人说会声会影&#xff0c;但是很多人都不知道这是什么软件。其实听它的名字就知道这是一款和声音、影像有关系的软件。下面&#xff0c;小编就来给大家具体介绍一下这款软件吧。 会声会影是一套操作简单的DV、HDV影片剪辑软件。会声会影不仅完全符合家…

农业科技发展所带来的好处:提高农作物产量,提高农民收入

农业科技发展所带来的好处&#xff1a;&#xff1a;&#xff08;1&#xff09;育种技术&#xff1a;通过育种技术&#xff0c;科学家可以在农作物基因中挑选和改变一些特定的性状&#xff0c;例如增加产量、改善耐旱性和抗病性等等&#xff0c;从而提高农作物产量。&#xff08…

el-cascader 级联选择器懒加载的使用及回显 + 点击任意一级都能返回

需要实现的需求 数据渲染使用懒加载点击任意一级都可返回&#xff0c;不需要一直点到最后一级编辑或者查看功能&#xff0c;回显之前选择的数据 实例解析 dom 元素 <el-cascaderv-model"value":options"options":props"props":key"n…

Linux内核段页式内存管理技术

一、概述 1.虚拟地址空间 内存是通过指针寻址的&#xff0c;因而CPU的字长决定了CPU所能管理的地址空间的大小&#xff0c;该地址空间就被称为虚拟地址空间&#xff0c;因此32位CPU的虚拟地址空间大小为4G&#xff0c;这和实际的物理内存数量无关。 Linux内核将虚拟地址空间分…

五种IO模型以及select多路转接IO模型

目录 一、典型IO模型 1.1 阻塞IO 1.2 非阻塞IO 1.3 信号驱动I0 1.4 IO多路转接 1.5 异步IO 多路转接的作用和意义 二、多路转接IO模型&#xff08;select&#xff09; 2.1 接口 2.2 接口当中的事件集合&#xff1a; fd_set 2.2 select使用事件集合&#xff08;位图&am…

174万亿采购,奔向数字化

采购不单纯发生在外部&#xff0c;更发生在内部&#xff0c;只有两者同时进行&#xff0c;才能完成采购中心从成本到利润中心角色的转变。 作者|斗斗 编辑|皮爷 出品|产业家 数字化&#xff0c;让很多企业业务流程发生了质变。 《2022数字化采购发展报告》显示&#x…

破解票房之谜:为何高票房电影绕不过“猫眼们”?

如此火爆的春节档很多&#xff0c;如此毁誉参半的春节档鲜有。2023开年&#xff0c;集齐张艺谋、沈腾的《满江红》&#xff0c;以及有票房前作打底的《流浪地球2》接连两部春节档电影票房进入前十&#xff0c;为有些颓靡的中国电影市场注入了一针“强心剂”。与票房同样热闹起来…

无重叠区间-力扣435-java贪心策略

一、题目描述给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。示例 1:输入: intervals [[1,2],[2,3],[3,4],[1,3]]输出: 1解释: 移除 [1,3] 后&#xff0c;剩下的区间没有重叠。…

【爬虫】2.2 BeautifulSoup 装载HTML文档

HTML文档结点的查找工具很多&#xff0c;其中 BeautifulSoup 是功能强大且十分流行的查找工具之一。1. BeautifulSoup 的安装安装&#xff1a;pip install bs4导包&#xff1a;from bs4 import BeautifulSoup2. BeautifulSoup 装载HTML文档如果 doc 是一个 HTML 文档&#xff0…

深度学习训练营之yolov5 官方代码调用以及-requirements.txt下载当中遇到的问题

深度学习训练营之yolov5 官方代码调用原文链接内容总结环境介绍前置工作简单介绍yolov5下载源码yolov5的下载遇到问题问题解析问题处理创建虚拟环境下载当中遇到的问题代码运行视频检测参考内容原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客…

gazebo仿真环境中添加robotiq 2f 140的gripper_controller控制器

gazebo仿真环境中添加robotiq 2f 140的gripper_controller控制器 搭建环境&#xff1a; ubuntu: 20.04 ros: Nonetic sensor: robotiq_ft300 gripper: robotiq_2f_140_gripper UR: UR3 reasense&#xff1a; D435i 通过下面几篇博客配置好了ur3、力传感器、robotiq夹爪、rea…

18523-47-2,3-Azidopropionic Acid,叠氮基丙酸,可以与炔烃发生点击化学反应

【中文名称】3-叠氮基丙酸【英文名称】 3-Azidopropionic Acid&#xff0c;3-Azidopropionic COOH【结 构 式】【CAS】18523-47-2【分子式】C3H5N3O2【分子量】115.09【纯度标准】95%【包装规格】1g&#xff0c;5g&#xff0c;10g【是否接受定制】可进行定制&#xff0c;定制时…

java原理4:java的io网络模型

文章目录1&#xff1a;基础概念1&#xff1a;同步和异步2&#xff1a;阻塞和非阻塞2.1&#xff1a;阻塞IO2.2&#xff1a;非阻塞io2.3&#xff1a;io复用3&#xff1a;同步/异步和阻塞/非阻塞3.1&#xff1a;同步非阻塞NIO4: redis为什么速度快Java 网络IO模型简介1&#xff1a…

Tapdata 和 Databend 数仓数据同步实战

作者&#xff1a;韩山杰https://github.com/hantmacDatabend Cloud 研发工程师基础架构在云计算时代也发生着翻天地覆的变化&#xff0c;对于业务的支持变成了如何能利用好云资源实现降本增效&#xff0c;同时更好的支撑业务也成为新时代技术人员的挑战。 本篇文章通过&#xf…

删除MySQL表中的重复数据?

前言 一般我们将数据存储在MySQL数据库中&#xff0c;它允许我们存储重复的数据。但是往往重复的数据是作废的、没有用的数据&#xff0c;那么通常我们会使用数据库的唯一索引 unique 键作为限制。问题来了啊&#xff0c;我还没有创建唯一索引捏&#xff0c;数据就重复了&…

jianzhiOffer第二版难重点记录

04. 二维数组中的查找https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/ 思路&#xff1a;可以每层用以恶搞二分查找&#xff0c;优化思路&#xff1a;从左下角出发直接用二分。 ​​​​​​07. 重建二叉树https://leetcode.cn/problems/zhong-jian-er-cha…

springboot+vue.js高校大学生选课成绩管理系统javaweb

本课题要求实现一套学生成绩管理系统&#xff0c;系统主要包括管理员&#xff0c;学生和教师三大模块 (a) 管理员&#xff1b;管理员进入系统主要功能包括首页&#xff0c;个人中心&#xff0c;教师管理&#xff0c;学生管理&#xff0c;公告信息管理&#xff0c;课程类型管理&…

Android自定义View实现横向的双水波纹进度条

效果图&#xff1a;网上垂直的水波纹进度条很多&#xff0c;但横向的很少&#xff0c;将垂直的水波纹改为水平的还遇到了些麻烦&#xff0c;现在完善后发布出来&#xff0c;希望遇到的人少躺点坑。思路分析整体效果可分为三个&#xff0c;绘制圆角背景和圆角矩形&#xff0c;绘…