JDBC连接MySQL数据库初探

一、第一个JDBC程序

要通过Java编写程序访问并操纵数据库,需要JDBC的帮助。JDBC一般指Java数据库连接,即Java Database Connectivity,是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,相当于应用程序与数据库间的一座桥梁。

要使得程序能够与某一品牌的数据库成功建立联系,需要下载相应数据库厂商提供的JDBC jar包。本文以MySQL为例,读者需要自行从https://dev.mysql.com/downloads/connector/j/上下载数据库对应版本的jar包,然后将其添加入Java项目的Library中。

下面的程序实现了对MySQL数据库中item表的查询操作:

import java.sql.*;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 1. 加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

        // 2. 连接数据库
        String url = "jdbc:mysql://localhost/TestMySQL?useUnicode=true&characterEncoding=utf8&useSSL=true";
        String user = "root";
        String password = "123456";
        Connection conn = DriverManager.getConnection(url, user, password);

        // 3. 创建数据库对象
        Statement stat = conn.createStatement();

        // 4. 编写SQL语句
        String sql = "SELECT `id`, `name`, `price`, `production_date` FROM `item`";

        // 5. 执行SQL语句并获取数据
        ResultSet rs = stat.executeQuery(sql);
        System.out.println("id\t\tname\tprice\tproduction_date\n" +
                           "------------------------------------------");
        while (rs.next()) {
            String id = rs.getString("id");
            String name = rs.getString("name");
            float price = rs.getFloat("price");
            java.util.Date date = rs.getDate("production_date");

            System.out.println(id + "\t" + name + "\t" + price + "\t" + date);
        }

        // 6. 释放链接
        rs.close();
        stat.close();
        conn.close();
    }
}

实现对数据库的连接与操纵可以归纳为6个步骤:

1. 加载驱动

Class.forName("com.mysql.cj.jdbc.Driver");

这一语句通过反射机制,输入MySQL的JDBC驱动类名,将类进行加载。而这个com.mysql.cj.jdbc.Driver类的内容为:

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!");
        }
    }
}

根据Java类的加载机制,当执行反射语句时,JVM会执行其中的静态语句块(即static部分),而语句块中的内容事实上就是新建一个驱动对象。通过反射这种方式,我们就成功的隐式的将驱动加载到DriverManager中。

2. 连接数据库

Connection conn = DriverManager.getConnection(url, user, password);

上述语句中第一个参数是要连接到数据库的地址,如:

"jdbc:mysql://localhost/TestMySQL?useUnicode=true&characterEncoding=utf8&useSSL=true"

打头的“jdbc:mysql://”是固定写法,后跟的“localhost”表示本地主机,当然你也可以换成远程主机的IP地址,再后的“TestMySQL”表示要使用的Schema名,问号?后面的为连接参数:1)useUnicode表示使用Unicode编码;2)characterEncoding表示字符集编码为utf8;3)useSSL表示使用SSL安全连接。当然,这里还有很多其它参数,可以根据需要进行设置。

3. 创建数据库对象

Statement stat = conn.createStatement();

这里我们通过调用Connection对象的createStatement方法,建立了一个Statement对象,它相当于一个我们对数据库进行操作的“操作臂”。

4. 编写SQL语句

这一步我们就将要执行的SQL语句赋予String对象。

5. 执行SQL语句并获取数据

首先,我们通过执行Statement对象的executeQuery方法,执行查询语句,并得到ResultSet类的返回结果。假如要执行Insert、Delete、Update等DML语句,则可以调用executeUpdate方法。

ResultSet rs = stat.executeQuery(sql);

ResultSet对象有点类似于一个链表,将一行行返回结果依次排列。要从中取出数据,可以调用ResultSet对象的getX方法,这里X指代相应的数据类型。例如,我们要获取第一个字段的数据,在数据库中它是一个varchar类型,那么对应到Java中就是String类型,所以调用getString方法:

String id = rs.getString("id");

其中,方法的参数为数据库中相应字段的名字。

要判断ResultSet对象取数是否完毕,可以通过调用ResultSet的next方法,判断是否还有下一条语句。

6. 释放连接

上面我们建立了Connection、Statement和ResultSet对象,运行结束前需要依次调用它们的close方法释放连接。请注意释放的顺序,后申请的要先释放。

二、Statement对象无法抵御SQL注入

SQL注入即是指应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。

假如现在item数据库中有如下3条数据:

现在我们需要用户输入要查询的商品名称,然后程序从数据库中进行查询并输出该商品的价格和生产日期。

import java.sql.*;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 1. 加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

        // 2. 连接数据库
        String url = "jdbc:mysql://localhost/TestMySQL?useUnicode=true&characterEncoding=utf8&useSSL=true";
        String user = "root";
        String password = "961010";
        Connection conn = DriverManager.getConnection(url, user, password);

        // 3. 创建数据库对象
        Statement stat = conn.createStatement();

        // 4. 执行SQL语句并获取数据
        System.out.print("Enter the query product: ");
        Scanner scanner = new Scanner(System.in);
        String query_name = scanner.nextLine().trim();
        // 注意:这里使用了字符串拼接,将SELECT语句与用户输入的查询商品名拼在一起
        ResultSet rs = stat.executeQuery("SELECT `price`, `production_date` FROM `item` WHERE `name` = '" + query_name + "'");
        System.out.println("price\tproduction_date\n" +
                           "------------------------");
        while (rs.next()) {
            float price = rs.getFloat("price");
            java.util.Date date = rs.getDate("production_date");

            System.out.println(price + "\t" + date);
        }

        // 5. 释放链接
        rs.close();
        stat.close();
        conn.close();
    }
}

例如,用户输入了beer,则程序的输出结果为:

但是,假如一名黑客输入了这样一串字符串:' OR '1'='1 

则输出结果居然是数据库中的所有数据:

事实上,程序执行的SQL被拼接成了这样:

SELECT  `price`, `production_date` FROM `item` WHERE `name` = '' OR '1' = '1'

由于'1' = '1'一定成立,OR条件便整体成立,于是WHERE字句就失去作用,变成了将item表中所有数据都取出。

三、使用PreparedStatement防止SQL注入

要防止可能的SQL注入,我们可以使用PreparedStatement替换上面的Statement。

import java.sql.*;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 1. 加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

        // 2. 连接数据库
        String url = "jdbc:mysql://localhost/TestMySQL?useUnicode=true&characterEncoding=utf8&useSSL=true";
        String user = "root";
        String password = "961010";
        Connection conn = DriverManager.getConnection(url, user, password);

        // 3. 编写SQL模版,使用问号?占位符表示待填充的内容
        String sql = "SELECT `price`, `production_date` FROM `item` WHERE `name` = ?";

        // 4. 创建数据库对象
        PreparedStatement stat = conn.prepareStatement(sql);

        // 4. 执行SQL语句并获取数据
        System.out.print("Enter the query product: ");
        Scanner scanner = new Scanner(System.in);
        String query_name = scanner.nextLine().trim();
        // 将query_name填充进第1个占位符的位置,类型为String
        stat.setString(1, query_name);
        // 这里就不再需要将SQL语句作为参数传入了,因为事先已经经过编译存储在stat中了
        ResultSet rs = stat.executeQuery();
        System.out.println("price\tproduction_date\n" +
                           "------------------------");
        while (rs.next()) {
            float price = rs.getFloat("price");
            java.util.Date date = rs.getDate("production_date");

            System.out.println(price + "\t" + date);
        }

        // 5. 释放链接
        rs.close();
        stat.close();
        conn.close();
    }
}

需要注意的几个点:

1. 我们现在需要写好SQL模版,并将待填入的内容用问号“?”占位符空出,而不再使用字符串拼接这样危险的方式。

2. 需要在准备statement时就将SQL模版作为参数,传入prepareStatement方法中。

3. 不再需要将SQL语句作为参数传入executeQuery/executeUpdate方法中了,因为事先已经经过编译存储在stat中了。

我们现在来测试一下危险的破坏性输入,看看运行结果:

 此时,程序没有给出任何查询结果,这正是我们所需要的。

事实上,prepareStatement对象防止sql注入的方式是把用户非法输入的单引号用反斜杠“\”做了转义。例如,上面的输入被处理后的实际查询语句为:

SELECT `price`, `production_date` FROM `item` WHERE `name` = '\' OR \'1\' = \'1'

即name条件是" \' OR \'1\' = \'1  " 或者说是" ' OR '1' = '1 "。在数据库显然不会有一个商品会叫" ' OR '1' = '1 "这个名字,因此也就查不到数据了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值