一、Java程序完成对数据库的操作


  我们做为后端程序开发人员,通常会使用Java程序来完成对数据库的操作。Java程序操作数据库的技术呢,有很多啊。

  -JDBC:(Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API。 【是操作数据库最为基础、底层的技术】
  那现在在企业项目开发中呢,一般都会使用基于JDBC的封装的高级框架,比如:Mybatis、MybatisPlus、Hibernate、SpringDataJPA。 而这些技术,目前的市场占有份额如下图所示:

 JDBC1-2

 

  目前最为主流的就是Mybatis,其次是MybatisPlus。

  我们需要学习一下操作数据库的基础基础 JDBC。

二、JDBC


  2.1 JDBC概述


  JDBC:(Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API。

 JDBC1-1

 

  本质:

    sun公司官方定义的一套操作所有关系型数据库的规范,即接口。

    各个数据库厂商去实现这套接口,提供数据库驱动jar包。

    我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

  2.2 JDBC快速入门


  需求:通过JDBC程序,执行update语法,更新用户表中的数据。

  步骤:

  准备工作:
    创建项目,引入mysql的驱动、junit依赖。
    注册驱动。
    获取连接对象 Connection。
    获取SQL语句执行对象 Statement。
  执行SQL语句。
  释放资源。


三、JDBC API详解


  3.1 DriverManager


  作用:

    注册驱动。
    获取数据库链接 Connection。

d388a010bea44be3be068fd4e181de9b

 


  3.1.1 注册驱动


  而通过上述的代码,大家可以看到注册驱动,我们是通过 Class.forName("com.mysql.cj.jdbc.Driver") 来注册的,看似和DriverManager没什么联系。其实不然,Class.forName("com.mysql.cj.jdbc.Driver") 只是将 Driver 这个类加载到JVM中。 而在 Driver 这个类中定义了静态代码块,内容如下:

 JDBC1-10

 

 

  当 Driver 这个类被加载时,就会自动执行静态代码块中的代码,然后就完成了注册驱动的操作。

  备注:

    而其实啊,Class.forName("com.mysql.cj.jdbc.Driver") 这句代码呢,是可以省略的,省略了,也可以正常的完成驱动的注册。 原因是因为在MySQL的驱动jar包中,在 META-INF/services 目录中提供了一个文件 java.sql.Driver ,在这个文件中编写了一行内容,就是驱动类的全类名 :

 JDBC1-4

 

  当在程序需要用到这个类时,java会自动加载这个类,这个类一加载 就会自动执行静态代码块中的内容,就完成了注册驱动的操作 ,而java中的这个机制称之为 SPI。

  SPI机制:Service Provider Interface,JDK内置的一种服务提供发现机制,可以轻松的扩展你得程序(切换实现),实现接口与实现类之间的解耦。

  3.1.2 获取链接


  DriverManager.getConnection(url, username, password);

  url: 数据库连接url。

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

    说明:如果连接的是本机的默认端口的mysql,url可以简写为:jdbc:mysql:///数据库名?参数键值对…

  user:用户名。

  password:密码。

  3.2 Connection & Statement


  Connection的作用:获取执行SQL的对象。
    执行普通SQL对象的Statement:connection.createStatement();
    执行预编译SQL对象PreparedStatement:connection.prepareStatement(); 【后面单独讲解】。

  Statement的作用:执行SQL。
    执行DDL、DML语句:executeUpdate(sql); 如果是执行DML语句完毕,返回值int代表 DML 语句影响的函数 。
    执行SQL语句:executeQuery(sql); 返回值为ResultSet,里面封装了查询结果。

  3.3 ResultSet

  需求:请根据 用户名 与 密码 查询用户的信息,实现用户登录操作。

  实现如下:

 

    /**
     * 根据用户名和密码查询用户的基本信息 - 参数化测试
     */
    @ParameterizedTest
    @CsvSource({"lvbu,123456", "xiaoqiao,123456"})
    public void testQuery2(String _username , String _password) throws Exception {
        //1. 注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

        //2. 获取链接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/web01", "root", "root@1234");

        //3. 获取数据库执行对象 Statement
        Statement statement = connection.createStatement();

        //4. 执行SQL
        ResultSet resultSet = statement.executeQuery("select * from user where username = '"+_username+"' and password = '"+_password+"'");

        //5. 获取结果
        while (resultSet.next()){
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            System.out.println(new User(id,username,password,name,age));
        }

        //6. 释放资源
        statement.close();
        connection.close();
    }

 

  ResultSet(结果集对象):封装了DQL查询语句查询的结果。

  next():将光标从当前位置向前移动一行,并判断当前行是否为有效行,返回值为boolean。

    true:有效行,当前行有数据

    false:无效行,当前行没有数据

   getXxx(…):获取数据,可以根据列的编号获取,也可以根据列名获取(推荐)。

  而上述的单元测试中,在SQL语句中,用户名 和密码的值是动态的,是将来页面传递到服务端的。 那么,我们可以基于JUnit中的参数化测试进行单元测试。

  如果在测试时,需要传递一组参数,可以使用 @CsvSource 注解。

  3.4 PreparedStatement


  作用:预编译SQL语句并执行,可以防止SQL注入问题。

  SQL注入:通过控制输入来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。

 JDBC1-6

 

  3.4.1 SQL注入演示


  为什么会出现SQL注入这种现象呢?

  在进行登录操作时,怎么样才算登录成功呢? 如果我们查询到了数据,就说明用户名密码是对的。 如果没有查询到数据,就说明用户名或密码错误。

  而出现上述现象,原因就是因为,我们我们编写的SQL语句是基于字符串进行拼接的 。 我们输入的用户名无所谓,比如:shfhsjfhja ,而密码呢,就是我们精心设计的,如:' or '1' = '1 。

  那最终拼接的SQL语句,如下所示:

 JDBC1-7

 

  我们知道,or 连接的条件,是或的关系,两者满足其一就可以。 所以,虽然用户名密码输入错误,也是可以查询返回结果的。

  3.4.2 SQL注入解决


  PreparedStatement 就可以解决SQL注入的问题。那么接下来,我们就来演示一下 PreparedStatement 的基本使用:

 

/**
 * 根据用户名和密码查询用户的基本信息 - PreparedStatement
 */
@ParameterizedTest
@CsvSource({"lvbu,123456"})
public void testQuery3(String _username , String _password) throws Exception {
    //1. 注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");

    //2. 获取链接
    Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/web01", "root", "root@1234");

    //3. 获取数据库执行对象 Statement
    PreparedStatement preparedStatement = connection.prepareStatement("select * from user where username = ? and password = ?");
    preparedStatement.setString(1, _username);
    preparedStatement.setString(2, _password);

    //4. 执行SQL
    ResultSet resultSet = preparedStatement.executeQuery();

    //5. 获取结果
    while (resultSet.next()){
        int id = resultSet.getInt("id");
        String username = resultSet.getString("username");
        String password = resultSet.getString("password");
        String name = resultSet.getString("name");
        int age = resultSet.getInt("age");
        System.out.println(new User(id,username,password,name,age));
    }

    //6. 释放资源
    preparedStatement.close();
    connection.close();
}

 

 

  而 select * from user where username = ? and password = ? 这种SQL呢,我们称之为预编译SQL 。那么基于这种预编译SQL呢,是可以解决SQL注入问题的 。

  通过控制台,可以看到输入的SQL语句,是预编译SQL语句。

 JDBC1-9

 

  而在预编译SQL语句中,当我们执行的时候,会把整个' or '1'='1作为一个完整的参数,赋值给第2个问号(' or '1'='1进行了转义,只当做字符串使用)那么此时再查询时,就查询不到对应的数据了,登录失败。

 

 

注意:在项目开发中,我们使用的基本全部都是预编译SQL语句。

预编译SQL的优势:

  安全(防止SQL注入)

  性能更高
JDBC1-8

 

posted on 2025-10-15 20:55  努力--坚持  阅读(6)  评论(0)    收藏  举报