JDBC(超详细)

目录
一、前置准备
二、JDBC连接数据库
1. 方式一
2. 方式二
3. 方式三
4. 方式四
5. 方式五(推荐)
三、ResultSet结果集
1. 获取结果集ResultSet的步骤
2. 如何获取到ResultSet结果集
3. ResultSet 底层结构
四、SQL注入问题
五、Preparment(预处理)
1. 步骤
2. 案例演示
3. Statement 和 PreparedStatement的使用对比
六、JDBC相关的API
七、工具类的编写
八、事务
1. 概念
2. 使用场景
3. 事务中需要使用到的API
4. 事务使用实例
九、批处理
1. 批处理用到的API
2. addBatch源码分析
十、总结


相关文章

MysqlMysql

一、前置准备

 在正式开始学习JDBC前,我需要去Mysql官网下载JDBC的驱动包。我这里Mysql版本是8.0,所以驱动版本也要与其对应。
📥Mysql驱动下载地址

在这里插入图片描述

二、JDBC连接数据库

1. 连接步骤

JDBC连接数据库步骤:👇

在这里插入图片描述

2. JDBC的几种连接方式

2.1 方式一

// 注册驱动
Driver driver = new Driver();
String url = "jdbc:mysql://localhost:3306/bd1?serverTimezone=UTC"; 
Properties properties = new Properties();
properties.setProperty("user", "用户名");
properties.setProperty("password", "密码");
// 得到连接
Connection connect = driver.connect(url, properties);

 👆因为我们驱动是8.0版本,所以url后边加了时区serverTimezone=UTC

2.2 方式二

// 注册驱动
Class aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/bd1?serverTimezone=UTC";
Properties properties = new Properties();
properties.setProperty("user", "用户名");
properties.setProperty("password","密码");
// 得到连接
Connection connect = driver.connect(url, properties);

 👆这种方式是通过反射来创建Driver驱动的。8.0版本对应的驱动类全类名是:com.mysql.cj.jdbc.Driver5.7版本对应的全类名是com.mysql.jdbc.Driver

2.3 方式三

// 注册驱动
Class aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
// 通过DriverManager注册驱动
DriverManager.registerDriver(driver);
String url = "jdbc:mysql://localhost:3306/bd1?serverTimezone=UTC";
String user = "用户名";
String password = "密码";
// 得到连接
Connection connection = DriverManager.getConnection(url, user, password);

 👆这种方式没有创建properties对象,而且最后是使用DriverManager来获取Connection连接的。

2.4 方式四

// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/bd1?serverTimezone=UTC";
String user = "用户名";
String password = "密码";
// 得到连接
Connection connection = DriverManager.getConnection(url, user, password);

 👆这种连接方式和第三种连接方式相比省略了些代码。但这种方式仍然可以获取到Connection连接。
 至于为什么不需要那些省略的代码,也可以获取连接的原因如下:👇

static {
    try {
        DriverManager.registerDriver(new Driver());
    } catch (SQLException var1) {
        throw new RuntimeException("Can't register driver!");
    }
} // 底层在进行类加载的时候就进行了 驱动的注册。

💡补充:

mysqL驱动5.1.6可以无需Class.forName(“com.mysql.jdbc.Driver”)。从jdk1.5以后使用了jdbc4,不再需要显式调用class.forName()注册驱动而是自动调用驱动jar包下META-INF.sqI.Driver文本中的类名称去注册(驱动8.0版本往后类名都是com.mysql.cj.jdbc.Driver)。但建议还是写上 Class.forName(“com.mysql.jdbc.Driver”),更加明确。
在这里插入图片描述

2.5 方式五(推荐)

Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
Class.forName(driver);
// 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);

 👆上边这种方式我们可以读取properties文件中配置的驱动信息用户名密码等信息。可以很方便的通过修改配置文件来达到连接不同数据库的目的,更加的灵活。

properties配置文件:👇

#Mysql
driverClassNam=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/goodsSystem?serverTimezone=UTC
username=用户名
password=密码

三、ResultSet结果集

 通过上边的知识了解了如何连接到数据库,接下来我们继续讲解,如何从数据库查询到数据。

1. 获取结果集ResultSet的步骤

在这里插入图片描述

2. 如何获取到ResultSet结果集

📌演示

@Test
public void jdbc07() throws IOException, ClassNotFoundException, SQLException {
    Properties properties = new Properties();
    properties.load(new FileReader("src\\mysql.properties"));
    String url = properties.getProperty("url");
    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    String driver = properties.getProperty("driver");
    // 1.注册驱动
    Class.forName(driver);
    // 2.获得驱动
    Connection connection = DriverManager.getConnection(url, user, password);
    String sql  = "select * from  `news`";
    // 3.得到Statement对象
    Statement statement = connection.createStatement();
    // 4.获得ResultSet结果集
    ResultSet resultSet = statement.executeQuery(sql); //通过statement对象调用executeQuery方法,执行sql语句,返回一个ResultSet对象。
    // 5.使用while循环取出ResultSet中的数据
    while (resultSet.next()){ //相当于一个指针,开始指向整个结果集第一行数据之前,每调用一次就向后移动一次,若没有数据就返回false。
        int id = resultSet.getInt(1); //getxxx()方法中的参数填的是列数,就是当前行的第几列数据,返回值就是对应位置的数据。
        String name = resultSet.getString(2);
        String news = resultSet.getString(3);
        System.out.println(id + "\t" + name + "\t" + news);
    }
    // 6.关闭连接
    connection.close();
    statement.close();
    resultSet.close();
}

3. ResultSet底层结构

在这里插入图片描述

ResultSet对象里面有一个rowData。数据实际在elementData中,因为查询的结果只有两行,所以elementData数组中只有两个,它们是以字节数组的形式出现的。其中internalRowData中出现了三个字节数组,则代表有三列。

四、SQL注入问题

 有关SQL注入问题,我们通过下边的代码来说明:👇

public class testStatement{
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入用户名"); // 1' or
        String userName = scanner.nextLine(); // or '1'= '1
        System.out.print("请输入密码");
        String pwd = scanner.nextLine();
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        Class.forName(driver);
        Connection connection = DriverManager.getConnection(url, user, password);
        Statement statement = connection.createStatement();
        System.out.println(connection);
        String sql = "select id, pwd from `actor` where id = '"
                + userName + "' and pwd = '"
                + pwd +"'" ;
        ResultSet resultSet = statement.executeQuery(sql);
        if (resultSet.next()){
            System.out.println("成功");
        }else {
            System.out.println("失败");
        }
        connection.close();
        statement.close();
        resultSet.close();

    }
}

📌actor

idpwd
tom123

 上边的代码,我们通过查询actor表来获取结果,可以看到,根据我们写的SQL语句只有满足 id=tom 并且 pwd=123 条件的时候,他才会返回结果,并且输出成功。但由于存在SQL注入的问题,这里我们输入id为1' or,pwd为or '1'= '1的时候也会返回结果,而且也输出了成功。当然出现SQL注入的情况有很多种,但我们这里只研究引起我们这个程序出现问题的根源:这就是因为使用了Statement,会有SQL注入的风险。而我们想要解决这种问题就得引出我们下边要将的知识点:Preparment

五、Preparment(预处理)

🎈在此之前我们和前边一样,先给出使用Preparment获取结果集的步骤。

1. 步骤

在这里插入图片描述

2. 案例演示

public class testPreparStatement {
    public static void main(String[] args) throws Exception{
        String pwd = scanner.nextLine();
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        // 1. 注册驱动
        Class.forName(driver);
        // 2. 获得连接
        Connection connection = DriverManager.getConnection(url, user, password);
        // 3. 组织sql
        String sql = "select id , pwd from `actor` where id =? and pwd=?";
        // 4. 得到PreparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        // 5. 配置参数(给?赋值)
        preparedStatement.setString(1,"值");
        preparedStatement.setString(2,"值");
        ResultSet resultSet = preparedStatement.executeQuery();
        if (resultSet.next()){
            System.out.println("success");
        }else {
            System.out.println("faile");
        }
        // 6. 关闭连接
        connection.close();
        preparedStatement.close();
        resultSet.close();
    }
}
  1. ?为占位符,用来代表不确定的值。
  2. PreparedStatement中的setXxx方法中的参数,第一个参数是取代第几个?,第二个参数是具体的值。

3. Statement 和 PreparedStatement 的使用对比

在这里插入图片描述
🚫注意:

 和以前举的例子不同之处在于,通过connection获得PreparedStatement对象的时候,就需要填入参数(sql语句),而不是在执行executeQuery的时候写参数(sql语句),如果此时写上sql则会报错。除非没有?占位符

六、JDBC相关的API

在这里插入图片描述

七、工具类的编写

 通常我们会将重复操作相同的逻辑代码抽取出来,封装成一个类,方便我们来使用。这里我们封装一个获取Connection关闭Connection、关闭ResultSet、关闭Statement的工具类。

import javax.management.RuntimeMBeanException;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
 * @author long
 * @date 2022/11/7
 */
public class JdbcUtil {
    private static String url;
    private static String user;
    private static String password;
    private static String driver;
    static {
        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("src\\DateBase_select\\mysql.properties"));
            url = properties.getProperty("url");
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            driver = properties.getProperty("driver");
        } catch (IOException e) {
            throw new RuntimeException();
        }
    }

    public static Connection getConnection(){
        try {
            return DriverManager.getConnection(url,user,password);
        } catch (SQLException throwables) {
            throw new RuntimeException();
        }
    }
    public static void close(ResultSet resultSet,Statement statement, Connection connection){
        try {
            resultSet.close();
            statement.close();
            connection.close();
        } catch (SQLException throwables) {
            throw new RuntimeException();
        }
    }
    public static void close(Statement statement, Connection connection){
        try {
            statement.close();
            connection.close();
        } catch (SQLException throwables) {
            throw new RuntimeException();
        }
    }
}

👆工具类中关于异常,我们的处理方式是向调用者抛出一个运行时异常,这种做法可以配合我们业务中的一些逻辑来完成一些东西(例如:配合ThreadLocal来捕获这个异常来进行事务的回滚)。
 细心的小伙伴还会发现一个地方,工具类close()的参数中有一个Statement,这里用Statement而不用PreparedStatement是因为:StatementPreparedStatement父接口Statement可以兼容这两种不同的对象。

八、事务

1.概念

什么是事务❓
事务是指单个逻辑工作单元执行的一系列操作,要么都做,要么都不做,是不可分割的工作单位,是数据库环境中的的最小工作单元

2. 使用场景

银行转账,甲给乙转账。
​ 正常情况==>甲账户减去100,乙账户加上100。
​ 异常情况==>甲账户减去100,中途出现异常,乙账户未加上100。
解决方案:使用事务处理。事务可以保证整个流程走完之后,才将最终的结果提交给数据库。

3. 事务中需要使用到的API

在这里插入图片描述
💡上述方法的一些说明

  1. connection.setAutoCommit(false); // 取消事务的自动提交
  2. connection.rollback(); // 事务回滚
    参数为空的时候,回滚到事务的开始。如果有参数的时候,返回到回滚点。
  3. connection.commit(); // 提交结果给数据库

4. 事务使用实例

public class Transaction {
    public static void main(String[] args) {
        Connection connection = null;
        String sql1 = "update `actor` set salary = salary + 100 where `name` = '小王'";
        String sql2 = "update `actor` set salary = salary - 100 where `name` = '小李'";
        PreparedStatement preparedStatement = null;
        try {
            connection = JdbcUtil.getConnection();
            connection.setAutoCommit(false);
            preparedStatement = connection.prepareStatement(sql1);
            preparedStatement.executeUpdate();
            int i = 1/0;
            preparedStatement = connection.prepareStatement(sql2);
            preparedStatement.executeUpdate();
            connection.commit();
        } catch (SQLException throwables) {
            try {
                connection.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            throwables.printStackTrace();
        }finally {
            JdbcUtil.close(preparedStatement,connection);
        }

    }
}

 👆当我们的语句执行到int i=1/0;的时候,会抛出一个异常try-catch捕获到异常之后,我们就会执行connection.rollback();来进行事务的回滚

九、批处理

使用批处理前,我们必须在URL的后边加上rewriteBatchedStatements=true

批处理顾名思义就是批量处理,就相当于一个公交车拉乘客,每条sql就是一个乘客,而批量包处理就是公交车,等公交车上人坐满了发车,肯定效率高于一次拉一个乘客效率来的高。

在这里插入图片描述

1. 批处理用到的API

在这里插入图片描述
💡说明:

  1. preparedStatement.addBatch();要在设置完SQL语句之后执行。
  2. 在执行完批处理包之后,需要清空

2. addBatch源码分析

📌addBatch源码

public void addBatch() throws SQLException {
    try {
        synchronized(this.checkClosed().getConnectionMutex()) {
            QueryBindings<?> queryBindings = ((PreparedQuery)this.query).getQueryBindings();
            queryBindings.checkAllParametersSet();
            this.query.addBatch(queryBindings.clone()); // 此处进入addBatch
        }
    } catch (CJException var6) {
        throw SQLExceptionsMapping.translateException(var6, this.getExceptionInterceptor());
    }
}

📌this.query.addBatch源码

// this.query.addBatch 源码
public void addBatch(Object batch) {
    if (this.batchedArgs == null) {
        this.batchedArgs = new ArrayList(); //判断batchedArgs是否为空,若为分配ArrayList()数组
    }
    this.batchedArgs.add(batch); //添加sql数据进入到批处理包。
}

批处理包中每条sql数据实际的存储位置:preparedStatement➡query➡batchedArgs(ArrayList数组)➡elementData(对象数组)➡bindValues➡values(字节数组)。在values中以ASCLL码形式存储。如下图:👇

在这里插入图片描述

 👆上图bindValues数组中只有两个值,这是因为我们在设置sql语句的时候使用了两个?占位符。这里也可以看出value字节数组中的首尾都为 39,而 39 对应的ASCLL编码为'(单引号),我这里使用的是语句是preparedStatement.setString(1,"jack"+0);。从这可以看出他把给String语句自动加上了单引号。
 ArrayList数组初始空间为10,扩容为容量的1.5倍。

十、总结

 以上就是我们这次要将的全部内容。从JDBC驱动下载,到日常需要使用到的常用操作。有关数据库连接池的内容,我们下节接着讲!🚩

最后希望大家多多 关注+点赞+收藏^_^,你们的鼓励是我不断前进的动力!!!
感谢感谢~~~🙏🙏🙏

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艺术留白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值