写点什么

【JavaWeb】JDBC 快速入门时间

作者:Java-fenn
  • 2022 年 9 月 12 日
    湖南
  • 本文字数:9048 字

    阅读完需:约 30 分钟

学习目标掌握 JDBC 的的 CRUD 理解 JDBC 中各个对象的作用掌握 Druid 的使用概述在开发中,我们使用的是 Java 语言,那么势必要通过 Java 语言操作数据库中的数据,这就是我们接下来要学习的 JDBC


1 - JDBC 的概念 JDBC( Java DataBase Connectivity ),中文为 Java 数据库连接,就是使用 Java 语言操作关系型数据库的一套 API


我们开发的同一套 Java 代码是无法操作不同的关系型数据库,因为每一个关系型数据库的底层实现细节都不一样。如果这样,问题就很大了,在公司中可以在开发阶段使用的是 MySQL 数据库,而上线时公司最终选用 Oracle 数据库,我们就需要对代码进行大批量修改,这显然并不是我们想看到的


我们要做到的是同一套 Java 代码操作不同的关系型数据库,所以 sun 公司就指定了一套标准接口( JDBC ), JDBC 中定义了所有操作关系型数据库的规则。众所周知,接口是无法直接使用的,我们需要使用接口的实现类,而这套实现类(称之为:驱动)就由各自的数据库厂商给出


2 - JDBC 的本质官方(sun 公司)定义的一套操作所有关系型数据库的规则,即接口各个数据库厂商去实现这套接口,提供数据库驱动 jar 包我们可以使用这套接口( JDBC )编程,真正执行的代码是驱动 jar 包中的实现类 3 - JDBC 的优点各数据库厂商使用相同的接口, Java 代码不需要针对不同数据库分别开发可随时替换底层数据库,访问数据库的 Java 代码基本不变以后编写操作数据库的代码只需要面向 JDBC (接口),操作哪个关系型数据库就需要导入该数据库的驱动包,如需要操作 MySQL 数据库,就需要再项目中导入 MySQL 数据库的驱动包


下图所示就是 MySQL 的驱动包:


快速上手我们先来看看通过 Java 操作数据库的流程


第一步:编写 Java 代码


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


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


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


1 - 编写代码步骤创建工程,导入驱动 jar 包驱动 jar 包的获取: pan.baidu.com/s/1HVOir_iY…


注册驱动 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();复制代码执行 SQLstmt.executeUpdate(sql);


复制代码处理返回结果释放资源 2 - 具体操作创建一个空项目


定义项目的名称,并指定位置


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


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


导入驱动包将 mysql 的驱动包放在模块下自己创建的一个 lib 目录(随意命名)下,并将该 jar 包添加为库文件


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


在 src 下创建一个类 JDBC_Demo 编写代码如下


import java.sql.*;


public class JDBC_Demo {public static void main(String[] args) throws Exception {//1. 注册驱动 Class.forName("com.mysql.jdbc.Driver");


    //2. 获取连接    String url = "jdbc:mysql://127.0.0.1:3306/db1";    String username = "root";    String password = "1234";    Connection conn = DriverManager.getConnection(url, username, password);        //3. 定义sql    String sql = "update account set money = 2000 where id = 1";        //4. 获取执行sql的对象 Statement    Statement stmt = conn.createStatement();        //5. 执行sql    int count = stmt.executeUpdate(sql);//受影响的行数        //6. 处理结果    System.out.println(count);        //7. 释放资源    stmt.close();    conn.close();}
复制代码


}复制代码 3 - JDBC API 详解(1) DriverManagerDriverManager(驱动管理类)作用:


注册驱动在使用手册中查询该类,我们可以看到有一个 registerDriver 方法:


registerDriver 方法是用于注册驱动的,但是我们之前写的入门案例并不是这样写的,而是通过下面的语句来实现


Class.forName("com.mysql.jdbc.Driver");复制代码查询一下 MySQL 提供的 Driver 类,源码如下:


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


提示:


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


参数说明:


url: 连接路径


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


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


细节:


如果连接的是本机 mysql 服务器,并且 mysql 服务默认端口是 3306,则 url 可以简写为:jdbc:mysql://数据库名称?参数键值对配置 useSSL=false 参数,禁用安全连接方式,解决警告提示 user:用户名


poassword :密码(2) ConnectionConnection(数据库连接对象)作用:


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


普通执行 SQL 对象 Statement createStatement()复制代码入门案例中就是通过该方法获取的执行对象


预编译 SQL 的执行 SQL 对象:防止 SQL 注入 PreparedStatement prepareStatement(sql)复制代码通过这种方式获取的 PreparedStatement SQL 语句执行对象是我们一会重点要进行讲解的,它可以防止 SQL 注入


执行存储过程的对象 CallableStatement prepareCall(sql)复制代码通过这种方式获取的 CallableStatement 执行对象是用来执行存储过程的,而存储过程在 MySQL 中不常用,所以这个我们不进行讲解


事务管理


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


开启事务 : BEGIN; 或者 START TRANSACTION;提交事务 : COMMIT;回滚事务 : ROLLBACK;MySQL 默认是自动提交事务


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


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


开启事务


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


提交事务


回滚事务


具体代码实现如下:


import java.sql.*;


public class JDBCDemo_Connection {


public static void main(String[] args) throws Exception {    //1. 注册驱动    //Class.forName("com.mysql.jdbc.Driver");    //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写    String url = "jdbc:mysql:///db1?useSSL=false";    String username = "root";    String password = "1234";    Connection conn = DriverManager.getConnection(url, username, password);    //3. 定义sql    String sql1 = "update account set money = 3000 where id = 1";    String sql2 = "update account set money = 3000 where id = 2";    //4. 获取执行sql的对象 Statement    Statement stmt = conn.createStatement();
try { // ============开启事务========== conn.setAutoCommit(false); //5. 执行sql int count1 = stmt.executeUpdate(sql1);//受影响的行数 //6. 处理结果 System.out.println(count1); //5. 执行sql int count2 = stmt.executeUpdate(sql2);//受影响的行数 //6. 处理结果 System.out.println(count2);
// ============提交事务========== //程序运行到此处,说明没有出现任何问题,则需求提交事务 conn.commit(); } catch (Exception e) { // ============回滚事务========== //程序在出现异常时会执行到这个地方,此时就需要回滚事务 conn.rollback(); e.printStackTrace(); }
//7. 释放资源 stmt.close(); conn.close();}
复制代码


}复制代码


(3) Statement 概述


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


执行 DDL、DML 语句


执行 DQL 语句


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


代码实现


执行 DML 语句 public void testDML() throws Exception {//1. 注册驱动//Class.forName("com.mysql.jdbc.Driver");//2. 获取连接:如果连接的是本机 mysql 并且端口是默认的 3306 可以简化书写 String url = "jdbc:mysql:///db1?useSSL=false";String username = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, username, password);//3. 定义 sqlString sql = "update account set money = 3000 where id = 1";//4. 获取执行 sql 的对象 StatementStatement stmt = conn.createStatement();//5. 执行 sqlint count = stmt.executeUpdate(sql);//执行完 DML 语句,受影响的行数//6. 处理结果//System.out.println(count);if(count > 0){System.out.println("修改成功~");}else{System.out.println("修改失败~");}//7. 释放资源 stmt.close();conn.close();}复制代码执行 DDL 语句 public void testDDL() throws Exception {//1. 注册驱动//Class.forName("com.mysql.jdbc.Driver");//2. 获取连接:如果连接的是本机 mysql 并且端口是默认的 3306 可以简化书写 String url = "jdbc:mysql:///db1?useSSL=false";String username = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, username, password);//3. 定义 sqlString sql = "drop database db2";//4. 获取执行 sql 的对象 StatementStatement stmt = conn.createStatement();//5. 执行 sqlint count = stmt.executeUpdate(sql);//执行完 DDL 语句,可能是 0//6. 处理结果 System.out.println(count);


//7. 释放资源stmt.close();conn.close();
复制代码


}复制代码注意:以后开发很少使用 Java 代码操作 DDL 语句(4) ResultSet 概述


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() 方法,以此类推


代码实现


public void testResultSet() throws Exception {//1. 注册驱动//Class.forName("com.mysql.jdbc.Driver");//2. 获取连接:如果连接的是本机 mysql 并且端口是默认的 3306 可以简化书写 String url = "jdbc:mysql:///db1?useSSL=false";String username = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, username, password);//3. 定义 sqlString sql = "select * from account";//4. 获取 statement 对象 Statement stmt = conn.createStatement();//5. 执行 sqlResultSet rs = stmt.executeQuery(sql);//6. 处理结果, 遍历 rs 中的所有数据// 6.1 光标向下移动一行,并且判断当前行是否有数据 while (rs.next()){//6.2 获取数据 getXxx()int id = rs.getInt("id");String name = rs.getString("name");double money = rs.getDouble("money");


    System.out.println(id);    System.out.println(name);    System.out.println(money);
System.out.println("--------------");}
//7. 释放资源rs.close();stmt.close();conn.close();
复制代码


}复制代码(5) 案例需求:查询 account 账户表数据,封装为 Account 对象中,并且存储到 ArrayList 集合中


代码实现


创建一个 Account 类来存储信息,便于存进集合


package pojo;


public class Account {private int id;private String name;private double money;


public int getId() {    return id;}
public void setId(int id) { this.id = id;}
public String getName() { return name;}
public void setName(String name) { this.name = name;}
public double getMoney() { return money;}
public void setMoney(double money) { this.money = money;}
复制代码


}复制代码 import java.sql.;import java.util.;import pojo.Account;


public class JDBCDemo_ResultSet {/** 查询 account 账户表数据,封装为 Account 对象中,并且存储到 ArrayList 集合中* 1. 定义实体类 Account* 2. 查询数据,封装到 Account 对象中* 3. 将 Account 对象存入 ArrayList 集合中*/public static void testResultSet2() throws Exception {//1. 注册驱动//Class.forName("com.mysql.jdbc.Driver");//2. 获取连接:如果连接的是本机 mysql 并且端口是默认的 3306 可以简化书写 String url = "jdbc:mysql:///db1?useSSL=false";String username = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, username, password);


    //3. 定义sql    String sql = "select * from account";
//4. 获取statement对象 Statement stmt = conn.createStatement();
//5. 执行sql ResultSet rs = stmt.executeQuery(sql);
//6.处理结果,遍历rs中的所有数据 // 6.1 光标向下移动一行,并且判断当前行是否有数据 // 创建集合 List<Account> list = new ArrayList<>();
while (rs.next()){ Account account = new Account();
//6.2 获取数据 getXxx() int id = rs.getInt("id"); String name = rs.getString("name"); double money = rs.getDouble("money");
//赋值 account.setId(id); account.setName(name); account.setMoney(money);
// 存入集合 list.add(account); }
for(Account a:list){ System.out.println(a.getId()); System.out.println(a.getName()); System.out.println(a.getMoney()); }
//7. 释放资源 rs.close(); stmt.close(); conn.close();}
public static void main(String[] args) { try { testResultSet2(); } catch (Exception e) { e.printStackTrace(); }}
复制代码


}复制代码执行结果如下:


(6) PreparedStatement 作用:预编译 SQL 语句并执行,预防 SQL 注入问题


对上面的作用中 SQL 注入问题你可能不理解,接下来我们先对 SQL 注入进行说明


① 什么是 SQL 注入


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


首先准备数据:


CREATE TABLE tb_user(id int PRIMARY KEY auto_increment,username varchar(10),password int(10));


-- 添加数据 INSERT INTO tb_user(username,password) VALUES('zhangsan',123),('lisi',123);


SELECT * FROM tb_user;复制代码


看一下这段代码


public void testLogin() throws Exception {//获取连接:如果连接的是本机 mysql 并且端口是默认的 3306 可以简化书写 String url = "jdbc:mysql:///db1?useSSL=false";String username = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, username, password);


// 接收用户输入 用户名和密码String name = "sjdljfld";String pwd = "' or '1' = '1";String sql = "select * from tb_user where username = '"+name+"' and password = '"+pwd+"'";// 获取stmt对象Statement stmt = conn.createStatement();// 执行sqlResultSet rs = stmt.executeQuery(sql);// 判断登录是否成功if(rs.next()){    System.out.println("登录成功~");}else{    System.out.println("登录失败~");}
//释放资源rs.close();stmt.close();conn.close();
复制代码


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


select * from tb_user where username = 'sjdljfld' and password = ''or '1' = '1'复制代码从上面语句可以看出条件 username = 'sjdljfld' and password = '' 不管是否满足,而 or 后面的 '1' = '1' 是始终满足的,最终条件是成立的,就可以正常的进行登陆了。如果此时有一个登录系统是这样写的,那么很容易就被人入侵


接下来我们来学习 PreparedStatement 对象


③ PreparedStatement 概述


获取 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 语句进行预编译了④ 使用 PreparedStatement 改进


import java.sql.*;


public class JDBC_PreparedStatement {public static void testPreparedStatement() throws Exception {//获取连接:如果连接的是本机 mysql 并且端口是默认的 3306 可以简化书写 String url = "jdbc:mysql:///db1?useSSL=false";String username = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, username, password);


    // 接收用户输入 用户名和密码    String name = "zhangsan";    String pwd = "' or '1' = '1";
// 定义sql String sql = "select * from tb_user where username = ? and password = ?"; // 获取pstmt对象 PreparedStatement pstmt = conn.prepareStatement(sql); // 设置?的值 pstmt.setString(1,name); pstmt.setString(2,pwd); // 执行sql ResultSet rs = pstmt.executeQuery(); // 判断登录是否成功 if(rs.next()){ System.out.println("登录成功~"); }else{ System.out.println("登录失败~"); } //释放资源 rs.close(); pstmt.close(); conn.close();}
public static void main(String[] args) throws Exception { testPreparedStatement();}
复制代码


}复制代码执行结果如下:


此时以无法登录成功,说明执行上面语句就不会出现 SQL 注入漏洞问题了


那么 PreparedStatement 又是如何解决的呢?


其实它是将特殊字符进行了转义,转义的 SQL 如下:


select * from tb_user where username = 'sjdljfld' and password = ''or '1' = '1'复制代码⑤ PreparedStatement 原理


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


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


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


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


检查 SQL 语句


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


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


检查 SQL 和编译 SQL 花费的时间比执行 SQL 的时间还要长。如果我们只是重新设置参数,那么检查 SQL 语句和编译 SQL 语句将不需要重复执行,这样就提高了性能


执行 SQL 语句


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


开启预编译功能在代码中编写 url 时需要加上以下参数,而我们之前根本就没有开启预编译功能,只是解决了 SQL 注入漏洞


useServerPrepStmts=true 复制代码配置 MySQL 执行日志(重启 mysql 服务后生效)在 mysql 配置文件 (my.ini) 中添加如下配置


log-output=FILEgeneral-log=1general_log_file="D:\mysql.log"slow-query-log=1slow_query_log_file="D:\mysql_slow.log"long_query_time=2 复制代码 Java 测试代码如下 public void testPreparedStatement2() throws Exception {//获取连接:如果连接的是本机 mysql 并且端口是默认的 3306 可以简化书写// useServerPrepStmts=true 参数开启预编译功能 String url = "jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true";String username = "root";String password = "1234";Connection conn = DriverManager.getConnection(url, username, password);


// 接收用户输入 用户名和密码String name = "zhangsan";String pwd = "' or '1' = '1";
// 定义sqlString sql = "select * from tb_user where username = ? and password = ?";
// 获取pstmt对象PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置?的值pstmt.setString(1,name);pstmt.setString(2,pwd);ResultSet rs = null;// 执行sqlrs = pstmt.executeQuery();
// 设置?的值pstmt.setString(1,"aaa");pstmt.setString(2,"bbb");// 执行sqlrs = pstmt.executeQuery();
// 判断登录是否成功if(rs.next()){ System.out.println("登录成功~");}else{ System.out.println("登录失败~");}
//释放资源rs.close();pstmt.close();conn.close();
复制代码


}复制代码执行 SQL 语句,查看 D:\mysql.log 日志如下


上图中第三行中的 Prepare 是对 SQL 语句进行预编译,第四行和第五行是执行了两次 SQL 语句,而第二次执行前并没有对 SQL 进行预编译


小结:


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

用户头像

Java-fenn

关注

需要Java资料或者咨询可加我v : Jimbye 2022.08.16 加入

还未添加个人简介

评论

发布
暂无评论
【JavaWeb】JDBC快速入门时间_Java_Java-fenn_InfoQ写作社区