Java_16_JDBC&连接池:JDBC(连接数据库技术)

摘要

一、JDBC介绍

客户端操作MySQL数据库的方式
1、使用第三方客户端来访问MySQL: SQLyog, Navicat, SQLWave,MyDB Studio, EMS SQLManager for MySQL
2、使用MySQL自带的命令行方式
3、通过Java程序来访问MySQL数据库(即JDBC),今天要学习的内容

1、什么是JDBC

JDBC概念: 英文全称Java DataBase Connectivity,中文全称Java连接数据库的技术
作用:通过Java程序来对数据库进行各种操作。

2、JDBC的由来

直接写Java代码操作数据库存在的问题:
1、不知道MySQL数据库的操作方式,解析方式
2、代码繁琐,写起来麻烦
3、MySQL和Oracle等其他数据库的操作方式和解析方式不同,每个数据库都要写一套代码
4、MySQL和Oracle等其他数据库相互切换麻烦
在这里插入图片描述

JDBC规范定义接口,具体的实现由各大数据库厂商来实现
JDBC是Java访问数据库的标准规范。真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用JDBC接口中的方法即可。数据库驱动由数据库厂商提供。
在这里插入图片描述

3、JDBC的好处

1、我们只需要会调用JDBC接口中的方法即可,使用简单
2、使用同一套Java代码,进行少量的修改就可以访问其他JDBC支持的数据库

4、JDBC用到的包

在这里插入图片描述

5、JDBC核心API

在这里插入图片描述

JDBC操作数据库流程
在这里插入图片描述

小结

JDBC核心类和接口说明
DriverManager 类用来注册和管理数据库驱动
用来于数据库建立连接获得连接对象
Driver 接口数据库驱动对象
Connection 接口连接对象
与数据库建立连接形成数据传输通道
Statement 接口SQL语句发送对象
将SQL语句发送给数据库执行并获得结果
ResultSet 接口结果集对象
用来封装满足查询条件的记录

二、利用JDBC获取Java程序与数据库的连接

Connection接口表示Java程序与数据库之间的连接,只有拿到Connection对象才能操作数据库。

JDBC获取连接步骤
1、导入驱动Jar包
2、注册驱动
3、获取连接

1、导入驱动Jar包

在IDEA工程下,创建一个名称为lib(Libraries)的文件夹,专门用来放jar包。并把数据库驱动包放到这个文件夹
在这里插入图片描述
右键点击lib文件夹->“add as library”->确定

2、注册驱动(Driver类和DriverManager类)

我们Java程序需要通过数据库驱动才能连接到数据库,因此需要注册驱动。MySQL的驱动的入口是:com.mysql.jdbc.Driver类
com.mysql.jdbc.Driver类是数据库驱动对象,
java.sql.DriverManager类用来注册和管理数据库驱动,和数据库建立连接获得连接对象
在这里插入图片描述
注册驱动的作用:告诉JVM接下来操作的是哪个厂商的数据库

注册驱动的方法

java.sql.DriverManager类用于注册驱动。提供如下方法注册驱动

//向DriverManager注册给定驱动程序。
static void registerDriver(Driver driver)

registerDriver注册驱动示例

DriverManager.registerDriver(驱动对象);传入对应参数即可

public class Demo01 {
public static void main(String[] args) throws Exception {
	//注册驱动
	DriverManager.registerDriver(new com.mysql.jdbc.Driver());
}

registerDriver注册驱动存在的问题(重复注册)

通过查询com.mysql.jdbc.Driver源码,我们发现Driver类又"主动"将自己进行注册

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
	static {
		try {
			// 自己自动注册
			java.sql.DriverManager.registerDriver(new Driver());
		} catch (SQLException E) {
			throw new RuntimeException("Can't register driver!");
		}
	}
	
	public Driver() throws SQLException {
	}
}

在这里插入图片描述
区分 Driver类和Driver接口
1、com.mysql.jdbc.Driver是类,java.sql.Driver是接口
2、com.mysql.jdbc.Driver类 继承 NoRegisteringDriver类,NoRegisteringDriver类实现java.sql.Driver接口

注意:
使用DriverManager.registerDriver (new com.mysql.jdbc.Driver())方式注册驱动,存在两方面不足
1、硬编码,后期不易于程序扩展和维护
2、驱动被注册两次

DriverManager.registerDriver(driver)会触发两次注册,不推荐使用

forName注册驱动示例(解决重复注册)

使用Class. forName (“com.mysql.jdbc.Driver”);加载驱动,这样驱动只会注册一次

public class Demo01 {
	public static void main(String[] args) throws Exception {
		Class.forName("com.mysql.jdbc.Driver"); // 后期可将"com.mysql.jdbc.Driver"字符串写在文件中.
	}
}

3、获取连接(DriverManager类)

java. sql.DriverManager类中有如下方法获取数据库连接

 static Connection	getConnection(String url, Properties info)
    * 与数据库建立连接并获得连接对象(等价于Socket对象)
    * url:数据库连接字符串
    * info:属性集合:用来存储用户名和密码,可以将用户名和密码存储到配置文件

 static Connection	getConnection(String url, String user, String password)
    * 与数据库建立连接并获得连接对象(等价于Socket对象)
    * url:数据库连接字符串
    * user:     用户名 root
    * password: 密码  root

参数说明
1、string url:连接数据库的URL,用于说明连接数据库的位置
2、string user:数据库的账号
3、string password:数据库的密码

连接数据库的URL地址格式:

协议名:子协议://服务器名或IP地址:端口号/数据库名?参数二参数值

在这里插入图片描述
MySQL数据库的URL写法:

jdbc:mysql://localhost:3306/day24

如果是本地服务器localhost,端口号是默认的3306,则可以省略简写为

jdbc:mysql:///day24

注意事项
如果查询出来的数据出现乱码,需要加上参数:?characterEncoding=utf8
表示让数据库以UTF-8编码来处理数据。如: jdbc:mysql://localhost:3306/day24?characterEncoding=utf8

获取连接示例

DriverManager.getConnection(url, user, password);传入对应参数即可

public class Demo01 {
	public static void main(String[] args) throws Exception {
		Class.forName("com.mysql.jdbc.Driver");
		// 连接到MySQL
		// url: 连接数据库的URL
		// user: 数据库的账号
		// password: 数据库的密码
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day24", "root", "root");
		System.out.println(conn);
	}
}

连接成功
在这里插入图片描述
连接失败
在这里插入图片描述

三、使用JDBC实现对单表数据增删改查

我们要对数据库进行增、删、改、查,需要使用statement对象来执行SQL语句。

1、获取Statement对象(通过Connection接口获取)

获取到statement对象,要先获取连接connection对象(通过驱动管理DriverManager类获取)

在java.sql.Connection接口中有如下方法获取到statement对象(connection对象获取statement对象)

Statement createStatement()
//创建一个 Statement 对象来将 SQL 语句发送到数据库

Statement 对象方法

boolean execute(String sql)

execute方法说明:此方法可以执行任意sql语句。返回boolean值,表示是否返回Resultset结果集。仅当执行select语句,且有返回结果时返回true,其它语句都返回false;

int executeUpdate(String sql)

executeUpdate方法说明:根据执行的DML (INSERT、UPDATE、DELETE)语句,返回受影响的行数

ResultSet executeQuery(String sql)

executeQuery方法说明:根据查询语句返回结果集,只能执行SELECT语句

注意:
在MySQL中,只要不是查询就是修改。
executeUpdate:用于执行增删改
executeQuery:用于执行查询

2、使用JDBC实现增删改查步骤

1、注册驱动
2、获取连接
3、获取Statement对象
4、使用Statement对象执行SQL语句
5、释放资源

3、JDBC增删改案例

使用JDBC增删改示例1

JDBC执行DDL语句_创建表

使用JDBC在MySQL的数据库中创建一张学生表
1) id 是主键,整数类型,自增长
2) name 是varchar(20),非空
3) 性别 是char类型
4) 生日 是date类型

package com.itheima._03执行DDL语句;

import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

/**
    目标:使用JDBC在MySQL的数据库中创建一张学生表

    实现步骤:
        1. 注册驱动
        2. 获得连接对象:Connection对象
        3. 获得SQL语句发送对象:Statement对象
        4. 准备SQL语句:String sql = ...
        5. 执行SQL语句
        6. 关闭连接释放资源:Connection和Statement
 */
public class JDBCDemo01 {
    @Test
    public void testCreateTable()throws Exception{
        // 1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 2. 获得连接对象:Connection对象
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day16", "root", "root");
        // 3. 获得SQL语句发送对象:Statement对象
        Statement stmt = conn.createStatement();
        // 4. 准备SQL语句:String sql = ...
        String sql = "create table student(" +
                "id int primary key auto_increment," +
                "name varchar(20) not null," +
                "gender char(1)," +
                "birthday date);";
        // 5. 执行SQL语句
        stmt.execute(sql);
        // 6. 关闭连接释放资源:Connection和Statement
        stmt.close();
        conn.close();
    }
}
JDBC执行DML语句_增删改
package com.itheima._04执行DML语句;

import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

/**
    目标:使用JDBC实现增删改功能
    实现步骤
        1. 注册驱动
        2. 获得连接对象
        3. 获得SQL语句发送对象
        4. 准备SQL语句
        5. 执行SQL语句并获得结果
        6. 关闭连接释放资源
 */
public class JDBCDemo01 {
    // 插入数据
    @Test
    public void testInsert() throws Exception{
        // 1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 2. 获得连接对象
        Connection conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
        // 3. 获得SQL语句发送对象
        Statement stmt = conn.createStatement();
        // 4. 准备SQL语句
        String sql = "insert into student values(null,'小苍','女','2000-11-10'),(null,'小武','男','1999-12-20')";
        // 5. 执行SQL语句并获得结果
        int row = stmt.executeUpdate(sql);
        System.out.println("row = " + row);
        // 6. 关闭连接释放资源
        stmt.close();
        conn.close();
    }

    // 修改数据
    @Test
    public void testUpdate()throws Exception{
        // 1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 2. 获得连接对象
        Connection conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
        // 3. 获得SQL语句发送对象
        Statement stmt = conn.createStatement();
        // 4. 准备SQL语句
        String sql = "update student set name = '武大郎', gender = '男' where id = 4;";
        // 5. 执行SQL语句并获得结果
        int row = stmt.executeUpdate(sql);
        System.out.println("row = " + row);
        // 6. 关闭连接释放资源
        stmt.close();
        conn.close();
    }

    // 删除数据:删除id为2的学生
    @Test
    public void testDelete() throws Exception{
        // 1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 2. 获得连接对象
        Connection conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
        // 3. 获得SQL语句发送对象
        Statement stmt = conn.createStatement();
        // 4. 准备SQL语句
        String sql = "delete from student where id = 2;";
        // 5. 执行SQL语句并获得结果
        int row = stmt.executeUpdate(sql);
        System.out.println("row = " + row);
        // 6. 关闭连接释放资源
        stmt.close();
        conn.close();
    }
}

使用JDBC增删改示例2

准备数据

-- 创建分类表
CREATE TABLE category (
cid INT PRIMARY KEY AUTO_INCREMENT,
cname VARCHAR(100)
);
-- 初始化数据
INSERT INTO category (cname) VALUES('家电');
INSERT INTO category (cname) VALUES('服饰');
INSERT INTO category (cname) VALUES('化妆品');

代码实现

public class Demo03 {
  public static void main(String[] args) throws Exception {
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root");
    System.out.println(conn);

    // String sql = "SELECT * FROM category;";
    // 从连接中拿到一个Statement对象
    Statement stmt = conn.createStatement();

    // 1.插入记录
    String sql = "INSERT INTO category (cname) VALUES ('手机');";
    int i = stmt.executeUpdate(sql);
    System.out.println("影响的行数:" + i);

    // 2.修改记录
    sql = "UPDATE category SET cname='汽车' WHERE cid=4;";
    i = stmt.executeUpdate(sql);
    System.out.println("影响的行数:" + i);

    // 3.删除记录
    sql = "DELETE FROM category WHERE cid=1;";
    i = stmt.executeUpdate(sql);
    System.out.println("影响的行数:" + i);
    
    // 释放资源
    stmt.close();
    conn.close();
  }
}

效果
在这里插入图片描述

4、使用JDBC查询单表

JDBC进行查询操作时,使用statement对象的executeQuery方法,返回值是ResultSet。
ResultSet对象的作用:用来封装满足查询条件的记录信息

ResultSet executeQuery(String sql)

Resultset用于保存执行查询SQL语句的结果。我们不能一次性取出所有的数据,需要一行一行的取出。

ResultSet的原理

1、ResultSet内部有一个指针,刚开始记录开始位置
2、调用next方法, ResultSet内部指针会移动到下一行数据
3、我们可以通过ResultSet得到一行数据getxxx得到某列数据

ResultSet原理图(和迭代器Iterator相似)
在这里插入图片描述

ResultSet的API

ResultSet获取数据的API其实ResultSet获取数据的API是有规律的get后面加数据类型。我们统称getxxx()
在这里插入图片描述

使用JDBC查询数据库中的数据的步骤

1、注册驱动
2、获取连接
3、获取到Statement
4、使用Statemen对象executeQuery方法t执行SQL
5、ResultSet处理结果
6、关闭资源

JDBC查询数据库单表示例1

public class Demo04 {
  public static void main(String[] args) throws Exception {
    Class.forName("com.mysql.jdbc.Driver");
    
    Connection conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root");
    Statement stmt = conn.createStatement();
    
    String sql = "SELECT * FROM category;";
    ResultSet rs = stmt.executeQuery(sql);
    
    // 内部有一个指针,只能取指针指向的那条记录
    while (rs.next()) { // 指针移动一行,有数据才返回true
      // 取出数据
      int cid = rs.getInt("cid");
      String cname = rs.getString("cname");
      
      System.out.println(cid + " == " + cname);
    }
    
    // 关闭资源
    rs.close();
    stmt.close();
    conn.close();
  }
}

案例效果
在这里插入图片描述
注意:
1、如果光标在第一行之前,使用rs.getXxX()取列值,报错: Before start of result set
2、如果光标在最后一行之后,使用rs.getXXX()获取列值,报错: After end of result set

JDBC查询数据库单表示例2

package com.itheima._05执行DQL语句;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

/**
    目标:使用JDBC实现查询功能
    需求:查询所有学生信息并输出
    实现步骤:
        1. 注册驱动
        2. 获得连接对象
        3. 获得SQL语句发送对象
        4. 准备要执行的SQL语句
        5. 执行SQL语句并获得结果集对象
        6. 通过结果集对象获得每行每列的值
        7. 关闭连接释放资源

    注意事项
        1. 指针指向第一行记录之前还调用getXxx方法就会出现如下异常
            java.sql.SQLException: Before start of result set
        2. 指针指向最后一行记录之后还调用getXxx方法就会出现如下异常
            java.sql.SQLException: After end of result set
 */
public class JDBCDemo01 {
    public static void main(String[] args) throws Exception{
        // 1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 2. 获得连接对象
        Connection conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");
        // 3. 获得SQL语句发送对象
        Statement stmt = conn.createStatement();
        // 4. 准备要执行的SQL语句
        String sql = "select * from student;";
        // 5. 执行SQL语句并获得结果集对象
        ResultSet rs = stmt.executeQuery(sql);
        // 6. 通过结果集对象获得每行每列的值
        while (rs.next()) {
            // 根据列号获得每列的值
           /* // 获得id值
            int id = rs.getInt(1);
            // 获得姓名
            String name = rs.getString(2);
            // 获得性别
            String gender = rs.getString(3);
            // 获得生日
            String birthday = rs.getString(4);
            System.out.println(id+":" + name + ":" + gender + ":" + birthday);*/

            // 根据列名获得每列的值
            // 获得id值
            int id = rs.getInt("id");
            // 获得姓名
            String name = rs.getString("name");
            // 获得性别
            String gender = rs.getString("gender");
            // 获得生日
            String birthday = rs.getString("birthday");
            System.out.println(id+":" + name + ":" + gender + ":" + birthday);
        }
        // 7. 关闭连接释放资源
        rs.close();
        stmt.close();
        conn.close();
    }
}

总结:

我们使用JDBC操作数据库的步骤都是固定的。不同的地方是在编写SQL语句

四、JDBC事务

之前我们是使用MySQL的命令来操作事务。接下来我们使用DBC来操作银行转账的事务。

Connection接口中与事务有关的方法

void setAutoCommit(boolean autoCommit) throws SQLException;
false:开启事务, ture:关闭事务

void commit() throws SQLException;
提交事务

void rollback() throws SQLException;
回滚事务

JDBC事务使用步骤

1、注册驱动
2、获取连接
3、获取到Statement
4、开启事务
5、使用Statement执行SQL
6、提交或回滚事务
7、关闭资源

JDBC事务示例1

public class Demo05 {
  public static void main(String[] args) {
    Connection conn = null;
    try {
      // 拿到连接
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root");
      
      // 开启事务
      conn.setAutoCommit(false);
      
      Statement pstmt = conn.createStatement();
      
      // 张三减500
      String sql = "UPDATE account SET balance = balance - 500 WHERE id=1;";
      pstmt.executeUpdate(sql);
      // 模拟异常
      // int i = 10 / 0;
      
      // 李四加500
      sql = "UPDATE account SET balance = balance + 500 WHERE id=2;";
      pstmt.executeUpdate(sql);
      
      pstmt.close();
      // 成功,提交事务
      System.out.println("成功,提交事务");
      conn.commit();
    } catch (Exception e) {
      // 失败,回滚事务
      try {
        System.out.println("出现了异常,回滚事务");
        conn.rollback();
      } catch (SQLException e1) {
        e1.printStackTrace();
      }
    } finally {
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }
}

案例效果
在这里插入图片描述

JDBC事务示例2

package com.itheima._06JDBC事务;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

/**
    目标:使用JDBC实现事务的管理
    需求:Jack给rose转500块
    实现步骤
        1. 注册驱动
        2. 获得连接对象
        3. 获得SQL语句发送对象
        4. 准备SQL语句:扣钱SQL
        5. 执行扣钱SQL语句
        6. 准备SQL语句:加钱SQL
        7. 执行加钱SQL语句
        8. 关闭资源

    Collection接口中与事务管理相关的方法
        void setAutoCommit(boolean b);
            设置是否自动提交事务
            true:自动提交事务
            false:禁止自动提交事务,等价于 start transaction;
        void commit();  提交事务
        void rollback(); 回滚事务
 */
public class JDBCDemo01 {
    public static void main(String[] args) {
        // 声明连接对象
        Connection conn = null;
        // 声明SQL发送对象
        Statement stmt =  null;
        try{
            // 1. 注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 2. 获得连接对象
            conn = DriverManager.getConnection("jdbc:mysql:///day16", "root", "root");

            // 开启事务:禁止自动提交事务
            conn.setAutoCommit(false);

            // 3. 获得SQL语句发送对象
            stmt = conn.createStatement();
            // 4. 准备SQL语句:扣钱SQL
            String sql01 = "update account set balance = balance - 500 where id = 1";
            // 5. 执行扣钱SQL语句
            int row = stmt.executeUpdate(sql01);
            System.out.println("扣钱:" + row);

            // 模拟异常
            // System.out.println(100/0);

            // 6. 准备SQL语句:加钱SQL
            String sql02 = "update account set balance = balance + 500 where id = 2";
            // 7. 执行加钱SQL语句
            row = stmt.executeUpdate(sql02);
            System.out.println("加钱:" + row);
            // 提交事务
            conn.commit();
        } catch(Exception e){
            // e.printStackTrace();
            try {
                System.out.println("回滚了事务...");
                // 回滚事务
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            // 8. 关闭资源
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

五、JDBC工具类

通过上面案例需求我们会发现每次去执行SQL语句都需要注册驱动,获取连接,得到Statement,以及释放资源。发现很多重复的劳动,我们可以将重复的代码定义到某个类的方法中。直接调用方法,可以简化代码。那么我们接下来定义一个JDBCutil类。把注册驱动,获取连接,得到Statement,以及释放资源的代码放到这个类的方法中。以后直接调用方法即可。

创建工具类的目的:将重复的代码定义到某个类的方法中,直接调用方法,可以简化代码。

编写JDBC工具类步骤

1、将固定字符串定义为常量
2、在静态代码块中注册驱动(只注册一次)
3、提供一个获取连接的方法static Connection getConneciton();
4、定义关闭资源的方法close(Connection conn, Statement stmt, Resultset rs)
5、重载关闭方法close(Connection conn, statement stmt)

工具类代码

package com.itheima._07JDBC工具类;

import java.sql.*;

/**
 创建工具类的目的:将重复的代码定义到某个类的方法中,直接调用方法,可以简化代码。
 实现工具类的步骤
     1. 将固定字符串定义为常量
     2. 在静态代码块中注册驱动(只注册一次)
     3. 提供一个获取连接的方法:static Connection getConneciton();
     4. 定义关闭资源的方法:close(Connection conn, Statement stmt, ResultSet rs)
     5. 重载关闭方法:close(Connection conn, Statement stmt)
 */
public class JDBCUtils {
    // 1. 将固定字符串定义为常量
    private static final String URL = "jdbc:mysql:///day16";
    private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
    private static final String USER = "root";
    private static final String PASSWORD = "root";

    // 2. 在静态代码块中注册驱动(只注册一次)
    static {
        try{
            Class.forName(DRIVER_CLASS);
        } catch(Exception e){
            e.printStackTrace();
        }
    }

    // 3. 提供一个获取连接的方法:static Connection getConnection();
    public static Connection getConnection(){
        try {
            return DriverManager.getConnection(URL, USER, PASSWORD);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    //  4. 定义关闭资源的方法:close(Connection conn, Statement stmt, ResultSet rs)
    //关闭资源方法(先关闭ResultSet,再关闭statement,最后关闭Connection)
    public static void close(Connection conn, Statement stmt, ResultSet rs){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    //  5. 重载关闭方法:close(Connection conn, Statement stmt)
    //关闭只有2个对象的方法,非查询的JDBC没有ResultSet不用关闭
    public static void close(Connection conn, Statement stmt){
        close(conn, stmt, null);
    }

}

使用JDBC工具类案例

public class Demo06 {
  public static void main(String[] args) throws Exception {
    createTable();
//    addEmployee();
//    updateEmployee(); 
//    deleteEmployee();
  }
  
  // 删除员工
  public static void deleteEmployee() throws Exception {
    Connection conn = JDBCUtils.getConnection();
    Statement stmt = conn.createStatement();
    
    // 删除id为3的员工
    String sql = "DELETE FROM employee WHERE id=3;";
    
    int i = stmt.executeUpdate(sql);
    System.out.println("影响的行数: " + i);
    
//    stmt.close();
//    conn.close();
//    JDBCUtils.close(conn, stmt, null);
    JDBCUtils.close(conn, stmt);
  }
  
  // 修改员工
  public static void updateEmployee() throws Exception {
    Connection conn = JDBCUtils.getConnection();
    Statement stmt = conn.createStatement();
    
    // 将id为3的员工姓名改成田七,地址改成天津
    String sql = "UPDATE employee SET address='天津', name='田七' WHERE id=3;";
    
    int i = stmt.executeUpdate(sql);
    System.out.println("影响的行数: " + i);
    
//    stmt.close();
//    conn.close();
//    JDBCUtils.close(conn, stmt, null);
    JDBCUtils.close(conn, stmt);
  }
  
  // 定义添加员工
  public static void addEmployee() throws Exception {
    Connection conn = JDBCUtils.getConnection();
    Statement stmt = conn.createStatement();
    // 添加4个员工
    String sql = "INSERT INTO employee VALUES (NULL, '张三4', 20, '北北京'),"
        + " (NULL, '李李四4', 21, '南京'),"
        + " (NULL, '王五4', 18, '东京'),"
        + " (NULL, '赵六4', 17, '西安');";
    
    int i = stmt.executeUpdate(sql);
    System.out.println("影响的行数: " + i);
    
//    stmt.close();
//    conn.close();
//    JDBCUtils.close(conn, stmt, null);
    JDBCUtils.close(conn, stmt);
  }

  // 创建表
  public static void createTable() throws Exception {
    Connection conn = JDBCUtils.getConnection();
    Statement stmt = conn.createStatement();
    
    String sql = "CREATE TABLE IF NOT EXISTS employee ("
        + " id INT PRIMARY KEY AUTO_INCREMENT,"
        + " name VARCHAR(20) UNIQUE NOT NULL,"
        + " age INT,"
        + " address VARCHAR(50)"
        + ");";
    
    int i = stmt.executeUpdate(sql);
    System.out.println("ok");
    
//    stmt.close();
//    conn.close();
//    JDBCUtils.close(conn, stmt, null);
    JDBCUtils.close(conn, stmt);
  }
}

六、JDBC实现登录案例

案例需求

模拟用户输入账号和密码登录网站

案例效果

1、输入正确的账号,密码,显示登录成功
在这里插入图片描述

2、输入错误的账号,密码,显示登录失败
在这里插入图片描述

步骤分析

1. 准备数据表和测试数据
2. 创建键盘录入对象
3. 接收用户名和密码
4. 获得连接对象
5. 获得SQL语句发送对象
6. 准备要执行的SQL语句
7. 执行SQL语句并获得结果集对象
8. 根据结果集对象判断是否登录成功
9. 关闭资源

JDBC实现登录 示例1

1、创建一个用户表保存用户的账号和密码,并添加一些数据,SQL语句如下:

CREATE TABLE USER (
	id INT AUTO_INCREMENT PRIMARY KEY,
	NAME VARCHAR(50),
	PASSWORD VARCHAR(50)
);

INSERT INTO USER (NAME, PASSWORD) VALUES('admin', '123'), ('test', '123'), ('gm', '123');

2、编写代码让用户输入账号和密码

public class Demo07 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入账号: ");
		String name = sc.nextLine();
		System.out.println("请输入密码: ");
		String password = sc.nextLine(); 
   }

3、使用SQL根据用户的账号和密码去数据库查询数据

public class Demo07 {
	public static void main(String[] args) throws Exception {
		// 让用户输入账号和密码
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入账号: ");
		String name = sc.nextLine();
		System.out.println("请输入密码: ");
		String password = sc.nextLine();
   
		// 使用SQL根据用户的账号和密码去数据库查询数据
		Connection conn = JDBCUtils.getConnection();
		Statement stmt = conn.createStatement();
		String sql = "SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";
	}
}

4、如果查询到数据,说明登录成功,如果查询不到数据,说明登录失败

public class Demo07 {
	public static void main(String[] args) throws Exception {
		// 让用户输入账号和密码
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入账号: ");
		String name = sc.nextLine();
		System.out.println("请输入密码: ");
		String password = sc.nextLine();
   
		// 使用SQL根据用户的账号和密码去数据库查询数据
		Connection conn = JDBCUtils.getConnection();
		Statement stmt = conn.createStatement();
		String sql = "SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";

		// 如果查询到数据,说明登录成功,如果查询不不到数据,说明登录失败
		ResultSet rs = stmt.executeQuery(sql);

		if (rs.next()) {
			//能进来查询到了了数据.
			String name2 = rs.getString("name");
			System.out.println("欢迎您," + name2);
		} else {
			//查询不不到数据,说明登录失败
			System.out.println("账号或密码错误...");
		}
		
		JDBCUtils.close(conn, stmt, rs);
	}
}

JDBC实现登录 示例2

1、准备数据表和用户数据

-- 准备数据表
create table user(
	id int primary key auto_increment,
	username varchar(20) not null unique,
	password varchar(20) not null 
);
-- 测试数据
insert into user(username,password) values('jack','rose'),('rose','laowang');
select * from user;

2、实现代码

package com.itheima._08JDBC登录案例;

import com.itheima._07JDBC工具类.JDBCUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;

/**
 目标:使用JDBC实现登录功能
 实现步骤
     2. 创建键盘录入对象
     3. 接收用户名和密码
     4. 获得连接对象
     5. 获得SQL语句发送对象
     6. 准备要执行的SQL语句
     7. 执行SQL语句并获得结果集对象
     8. 根据结果集对象判断是否登录成功
     9. 关闭资源
 */
public class LoginDemo01 {
    public static void main(String[] args) throws Exception{
        // 2. 创建键盘录入对象
        Scanner sc = new Scanner(System.in);
        // 3. 接收用户名和密码
        System.out.println("请输入用户名:");
        String username = sc.nextLine();
        System.out.println("请输入密码:");
        String password = sc.nextLine();
        // 4. 获得连接对象
        Connection conn = JDBCUtils.getConnection();
        // 5. 获得SQL语句发送对象
        Statement stmt = conn.createStatement();
        // 6. 准备要执行的SQL语句
        String sql = "select * from user where username = '"+username+"' and password = '"+password+"';";
        System.out.println("sql = "  + sql);
        // 7. 执行SQL语句并获得结果集对象
        ResultSet rs = stmt.executeQuery(sql);
        // 8. 根据结果集对象判断是否登录成功
        if (rs.next()){
            System.out.println("欢迎你," + username);
        } else {
            System.out.println("用户名或密码错误...");
        }
        // 9. 关闭资源
        JDBCUtils.close(conn,stmt,rs);
    }
}

登录案例的SQL注入问题

SQL注入的概念:用户输入的内容作为了SQL语法的一部分,改变了原有SQL语句的含义。

SQL注入示例

在登录案例中当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了了

请输入用户名:
hehe
请输入密码:
a' or '1'='1

问题分析:

// 代码中的SQL语句
"SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";
// 将用户输入的账号密码拼接后
"SELECT * FROM user WHERE name='hehe' AND password='a' or '1'='1';"

我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。需要使用PreparedSatement接口(预编译对象)解决SQL注入。

小结

1、SQL注入的概念:用户输入的内容作为了SQL语法的一部分,改变了原有SQL语句的含义。
2、如何解决SQL注入问题:不能将用户输入的内容和SQL语句进行拼接了,此时发送SQL语句不能使用Statement要使用PreparedStatement来发送SQL语句到数据库执行了。

七、PreparedStatement预编译对象(解决SQL注入问题)

我们写的SQL语句让数据库执行,数据库不是直接执行SQL语句字符串。和Java一样,数据库需要执行编译后的SQL语句(类似ava编译后的字节码文件)。

1、Statement执行原理

Satement对象每执行一条SQL语句都会先将这条SQL语句发送给数据库编译,数据库再执行。

Statement stmt = conn.createStatement();
stmt.executeUpdate("INSERT INTO users VALUES (1, '张三', '123456');");
stmt.executeUpdate("INSERT INTO users VALUES (2, '李四', '666666');");

上面2条SQL语句我们可以看到大部分内容是相同的,只是数据略有不一样。数据库每次执行都编译一次。如果有1万条类似的SQL语句,数据库需要编译1万次,执行1万次,显然效率就低了。

2、PreparedStatement执行原理

preparestatement()会先将SQL语句发送给数据库预编译。之后Preparedstatement会引用着预编译后的结果。可以多次传入不同的参数给Preparedstatement对象并执行。相当于调用方法多次传入不同的参数。

String sql = "INSERT INTO users VALUES (?, ?, ?);";
//会先将SQL语句发送给数据库预编译。Preparedstatement会引用着预编译后的结果。
PreparedStatement pstmt = conn.prepareStatement(sql);

// 设置参数 
pstmt.setString(1, 1); 
pstmt.setInt(2, "张三");
pstmt.setString(3, "123456");
pstmt.executeUpdate();

// 再次设置参数 
pstmt.setString(1, 2); 
pstmt.setInt(2, "李四"); 
pstmt.setString(3, "66666"); 
pstmt.executeUpdate();

上面预编译好一条SQL,2次传入了不同的参数并执行。如果有1万条类似的插入数据的语句。数据库只需要预编译一次,传入1万次不同的参数并执行。减少了SQL语句的编译次数,提高了执行效率。

原理示意图
在这里插入图片描述

PreparedStatement好处

1、有预编译功能: preparestatement()会先将SQL语句发送给数据库预编译。Preparedstatement会引用着预编译后的结果。可以多次传入不同的参数给Preparedstatement对象并执行。执行时不需要再编译了。2、有缓冲功能:多次执行同一条SQL语句不需要多次编译。
3、安全性更高,没有SQL注入的隐患。

PreparedStatement和Statement区别-[面试题]

相同点:都是用来发送sql语句到数据库执行增删改查操作
不同点
1、PreparedStatement有预编译功能,Statement没有预编译功能。
2、PreparedStatement有缓存(缓存SQL语句编译的结果)功能,Statement没有缓存功能。
3、PreparedStatement没有SQL注入问题,Statement有SQL注入问题
4、PreparedStatement更安全,效率更高,可读性更好

3、PreparedStatement接口的基本使用

PreparedStatement接口继承Statement接口,PreparedStatement对象的作用是可以将SQL语法发送给数据库执行并获得结果
在这里插入图片描述

获取PreparedStatement对象

在java.sql.Connection有获取Preparedsatement对象的方法,即通过连接对象的方法获得PreparedStatement对象

PreparedStatement prepareStatement(String sql)
//会先将SQL语句发送给数据库预编译。PreparedStatement对象会引用着预编译后的结果。

PreparedStatement对象方法

在java.sql.Preparedstatement中有设置SQL语句参数,和执行参数化的SQL语句的方法

//将指定参数设置为给定Java double值。
void setDouble(int parameterIndex, double x)

//将指定参数设置为给定Java float值。
void setFloat(int parameterIndex, float x)

//将指定参数设置为给定Java int值。
void setInt(int parameterIndex, int x)

//将指定参数设置为给定Java long值。
void setLong(int parameterIndex, long x)

//使用给定对象设置指定参数的值。
void setObject(int parameterIndex, Object x)

//将指定参数设置为给定Java String值。
void setString(int parameterIndex, String x)

//在此Preparedstatement对象中执行SQL查询,并返回该查询生成的ResultSet对象。
ResultSet executeQuery()

//在此Preparedstatement对象中执行SQL语句,该语句必须是一个SQL数据操作语言(Data Manipulation Language, DML)语句,比如INSERT、 UPDATE或DELETE语句;或者是无返回内容的SQL语句,比如DDL语句。
int executeUpdate()

PreparedSatement使用步骤

1、编写SQL语句,未知内容使用?占位

SELECT * FROM user WHERE name=? AND password=?;

2、获得PreparedStatement对象
3、设置实际参数
4、执行参数化SQL语句
5、关闭资源

PreparedSatement使用示例

public class Demo08 {

	public static void main(String[] args) throws Exception {
		// 获取连接
		Connection conn = JDBCUtils.getConnection();
    
		// 编写SQL语句句,未知内容使用?占位
		String sql = "SELECT * FROM user WHERE name=? AND password=?;";
    
		// prepareStatement()会先将SQL语句句发送给数据库预编译。
		PreparedStatement pstmt = conn.prepareStatement(sql);
    
		// 指定?的值
		// parameterIndex: 第几个?,从1开始算
		// x: 具体的值
		pstmt.setString(1, "admin");
		pstmt.setString(2, "123"); // 正确的密码
		// pstmt.setString(2, "6666"); // 错误的密码
    
		ResultSet rs = pstmt.executeQuery();
    
		if (rs.next()) {
			String name = rs.getString("name");
			System.out.println("name:" + name);
		} else {
			System.out.println("没有找到数据...");
		}
    
		JDBCUtils.close(conn, pstmt, rs);
  }
}

示例效果
输入正确的账号密码:
在这里插入图片描述
输入错误的密码
在这里插入图片描述

4、PreparedStatement实现增删改操作

package com.itheima._09预编译对象;

import com.itheima._07JDBC工具类.JDBCUtils;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;

/**
    目标:使用PreparedStatement实现增删改功能
 */
public class JDBCDemo01 {
    // 添加数据: 向学生表添加3条记录
    @Test
    public void insertStudent() throws Exception {
        // 1. 获得连接对象
        Connection conn = JDBCUtils.getConnection();
        // 2. 准备SQL语句
        String sql = "insert into student values(null,?,?,?);";
        // 3. 获得预编译对象
        PreparedStatement ps = conn.prepareStatement(sql);
        // 4. 给占位符赋值
        ps.setString(1, "jack");
        ps.setString(2, "男");
        ps.setString(3, "2012-05-26");
        // 5. 执行SQL语句并获得结果
        int row = ps.executeUpdate();
        System.out.println(row);

        // 给占位符赋值
        ps.setString(1, "rose");
        ps.setString(2, "女");
        ps.setString(3, "2013-05-26");
        // 5. 执行SQL语句并获得结果
        row = ps.executeUpdate();
        System.out.println(row);


        // 给占位符赋值
        ps.setString(1, "lily");
        ps.setString(2, "男");
        ps.setString(3, "2013-06-16");
        // 5. 执行SQL语句并获得结果
        row = ps.executeUpdate();
        System.out.println(row);


        // 6. 关闭资源
        JDBCUtils.close(conn, ps);
    }

    // 修改数据: 将 id 为 3 的用户,姓名更新为 "嫦娥 "  性别换成女,
    @Test
    public void updateStudent() throws Exception {
        // 1. 获得连接对象
        Connection conn = JDBCUtils.getConnection();
        // 2. 准备SQL语句
        String sql = "update student set name = ?, gender = ? where id = ?;";
        // 3. 获得预编译对象
        PreparedStatement ps = conn.prepareStatement(sql);

        // 4. 给占位符赋值
        ps.setString(1, "嫦娥");
        ps.setString(2, "女");
        ps.setInt(3, 3);
        // 5. 执行SQL语句
        int row = ps.executeUpdate();
        System.out.println(row);
        // 6. 关闭资源
        JDBCUtils.close(conn, ps);
    }

    // 删除数据: 删除id为3的学生
    @Test
    public void deleteStudent() throws Exception {
        // 1. 获得连接对象
        Connection conn = JDBCUtils.getConnection();
        // 2. 准备SQL语句
        String sql = "delete from student where id = ?;";
        // 3. 获得预编译对象
        PreparedStatement ps = conn.prepareStatement(sql);
        // 4. 给占位符赋值
        ps.setInt(1, 3);
        // 5. 执行SQL语句
        int row = ps.executeUpdate();
        System.out.println(row);
        // 6. 关闭资源
        JDBCUtils.close(conn, ps);
    }
}

小结:
PreparedStatement实现增删改的步骤
1、获得连接对象
2、准备SQL语句
3、获得预编译对象
4、给占位符赋值
5、调用预编译对象的executeUpdate方法执行SQL
6、关闭资源

5、PreparedStatement实现查询操作

需求:查询id小于4的学生信息,并保存将每一个记录封装成一个学生对象存储到List集合中

创建学生类

// 学生类
package com.itheima._09预编译对象;

import java.sql.Date;

public class Student {
    private int id;
    private String name;
    private String gender;
    private Date birthday;

    public Student() {
    }

    public Student(int id, String name, String gender, Date birthday) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.birthday = birthday;
    }

    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 String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", birthday=" + birthday +
                '}';
    }
}

使用PreparedStatement对数据库进行查询

package com.itheima._09预编译对象;

import com.itheima._07JDBC工具类.JDBCUtils;
import org.junit.Test;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

/**
    目标:使用PreparedStatement实现查询功能
    需求:查询所有学生,将每一个记录封装成一个学生对象存储到List集合中
 */
public class JDBCDemo02 {
    public static void main(String[] args) throws Exception{
        // 1. 获得连接对象
        Connection conn = JDBCUtils.getConnection();
        // 2. 获得预编译对象
        PreparedStatement ps = conn.prepareStatement("select * from student");
        // 3. 执行SQL语句获得结果集对象
        ResultSet rs = ps.executeQuery();
        // 4. 创建集合存储学生对象
        List<Student> list = new ArrayList<>();
        // 5. 遍历结果集对象
        while (rs.next()){
            // 5.1 获得每一列的值给对应成员变量赋值
            int id = rs.getInt("id");
            String name = rs.getString("name");
            String gender = rs.getString("gender");
            Date birthday = rs.getDate("birthday");

            // 5.2 每遍历到一条记录就创建学生对象
            Student stu = new Student(id,name,gender,birthday);

            // 5.3 将学生对象添加到集合中
            list.add(stu);
        }
        // 6. 遍历集合输出学生信息
        for (Student student : list) {
            System.out.println(student);
        }
        // 7. 关闭资源
        JDBCUtils.close(conn, ps,rs);
    }
}

小结
PreparedStatement实现查询的步骤
1、获得连接对象
2、准备SQL语句
3、获得预编译对象
4、给占位符赋值
5、调用预编译对象的executeQuery方法执行SQL
6、关闭资源

八、使用PreparedStatement改造登录案例(解决SQL注入问题)

public class Demo11 {
   public static void main(String[] args) throws Exception {
      // 让用户输入账号和密码
      Scanner sc = new Scanner(System.in);
      System.out.println("请输入账号: ");
      String name = sc.nextLine();
      System.out.println("请输入密码: ");
      String password = sc.nextLine();

      // 获取连接
      Connection conn = JDBCUtils.getConnection();
      // 编写SQL语句,账号和密码使用?占位
      String sql = "SELECT * FROM user WHERE name=? AND password=?;";
      // 获取到PreparedStatement对象
      PreparedStatement pstmt = conn.prepareStatement(sql);
      // 设置参数
      pstmt.setString(1, name);
      pstmt.setString(2, password);
      // pstmt.setString(2, "a' or '1'='1");
      
      // 如果查询到数据,说明登录成功,如果查询不到数据,说明登录失败
      ResultSet rs = pstmt.executeQuery();

      if (rs.next()) {
         // 能进来查询到了数据.
         String name2 = rs.getString("name");
         System.out.println("欢迎您," + name2);
      } else {
         // 查询不到数据,说明登录失败
         System.out.println("账号或密码错误...");
      }

      JDBCUtils.close(conn, pstmt, rs);
   }
}

案例效果
输入 “a’ or ‘1’='1” 作为密码
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值