如何使用JDBC操作数据库?JDBC API的使用详细解读

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1. DriverManager

DriverManager 驱动管理类。在 JDBC 入门篇中我们使用了该类的方法来注册驱动和获取连接。

DriverManager 类主要有两个作用

  1. 注册驱动
  2. 获取连接

1.1 注册驱动

在 Driver 类静态代码块中 DriverManager 类执行了其 registerDriver() 方法用于注册驱动当我们把类Driver 加载到内存中后该静态代码块就会执行此时就完成了驱动注册我们就是使用这样的方法注册驱动的。查看 JDK 源码就不难理解

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

需要注意的是在 MySQL 5 以后的驱动 jar 包可以不用进行此步骤来注册驱动当我们把 jar 导入到项目以后程序会自动加载 jar 包中 META-INF/services/java.sql.Driver 文件中的驱动类。

1.2 获取连接

我们使用 DriverManager 类的 getConnection() 静态方法来获取数据库连接对象其方法有三个参数分别是 url数据库的用户名和密码。url 有其固定的语法格式

jdbc:mysql://ip地址(域名):端口号/连接的数据库名?参数键值对1&参数键值对2

jdbc:mysql://连接的数据库名?参数键值对1&参数键值对2 //如果使用的是本地的 MySQL 并且使用默认的端口号 3306 的url 的值可以简写

例如

jdbc:mysql://db1?useSSL=false

这里 useSSL = false 关闭了安全连接方式解决了 idea 警告的问题。

2. Connection

Connection 数据库连接对象接口。在入门篇中使用了该类获取 sql 的执行对象 statement。

Connection 接口的作用有

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

2.1 获取执行sql的对象

Connection 中的 createStatement() 方法可以获取执行 sql 的对象 Statement 用于把 sql 发送到数据库服务端。使用 preparedStatement() 方法可以获取预编译 sql 的执行 sql 的对象这个方法可以有效的防止 sql 注入的问题。

2.2 事务管理

在JDBC 中使用 Connection 对象进行事务管理Connection 中定义了三个对应的方法

开启事务

void setAutoCommit(boolean autoCommit) //将此连接的自动提交模式设置为给定状态

回滚事务

void rollback() //撤消当前事务中所做的所有更改并释放此 Connection对象当前持有的所有数据库锁

提交事务

void commit() //使自上次提交/回滚以来所做的所有更改成为永久更改并释放此 Connection对象当前持有的所有数据库锁

示例

public class JDBCDemo {

    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 ppassword = "abc123";
        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);
            int i = 3 / 0;
            //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();
    }
}

我们使用 Java 中的异常捕获机制来进行事务的管理。将执行 sql 的语句放在 try 代码块中并且使用 setAutoCommit() 方法开启事务如果代码块中没有出现异常则使用 commit() 方法提交事务如果 try 代码块某处出现了异常则需要回滚事务所以将回滚事务的 rollback() 方法定义在 catch 语句块中。

3. Statement

Statement 类的对象用来执行 sql 语句即把 sql 发送到数据库服务端但是使用此类对象执行 sql 会出现 sql 注入的问题。不同的 sql 语句使用不同的方法执行执行 DDLDML 语句使用下面的方法

int executeUpdate(String sql)//执行sql语句完成增删改操作

在执行 DML 语句完成对数据的增删改操作时该方法返回数据表中受影响的行数可以使用这个返回值来判断是否成功完成对数据的操作。而使用 DDL 来操作数据库和数据表时返回值可能为 0所以不能用作上面的判断且在开发中 DDL 很少被用到。

示例1使用 DML 修改数据

//3.定义sql
String sql="update account set money=2000 where name='张三'";
//5.执行sql
int count = stmt.executeUpdate(sql);
//6.处理结果
if(count>0){
    System.out.println("修改成功");
}else{
     System.out.println("修改失败");
}

示例2使用 DDL 删除数据库

//3.定义sql
String sql="drop database db2";
//4.获取执行sql的对象
Statement stmt = conn.createStatement();
//5.执行sql
int count = stmt.executeUpdate(sql);
//6.处理结果
System.out.println(count);

此时控制台返回了 0 但是对数据库的删除操作已经完成。

image-20230123111252544

执行 DQL 语句时需要使用下面的方法

ResultSet excuteQuery(String sql)//执行sql语句完成查询操作返回单个ResultSet对象

该方法在下面 ResultSet类中讲解。

4. ResultSet

ResultSet 结果集对象类其作用是封装 sql 查询语句的结果。执行了 DQL 查询语句后就会返回该类的对象执行 DQL 语句的方法如下

ResultSet excuteQuery(String sql)//执行sql语句完成查询操作返回单个ResultSet对象

ResultSet 类提供了操作查询结果数据的方法如下

/*
作用:将指针从当前位置移动到下一行然后这一行是否为有效行
返回值true 当前行为有效行false 当前行没有数据
*/
boolean next()
/*
作用获取数据  
xxx表示数据类型 例如 int getInt() 
参数int类型的参数表示列的编号这个编号从0开始;String类型的参数表示列的名称
*/
xxx getXxx(参数)
    
    

操作查询结果数据的方法如下图

一开始指针位于第一行前如图所示红色箭头指向于表头行。当我们调用了 next() 方法后指针就下移到第一行数据并且方法返回 true此时就可以通过 getInt("id") 获取当前行 id 字段的值也可以通过 getString("name") 获取当前行 name 字段的值。如果想获取下一行的数据继续调用 next() 方法以此类推。获取某个字段的值时既可以传入 int 类型即列的编号也可以传入列对应的字段名。

示例查询 account 表中数据并且打印所有结果

public class JDBCDemo {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
        String url = "jdbc:mysql://localhost:3306/db1?useSSL=false";
        String username = "root";
        String ppassword = "abc123";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3. 定义sql
        String sql = "select * from account";
        //4. 获取执行sql对象
        Statement stmt = conn.createStatement();
        //5. 执行sql
        ResultSet rs = stmt.executeQuery(sql);
        //6. 处理返回结果 遍历rs中的所有数据
        // 1 指针向下移动一行并且判断当前行是否有数据
        while (rs.next()) {
            // 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);
        }
        //7. 释放资源
        rs.close();
        stmt.close();
        conn.close();
    }
}

5. PreparedStatement

5.1 sql注入问题

前面 statement 类的对象用来执行sql语句例如

int executeUpdate(String sql)//执行sql语句完成增删改操作

但是使用此方法存在 sql 注入的问题什么是 sql 注入呢这里做一个大致的讲解。

SQL注入是通过操作输入来修改事先定义好的 sql 语句用来达到执行代码对服务器进行攻击的方法。例如在程序的登录操作中用户输入的用户名和密码会被发送到 Java 代码然后用于 Java 操作数据库的sql 语句中只有当用户输入的用户名和密码与数据库中的数据匹配时才能实现登录但是只要我们输入事先定义好的语句便可以进行破解。例如下面拼字符串的方式修改 sql 语句原来的含义

String name ="lisi";
String ppwd="' or '1' ='1";//事先定义好的输入拼接到sql语句中后改变其含义
String sql = "Select * from tb_user where name='"+username+"' and ppassword='"+ppwd+"'";

此时将用户名和密码拼接到 sql 中如下

select * from tb_user where username = 'lisi' and ppassword = ''or '1' = '1';

可以看到sql 中的判断条件永远为 true 不管输入什么样的用户名这条 sql 都成立实现了 sql 注入。

image-20230123152756547

为了解决这个问题出现了 preparedStatement 该类用于预编译 sql 语句并执行其优点是可以防止sql 注入并且预编译sql提高了性能。其实底层是将特殊字符进行了转义转义的 sql 如下

select * from tb_user where username = 'lisi' and ppassword = '\'or \'1\' = \'1'

示例

public class JDBCDemo {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接
        String url="jdbc:mysql://localhost:3306/db1?useSSL=false";
        String username="root";
        String ppassword="abc123";
        Connection conn = DriverManager.getConnection(url,username,password);
        //Java代码接收到客户端发送的用户名和密码
        String name="lisi";
        String ppwd="' or '1' ='1";
        //3. 定义sql
        String sql="select * from tb_user where username=? and password=?";
        //4. 获取执行sql的对象psmt
        PreparedStatement psmt = conn.prepareStatement(sql);
        //设置问号的值
        psmt.setString(1,name);
        psmt.setString(2,pwd);
        //5. 执行sql
        ResultSet rs = psmt.executeQuery();//此时不再需要传入sql语句
        //6. 处理返回结果
        if(rs.next()){
            System.out.println("yes");
        }else{
            System.out.println("no");
        }
        //7. 释放资源
        rs.close();
        psmt.close();
        conn.close();
    }
}

image-20230123152857215

5.2 preparedStatement 原理

前面使用 preparedStatement 解决了 sql 注入的问题其实我们还没有开启预编译的功能JDBC 中是如何通过预编译来提高性能的呢

要学习 prepareStatement 实现预编译的原理首先要明白 Java 操作数据库的步骤

首先 Java 代码将 sql 发送到 MySQL 服务端MySQL 服务端接收到 sql 语句以后会对 sql 语句进行检查检查 sql 语句的语法编译编译 sql 语句将 sql 语句编译成可执行的函数执行的操作。而检查和编译 sql 语句花费的时间往往较长如果想要提高 sql 的性能就可以从这方面入手。在使用预编译的方法时检查和编译 sql 语句的操作将会在获取执行 sql 的对象时完成并且不会重复执行从而提高了性能。

image-20230123155242436

要想打开预编译的功能就需要在 url 中设置如下的参数

useServerPrepStmts=true //参数开启预编译功能

示例

public class JDBCDemo {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接
        String url="jdbc:mysql://localhost:3306/db1?useSSL=false&useServerPrepStmts=true";
        String username="root";
        String ppassword="abc123";
        Connection conn = DriverManager.getConnection(url,username,password);
        //Java代码接收到客户端发送的用户名和密码
        String name="lisi";
        String ppwd="'or '1' = '1";
        //3. 定义sql
        String sql="select * from tb_user where username=? and password=?";
        //4. 获取执行sql的对象psmt
        PreparedStatement psmt = conn.prepareStatement(sql);

        //设置问号的值
        psmt.setString(1,name);
        psmt.setString(2,pwd);
        ResultSet rs=null;
        //5. 执行sql
        rs = psmt.executeQuery();//此时不需要传入sql语句

        psmt.setString(1,"zhangsan");
        psmt.setString(2,"abc123");
        rs=psmt.executeQuery();

        //6. 处理返回结果
        if(rs.next()){
            System.out.println("yes");
        }else{
            System.out.println("no");
        }
        //7. 释放资源
        rs.close();
        psmt.close();
        conn.close();
    }
}

在获取 sql 的执行对象时sql 会作为参数被发送到 MySQL 服务端进行检查和编译在执行时不再进行第二次检查和编译的操作提高了 sql 执行的性能。

6. 总结

文章代码中有密码单词敏感由于发文助手的提示密码全部使用ppassword代替。本文中主要对 JDBC 中常用的类和接口以及方法做了详细的描述。文章介绍的类或者方法有

  • DriverManager
  • Connection
  • Statement
  • ResultSet
  • PreparedStatement

下期见。


Java编程基础教程系列

【Java IO流】缓冲流及原理详解

MySQL详细教程2023年硬核学习路线

JDBC快速入门如何使用JDBC操作数据库

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: 数据库