JDBC和PreparedStatament的使用

JDBC的使用

1. SQL注入

什么是SQL注入

所谓 SQL 注入, 就是通过把含有 SQL 语句片段的参数插入到需要执行的 SQL 语句中,
最终达到欺骗数据库服务器执行恶意操作的 SQL 命令。

SQL注入案例

private static void selectDepartmentsByIdAndName(String name, int id) {
    String sql = "select * from departments where department_name = '" + name + "' and department_id = " + id;
    System.out.println("sql = " + sql);
    Connection connection = DBUtil.getConnection();
    Statement statement = null;
    ResultSet resultSet = null;
    try {
        statement = connection.createStatement();
        resultSet = statement.executeQuery(sql);
        while (resultSet.next()) {
            System.out.println("resultSet.getObject(1) = " + resultSet.getObject(1));
            System.out.println("resultSet.getObject(2) = " + resultSet.getObject(2));
            System.out.println("resultSet.getObject(3) = " + resultSet.getObject(3));
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtil.close(resultSet, statement, connection);
    }
}

可以看到这个函数可以根据department_name和department_id 查询到一行数据. 但是当我们调用此函数是这样传参

selectDepartmentsByIdAndName("Te2' or 1=1 -- ", 272);

打印出来sql语句是这样的. 查询的是1=1, 永真的数据. 也就是全部的数据. 而传递过去的-- 则把后面的条件注释了. 这就是一个典型的SQL注入问题

select * from departments where department_name = 'Te2' or 1=1 -- ' and department_id = 272

2. PreparedStatement的使用

2.1 PreparedStatement特点

  • PreparedStatment接口继承Statement接口
  • PreparedStatement效率高于Statement
  • PreparedStatement支持动态绑定参数
  • PreparedStatement具备SQL语句预编译的能力
  • 使用PreparedStatement可防止出现SQL注入问题

2.2 通过PreparedStatement对象向表中插入数据

 private static void insertDepartment(String department_name) {
     // 通过使用? 进行占位处理, 实现动态绑定数据, 防止sql注入
     String sql = "insert into departments (department_id, department_name, manager_id, location_id) values (default, ?, ?, ?)";
     Connection connection = DBUtil.getConnection();
     PreparedStatement statement = null;
     ResultSet resultSet = null;
     try {
         statement = connection.prepareStatement(sql);
         statement.setString(1, department_name);
         statement.setInt(2, 205);
         statement.setInt(3, 2400);
         statement.execute();
     } catch (SQLException e) {
         e.printStackTrace();
     } finally {
         DBUtil.close(statement, connection);
     }
 }

2.3 使用PreparedStatement查询一条数据

	/**
     * 查询一条数据
     * @param departmentId 从数据库中查询一条数据
     * @return 返回查询到的一条数据
     */
private static Departments selectDepartment(int departmentId) {
    String sql = "select * from departments where department_id = ?;";
    Connection conn = null;
    PreparedStatement statement = null;
    Departments departments = null;
    try {
        conn = DBUtil.getConnection();
        statement = conn.prepareStatement(sql);
        statement.setInt(1, departmentId);
        ResultSet resultSet = statement.executeQuery();
        while (resultSet.next()) {
            departments = new Departments();
            departments.setDepartmentId(resultSet.getInt(1));
            departments.setDepartmentName(resultSet.getString(2));
            departments.setLocationId(resultSet.getInt(3));
            departments.setManagerId(resultSet.getInt(4));
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return departments;
}

2.4 使用PreparedStatement查询多条数据

/**
     * 查询多条数据
     * @param departmentName 模糊查询的字符串, 匹配department_name
     * @return 返回查询到的多条数据的容器
     */
private static List<Departments> selectDepartments(String  departmentName) {
    String sql = "select * from departments where department_name like ?;";
    Connection conn = null;
    PreparedStatement statement = null;
    List<Departments> list = new ArrayList<>();
    try {
        conn = DBUtil.getConnection();
        statement = conn.prepareStatement(sql);
        statement.setString(1, "%" + departmentName + "%");
        ResultSet resultSet = statement.executeQuery();
        while (resultSet.next()) {
            Departments departments = new Departments();
            departments = new Departments();
            departments.setDepartmentId(resultSet.getInt(1));
            departments.setDepartmentName(resultSet.getString(2));
            departments.setLocationId(resultSet.getInt(3));
            departments.setManagerId(resultSet.getInt(4));
            list.add(departments);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return list;
}

2.5 使用PreparedStatement批量添加数据

/**
     * 批量添加数据
     * @param list 添加的数据容器
     */
private static void addBatch(List<Departments> list){
    String sql = "insert into departments (department_id, department_name, manager_id, location_id) values (default, ?, ?, ?);";
    Connection conn = null;
    PreparedStatement statement = null;

    try {
        conn = DBUtil.getConnection();
        statement = conn.prepareStatement(sql);
        for (Departments departments : list) {
            statement.setString(1, departments.getDepartmentName());
            statement.setInt(2, departments.getManagerId());
            statement.setInt(3, departments.getLocationId());
            statement.addBatch();
        }
        statement.executeBatch();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtil.close(statement, conn);
    }
}

3. PreparedStatement的预编译能力

3.1 什么是预编译

3.1.1 SQL语句的执行步骤
  1. 语法和语义解析
  2. 优化sql语句, 指定执行计划
  3. 执行并返回结果

但是很多情况, 我们的一天sql语句可能会反复执行, 或者每次执行的时候只有个别的值不同(比如select的where字句值不同, update的set字句值不同,insert的values值不同). 如果每次都需要经过上面的词法语义解析, 语句优化, 指定执行计划等, 则效率就明显不行了.

所谓预编译语句就是将这类语句中的值用占位符替代, 可以视为将sql语句模板化或者说参数化

预编译语句的优势在于: 一次编译、 多次运行, 省去了解析优化等过程; 此外预编译语
句能防止 sql 注入

3.1.2 解析过程
  • 硬解析 (statement使用形式)

    在不开启缓存执行计划的情况下, 每次sql的处理都要经过: 语法和语义的解析, 优化处理器处理sql, 生成执行计划. 整个过程我们称之为硬解析.

  • 软解析 (PreparedStatement使用形式)

    如果开启了缓存执行计划, 数据库在处理 sql 时会先查询缓存中是否含有与当前 SQL
    语句相同的执行计划, 如果有则直接执行该计划

3.2 预编译方式

3.2.1 依赖数据库驱动完成预编译(建议)

如果我们没有开启数据库服务端编译, 那么默认使用的是数据库驱动完成sql的预编译

3.2.2 依赖数据库服务器完成预编译

我们可以通过修改连接数据库的 URL 信息, 添加 useServerPrepStmts=true 信息开启服
务端预编译。

4. JDBC中的事务处理

在 JDBC 操作中数据库事务默认为自动提交。 如果事务需要修改为手动提交, 那么我们
需要使用 Connection 对象中的 setAutoCommit 方法来关闭事务自动提交。 然后通过
Connection 对象中的 commit 方法与 rollback 方法进行事务的提交与回滚。

//事务处理
public void deleteDempartments(String depratmentName){
    Connection conn = null;
    PreparedStatement ps = null;
    try{
        conn = JdbcUtil.getConnection();
        //关闭事务的自动提交
        conn.setAutoCommit(false);
        ps = conn.prepareStatement("delete from departments where department_name like ?");
        ps.setString(1, "%"+depratmentName+"%");
        ps.executeUpdate();
        ps = conn.prepareStatement("insert into departments values(default,'开发部',2)");
        ps.executeUpdate();
        String str = null;
        str.length();
        conn.commit();
    }catch(Exception e){
        e.printStackTrace();
        JdbcUtil.rollback(conn);
    }finally{
    	JdbcUtil.closeResource(ps, conn, null);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值