JDBC 常用 API 讲解 📚

310 阅读7分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情

写在前面👀

今天讲讲JDBC的常用API:DriverManager 类Connection 接口Statement 接口ResultSet 接口PreparedStatement 接口

一、DriverManager 类🍕

数据库驱动管理类,用来注册驱动连接数据库

1️⃣注册驱动🍔

  • 我们加载注册驱动用的是以下代码👇
Class.forName("com.mysql.cj.jdbc.Driver");
  • 其实在Driver接口的实现类里出现过DriverManager类registerDriver注册驱动方法
  • com.mysql.jdbc.Driver 源码如下👇
    • 使用DriverManager注册给定的驱动程序。 新加载的驱动程序类应调用方法registerDriver以使其自身为DriverManager
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!");
        }
    }
}
  • 提示:目前普遍使用的JDBC版本已经可以不用注册驱动而直接使用。

2️⃣连接数据库🍟

1. getConnection方法🌭

/* DriverManager类中的静态方法 */
Connection getConnection (String url, String user, String password) 
/* 示例 */
Connection connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "root", "123456");

2.连接数据库的几个参数🍿

JDBC 连接数据库的三个参数说明
url连接数据库的 URL 地址格式:协议名:子协议://服务器名或 IP 地址:端口号/数据库名?参数=参数值
连接MySQL示例:jdbc:mysql://localhost:3306/db
user数据库登录的用户名
password数据库登录的密码
  • 提示:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称

二、Connection 接口🧂

Connection 接口,具体的实现类由数据库的厂商实现,代表一个连接对象。

主要作用:1.获取执行SQL的对象 2.管理事务

1️⃣获取执行SQL的对象🥚

//普通执行SQL对象
Statement createStatement()
//预编译SQL的执行SQL对象:防止SQL注入
PreparedStatement  prepareStatement(sql)
//执行存储过程的对象
CallableStatement prepareCall(sql)

2️⃣事务管理🍳

1.开启事务🧇

void setAutoCommit (boolean autoCommit) throws SQLException
  • 参数autoCommit true表示启用自动提交模式; false表示启用手动提交事务
  • MySQL默认是自动提交事务的,所以开启事务需要将autoCommit设置为false

2.提交事务🥞

void commit() throws SQLException

3.回滚事务🧈

void rollback() throws SQLException

4.代码示例🍞

try {
    // 关闭自动提交:
    conn.setAutoCommit(false);
    // 执行多条SQL语句:
    ......
    // 提交事务:
    conn.commit();
} catch (SQLException e) {
    // 回滚事务:
    conn.rollback();
} finally {
    //恢复默认自动提交
    conn.setAutoCommit(true);
    //释放资源
    conn.close(); 
}

三、Statement 接口🥐

用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象

1️⃣执行DDL、DML语句🥨

int executeUpdate (String sql) throws SQLException
  • SQL语句可以是INSERTUPDATE ,或DELETE处理数据的DML语句,int返回受影响的行数;也可以是和数据库、表相关的DLL语句,不过DDL语句是没有返回值的

2️⃣执行DQL查询语句🥯

ResultSet executeQuery (String sql) throws SQLException
  • 返回的ResultSet对象是一个结果集

四、ResultSet 接口🥖

作用:封装数据库查询的结果集。结果集指包含在 ResultSet 对象中的行和列的数据

1️⃣常用方法🧀

ResultSet 接口中的方法描述
boolean next()1. 游标向下移动 1 行
2. 返回 boolean 类型,如果下一行还有记录,返回 true,否则返回 false
数据类型 getXxx()1. 通过字段名,参数是 String 类型。返回不同的类型
2. 通过列号,参数是整数,从 1 开始。返回不同的类型

2️⃣常用数据类型转换表🥗

SQL 类型JDBC对应方法返回类型
BIT(1) bit(n)getBoolean()boolean
TINYINTgetByte()byte
SMALLINTgetShort()short
INTgetInt()int
BIGINTgetLong()long
CHAR,VARCHARgetString()String
Text(Clob) BlobgetClob getBlob()Clob Blob
DATEgetDate()java.sql.Date 只代表日期
TIMEgetTime()java.sql.Time 只表示时间
TIMESTAMPgetTimestamp()java.sql.Timestamp 同时有日期和时间

3️⃣代码示例🥙

  • 使用完毕以后先关闭结果集 ResultSet,再关闭 Statement,再关闭 Connection
 @Test
    public void testResultSet() throws  Exception {
        //1. 注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
        String url = "jdbc:mysql:///db";
        String username = "root";
        String password = "123456";
        Connection conn = DriverManager.getConnection(url, username, password);

        //3. 定义sql
        String sql = "select * from stu_info";

        //4. 获取statement对象
        Statement stmt = conn.createStatement();

        //5. 执行sql
        ResultSet rs = stmt.executeQuery(sql);

        // 创建集合
        List<Student> list = new ArrayList<>();

        // 6.1 光标向下移动一行,并且判断当前行是否有数据
        while (rs.next()){
            Student Student = new Student();

            //6.2 获取数据  getXxx()
            int id = rs.getInt("id");
            String name = rs.getString("name");
            int age = rs.getInt("age");
            byte gender = rs.getByte("gender");
            float math = rs.getFloat("math");
            float english = rs.getFloat("english");
            String hometown =rs.getString("hometown");
            //6.2赋值
            Student.setId(id);
            Student.setName(name);
            Student.setAge(age);
            Student.setGender(gender);
            Student.setEnglish(english);
            Student.setMath(math);
            Student.setHometown(hometown);
            //6.3存入集合
            list.add(Student);
        }
        //6.4打印集合
        System.out.println(list);

        //7. 释放资源
        rs.close();
        stmt.close();
        conn.close();
    }
  • 演示结果如下👇

image-20220413154602595

五、PreparedStatement 接口🥪

PreparedStatement 是 Statement 接口的子接口,继承父接口中所有的方法。它能预编译 SQL 语句,能有效防止SQL注入问题。

1️⃣SQL注入问题🌮

SQL 注入就是在用户输入的字符串中加入 SQL 语句,修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法

  • 用代码模拟一下SQL注入👇
public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //连接数据库
        String url = "jdbc:mysql:///db";
        String username = "root";
        String password = "123456";
        Connection conn = DriverManager.getConnection(url, username, password);
        // 接收用户输入 用户名和密码
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入用户名:");
        String name = scanner.nextLine();
        System.out.print("请输入密码:");
        String pwd = scanner.nextLine();
        String sql = "select * from user where name = '" + name + "' and password = '" + pwd + "'";
        // 获取stmt对象
        Statement stmt = conn.createStatement();
        // 执行sql
        ResultSet rs = stmt.executeQuery(sql);
        // 判断登录是否成功
        if(rs.next()){
            System.out.println("登录成功~");
            System.out.println("id:"+rs.getInt("id") );
            System.out.println("真正的用户名:"+rs.getString("name"));
            System.out.println("真正的密码:" + rs.getString("password"));
        }else{
            System.out.println("登录失败~");
        }

        //7. 释放资源
        rs.close();
        stmt.close();
        conn.close();
    }
  • 演示结果如下👇image-20220413220346630

  • 可以发现即使输入错误的用户名和密码也能登录成功,所以说SQL注入很危险

  • 实际原理也很简单,就是利用or 后面的**'1'='1'**image-20220413213346326

2️⃣使用 步骤🌯

  1. 编写 SQL 语句,待输入内容使用?占位,如"SELECT * FROM user WHERE name=? AND password=?"
  2. 获得 PreparedStatement 对象
  3. 设置实际参数:setXxx(占位符的位置, 真实的值)
  4. 执行参数化 SQL 语句
  5. 关闭资源
PreparedStatement 中设置参数的方法
void setDouble(int parameterIndex, double x)
void setFloat(int parameterIndex, float x)
void setInt(int parameterIndex, int x)
void setLong(int parameterIndex, long x)
void setObject(int parameterIndex, Object x)
void setString(int parameterIndex, String x)
  • 举个栗子
public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //连接数据库
        String url = "jdbc:mysql:///db";
        String username = "root";
        String password = "123456";
        Connection conn = DriverManager.getConnection(url, username, password);
        // 接收用户输入 用户名和密码
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入用户名:");
        String name = scanner.nextLine();
        System.out.print("请输入密码:");
        String pwd = scanner.nextLine();
        String sql = "select * from user where name = ? and password = ?";
        // 获取prstmt对象
        PreparedStatement prstmt = conn.prepareStatement(sql);
        prstmt.setString(1, name);
        prstmt.setString(2, password);
        // 执行sql
        ResultSet rs = prstmt.executeQuery();
        // 判断登录是否成功
        if(rs.next()){
            System.out.println("登录成功~");
            System.out.println("id:" + rs.getInt("id"));
            System.out.println("真正的用户名:" + rs.getString("name"));
            System.out.println("真正的密码:" + rs.getString("password"));
        }else{
            System.out.println("登录失败~");
        }

        //7. 释放资源
        rs.close();
        prstmt.close();
        conn.close();
    }
  • 演示结果如下👇image-20220413220535704

  • 可以发现PreparedStatement确实可以防止SQL注入

3️⃣PreparedSatement 的执行原理🍖

  • Satement对象会将每条SQL语句都发送给数据库检查和编译,数据库再执行,效率很低。而prepareStatement(sql)会先将SQL语句发送给数据库预编译,可以多次传入不同的参数给PreparedStatement对象并执行,检查SQL语句和编译SQL语句将不需要重复执行,这样就提高了性能。

1.打开预编译功能🍗

/* 在连接数据库的uel地址后加入下面的参数 */
useServerPrepStmts=true
/* 示例 */
jdbc:mysql:///db?useServerPrepStmts=true

2.开启MySQL日志🥩

  • 在mysql配置文件(my.ini)中添加如下配置,并重启MySQL服务
/* 添加配置 */
log-output=FILE
general-log=1
general_log_file="D:\develop\mysql-5.7.30-winx64\mysql.log" # 注意写你自己放MySQL文件的目录下
slow-query-log=1
slow_query_log_file="D:\develop\mysql-5.7.30-winx64\mysql_slow.log" # 注意写你自己放MySQL文件的目录下
long_query_time=2
/* 重启MySQL服务 */
-- 以管理员身份打开cmd,输入以下命令
net stop mysql   //停止mysql服务
net start mysql  //启动mysql服务
  • 会出现下面两个文件image-20220413230001338

3.观察日志🍤

image-20220413231756531

  • 可以看到只进行了一次预编译,所以这是PreparedSatementSatement性能高的原因
  • 还注意到了他对引号进行了转义,所以这是他能防止SQL注入的原因

写在后面🍻

感谢观看啦✨
有什么不足,欢迎指出哦💖
掘金的运营同学审核辛苦了💗