JDBC操作MYSQL数据库

1. 什么是JDBC?

JDBC 是 Java 访问数据库的标准规范,真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用 JDBC 接口中的方法即可,数据库驱动由数据库厂商提供。

使用 JDBC 的好处:

  • 程序员如果要开发访问数据库的程序,只需要会调用 JDBC 接口中的方法即可,不用关注类是如何实现的。
  • 使用同一套 Java 代码,进行少量的修改就可以访问其他 JDBC 支持的数据库。

image-20201119222348763

2. 使用JDBC操作MYSQL

JDBC 的核心 API:

image-20201119222623795

2.1 JDBC访问MYSQL数据库的步骤

(1)注册和加载驱动(可以省略)

(2)创建数据库连接对象

我们知道DriverManager类是用来创建数据库连接对象的,DriverManager类创建数据库连接对象有以下两种方式:

image-20201119224303273

四个参数的详细解析:

image-20201119224537171

连接字符串URL的语法格式为:

协议名: 子协议://服务器名或IP 地址: 端口号/ 数据库名? 参数=参数值

image-20201119224831272

如果数据库出现乱码,可以指定参数: ?characterEncoding=utf8,表示让数据库以 UTF-8 编码来处理数据。

jdbc:mysql://localhost:3306/数据库?characterEncoding=utf8

(3)使用数据库连接对象Connection 获取 Statement 对象

获取Statement 对象方法:

image-20201119225044530

(4)使用 Statement 对象执行 SQL 语句

Statement 对象执行 SQL 语句的方法:

image-20201119225129005

(5)返回结果集

如果使用executeQuery方法,返回的结果集用ResultSet接口接收。

ResultSet接口作用:封装数据库查询的结果集,对结果集进行遍历,取出每一条记录。

ResultSet接口的方法:

image-20201119230059906

ResultSet接口遍历结果集图解:

image-20201119225613346

数据库列名类型和对应的ResultSet接口获取方法

image-20201119225820358

注:java.sql.Date、Time、Timestamp(时间戳),三个共同父类是:java.util.Date

(6)释放资源

  • 需要释放的对象:ResultSet 结果集,Statement 语句,Connection 连接
  • 释放原则:先开的后关,后开的先关。ResultSet => Statement =>Connection
  • 放在哪个代码块中:finally 块

2.2 代码示例

代码示例一:使用JDBC操作数据库增删改数据

public static void main(String[] args) throws SQLException {
    
    // 1) 创建数据库连接对象
    Connection connection = 
        DriverManager.getConnection("jdbc:mysql://localhost:3306/test?characterEncoding=utf8","root","root");
    // 2) 创建 Statement 语句对象
    Statement statement = connection.createStatement();
    // 3) 执行 SQL 语句:executeUpdate(sql)
    int count = 0;
    count += statement.executeUpdate("insert into student values(null, '孙悟空', 1, '1993-03-24')");
    count += statement.executeUpdate("insert into student values(null, '白骨精', 0, '1995-03-24')");
    count += statement.executeUpdate("insert into student values(null, '猪八戒', 1, '1903-03-24')");
    count += statement.executeUpdate("insert into student values(null, '嫦娥', 0, '1993-03-11')");
    // 4) 返回影响的行数
    System.out.println("插入了" + count + "条记录");
    // 5) 释放资源
    statement.close();
    connection.close();
}

代码示例二:使用JDBC操作数据库查询数据

public static void main(String[] args) throws SQLException {
    //1) 创建连接对象
    Connection connection =
        DriverManager.getConnection("jdbc:mysql://localhost:3306/test?characterEncoding=utf8","root","root");
    //2) 得到语句对象
    Statement statement = connection.createStatement();
    //3) 执行 SQL 语句得到结果集 ResultSet 对象
    ResultSet rs = statement.executeQuery("select * from student");
    //4) 循环遍历取出每一条记录
    while(rs.next()) {
        int id = rs.getInt("id");
        String name = rs.getString("name");
        boolean gender = rs.getBoolean("gender");
        Date birthday = rs.getDate("birthday");
        //5) 输出到控制台上
        System.out.println("编号:" + id + ", 姓名:" + name + ", 性别:" + gender + ", 生日:" +birthday);
    }
    //6) 释放资源
    rs.close();
    statement.close();
    connection.close();
}

关于 ResultSet 接口中的注意事项:

  • 如果光标在第一行之前,使用 rs.getXX()获取列值,报错:Before start of result set
  • 如果光标在最后一行之后,使用 rs.getXX()获取列值,报错:After end of result set
  • 使用完毕以后要关闭结果集 ResultSet,再关闭 Statement,再关闭 Connection

2.3 JDBC工具类

如果一个功能经常要用到,我们建议把这个功能做成一个工具类,可以在不同的地方重用。上面写的代码中出现了很多重复的代码,可以把这些公共代码抽取出来。

比如数据库连接对象的获取,释放资源等操作:

/* 访问数据库的工具类
*/
public class JdbcUtils {
    //可以把几个字符串定义成常量:用户名,密码,URL,驱动类
    private static final String USER = "root";
    private static final String PWD = "root";
    private static final String URL = "jdbc:mysql://localhost:3306/test?characterEncoding=utf8";
    private static final String DRIVER= "com.mysql.jdbc.Driver";
    /**
    * 注册驱动
    */
    static {
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    /**
    * 得到数据库的连接
    */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL,USER,PWD);
    }
    /**
    * 关闭所有打开的资源
    */
    public static void close(Connection conn, Statement stmt) {
        if (stmt!=null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn!=null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    /**
    * 关闭所有打开的资源,有ResultSet返回结果集
    */
    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        if (rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        close(conn, stmt);
    }
}

2.4 SQL注入问题

我们通过一个用户登录问题来了解一下SQL注入。

首先创建一个用户表,并往里添加几条记录:

create table user (
id int primary key auto_increment,
name varchar(20),
password varchar(20)
)
insert into user values (null,'jack','123'),(null,'rose','456');
-- 查询到数据表示登录成功, SQL 中大小写不敏感
select * from user where name='JACK' and password='123';
-- 查询不到数据表示登录失败
select * from user where name='JACK' and password='333';

使用 Statement 字符串拼接的方式实现用户的登录, 用户在控制台上输入用户名和密码:

//从控制台上输入的用户名和密码
public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入用户名:");
    String name = sc.nextLine();
    System.out.println("请输入密码:");
    String password = sc.nextLine();
    login(name, password);
}
//登录的方法
public static void login(String name, String password) {
    //a) 通过工具类得到连接
    Connection connection = null;
    Statement statement = null;
    ResultSet rs = null;
    try {
        connection = JdbcUtils.getConnection();
        //b) 创建语句对象,使用拼接字符串的方式生成 SQL 语句
        statement = connection.createStatement();
        //c) 查询数据库,如果有记录则表示登录成功,否则登录失败
        String sql = "select * from user where name='" + name + "' and password='" + password+ "'";
        System.out.println(sql);
        rs = statement.executeQuery(sql);
        if (rs.next()) {
            System.out.println("登录成功,欢迎您:" + name);
        } else {
            System.out.println("登录失败");
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        //d) 释放资源
        JdbcUtils.close(connection, statement, rs);
    }
}

SQL 注入问题:

当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了。

请输入用户名:
newboy
请输入密码:
a' or '1'='1
select * from user where name='newboy' and password='a' or '1'='1'
登录成功,欢迎您:newboy

问题分析:

select * from user where name='newboy' and password='a' or '1'='1'
name='newboy' and password='a' 为假
'1'='1' 真
相当于
select * from user where true; 查询了所有记录

我们让用户输入的密码和 SQL 语句进行字符串拼接。用户输入的内容作为了 SQL 语句语法的一部分,改变了原有 SQL 真正的意义,以上问题称为 SQL 注入。要解决 SQL 注入就不能让用户输入的密码和我们的 SQL 语句进行简单的字符串拼接。

2.4.1 防止SQL注入

我们可以使用PreparedStatement 接口防止SQL注入问题。PreparedStatement 是 Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语句。

PreparedSatement 的执行原理:

image-20201119232745014

Connection 创建 PreparedStatement 对象的方法:

image-20201119232857613

PreparedStatement 接口中的方法:

image-20201119232928006

使用 PreparedStatement 的步骤:

  1. 编写 SQL 语句,未知内容使用?占位:"SELECT * FROM user WHERE name=? AND password=?";
  2. 获得 PreparedStatement 对象
  3. 设置实际参数:setXxx(占位符的位置, 真实的值)
  4. 执行参数化 SQL 语句
  5. 关闭资源

image-20201119233134438

使用 PreparedStatement 改写上面的登录程序,解决 SQL 注入的问题

//从控制台上输入的用户名和密码
public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入用户名:");
    String name = sc.nextLine();
    System.out.println("请输入密码:");
    String password = sc.nextLine();
    login(name, password);
}
//登录的方法
private static void login(String name, String password) throws SQLException {
    Connection connection = JdbcUtils.getConnection();
    //写成登录 SQL 语句,没有单引号
    String sql = "select * from user where name=? and password=?";
    //得到语句对象
    PreparedStatement ps = connection.prepareStatement(sql);
    //设置参数
    ps.setString(1, name);
    ps.setString(2,password);
    ResultSet resultSet = ps.executeQuery();
    if (resultSet.next()) {
        System.out.println("登录成功:" + name);
    }
    else {
        System.out.println("登录失败");
    }
    //释放资源,子接口直接给父接口
    JdbcUtils.close(connection,ps,resultSet);
}
posted @ 2020-11-19 23:39  渺渺孤烟起  阅读(329)  评论(0)    收藏  举报