JDBC|JDBC基础进阶

本文详细介绍了JDBC中的预编译、批处理和事务控制功能,展示了它们如何提升数据库操作的安全性和效率,以及如何通过PreparedStatement避免SQL注入,同时讨论了批处理的优缺点和事务控制的原理及应用。
摘要由CSDN通过智能技术生成

目录

正文:

JDBC预编译

什么是预编译

JDBC预编译如何使用

预编译如何提升安全性

预编译如何提升效率

增强代码的可读性和可维护性

JDBC批处理

什么是批处理

JDBC如何进行批处理

批处理的优缺点

优点

缺点

事务控制

什么是事务

JDBC如何控制事务

如何设置回滚点

总结 


正文:

JDBC|JDBC基础知识

        在上一篇文章中,我们对JDBC的基础概念和使用做了介绍,现在我们再对JDBC的其他功能进行进一步的讲解,主要包括JDBC的预编译功能,批处理功能,事务控制功能。

JDBC预编译

什么是预编译

      JDBC的预编译功能是一种执行SQL语句的方式,通过预先编译SQL语句并将其缓存在数据库中,可以提高数据库操作的安全性和效率。

JDBC预编译如何使用

     JDBC预编译是通过PreparedStatement对象替代Statement对象来实现的,在使用PreparedStatement对象时,我们不需要将参数预先拼接到sql字符串中,而是在sql中以问号(?)来占位,如:

String sql = "select * from user where login_name= ? and password = ?";

        之后使用Connection的prepareStatement方法来创建预编译对象:

PreparedStatement statement = connection.prepareStatement(sql);

        再使用预编译对象的setXXX方法来设置参数:

statement.setString(1, loginName);
statement.setString(2, password);

         最后查询ResultSet:

ResultSet resultSet = statement.executeQuery();

        以上就是预编译对象的使用方法了,值得注意的是以下几点 :

  1. 预编译对象除了上面示例中的setString方法之外,还有setInt,setArray,setDate等一系列的set方法用于设置参数,具体如何选择要视sql参数类型而定;

  2. 预编译对象的setXXX方法的第一个参数为参数占位符的位置,即sql中的第几个问号,位置从1开始而不是从0开始;

  3. 设置好参数之后直接执行预编译对象的无参数的executeQuery()方法即可获取到查询结果,不需要再传sql作为参数了。

预编译如何提升安全性

        我们先来看一张表:

        这是一张简单的用户表,用于实现一个最简便的登录操作,里面包含用户的用户id,用户名,登录名以及登录密码,当用户需要登陆时,输入登录名和登录密码,通过登录名和登录密码在表里查找用户信息,如果能找到,则登录成功,如果找不到,则登录失败,此时如果简单的使用上一篇文章提到的jdbc基础知识的话,我们写出的代码大致是这样的:

         a. 拼接待执行的sql:

String sql = "select * from user where login_name='" + loginName + "' and password = '" + password + "'";

        b. 通过statement的executeQuery(sql)方法查询ResultSet对象,从而获取到用户信息:

ResultSet resultSet = statement.executeQuery(sql);
if (resultSet.next()) {
    //构建用户信息并返回
}else{
    //报错登录失败
}

        此时当我们正常输入登录名和密码时,是可以完成登录功能的,但是这里有一个巨大的安全隐患,就是当用户输入password="zzz' or 'a' = 'a"时,以上代码拼接出来的sql就变成了:

select * from user where login_name='lisi' and password = 'zzz' or 'a' = 'a';

        而由于or后面的条件‘a’='a'是恒成立的,则sql相当于全表查询了user表,必然会登录成功,这也就是我们常说的sql注入攻击。

        SQL注入攻击是一种常见的网络安全漏洞,攻击者利用该漏洞通过向应用程序的输入框或参数中插入恶意的SQL语句,来实现对数据库的非法访问和操作。SQL注入攻击通常发生在需要用户输入数据并将其传递给数据库执行SQL查询的应用程序中,如Web应用程序、数据库驱动程序等。攻击者利用SQL注入攻击的原理是通过在输入字段中注入恶意的SQL代码,使得应用程序在拼接SQL语句时无法正确过滤用户输入的内容,从而导致数据库执行了攻击者构造的恶意SQL语句

        而当我们使用预编译的方式来重写这段登录逻辑时,则会发现,不论我们的参数如何传值,都不会出现上述问题,这是因为JDBC的预编译对象在设置参数时,会帮我们对单引号进行转义处理,即上述问题示例中的password参数会被处理成“zzz\' or \'a\' = \'a”,这样最终执行的sql实际为:

select * from user where login_name='lisi' and password = 'zzz\' or \'a\' = \'a';

        如此一来,不论用户输入什么参数,都不会破坏sql的原有结构,大大提高了数据库操作的安全性。

预编译如何提升效率

        数据库在执行一个sql语句时,数据库会首先查询缓存,查询一下这个sql是否已经缓存了执行计划,如果没有缓存,则会对sql进行解析,编译,以及优化,最终生成执行计划,再由执行引擎执行执行计划,查询到数据结果之后,将结果返回给客户端,并将sql与对应的执行计划缓存起来,下图以Mysql数据库为例:

        如果使用PreparedStatement进行预编译,当PreparedStatement对象被创建时,对应的SQL语句会被发送到数据库进行预编译。数据库会对SQL语句进行解析、优化,并生成执行计划。这个执行计划会被缓存起来,当PreparedStatement被多次执行时,即使参数不断变化,数据库也可以直接使用这个缓存的执行计划,而不需要重新进行解析和编译。而尽管PreparedStatement对象本身是在客户端(即JDBC)创建和管理的,但预编译的过程以及执行计划的生成和缓存都是在数据库端完成的。同时,使用PreparedStatement时,只需要发送一次SQL语句模板到数据库服务器,之后每次执行只需发送参数值即可,减少了网络传输的数据量,特别是在执行大量相似SQL语句的批处理操作时,这种优势更为明显。 

增强代码的可读性和可维护性

        JDBC的预编译功能除了以上优点之外,还有一个附带的优势,就是这种写法本身对开发者的友好性,相对于生硬的拼接字符串,这种一开始便确定了sql字符串,在查询时随时设置参数获取结果的方式明显更加简便、优雅。

JDBC批处理

什么是批处理

     在前面说明预编译如何提升效率时,我们提到了一种操作叫做批处理,那么什么是批处理呢,见名知意,批处理就是将一批sql语句一起处理的操作,它允许应用程序将一组SQL语句作为一个批次发送到数据库进行执行,而不是一条一条地发送执行,在需要执行大量插入、更新或删除操作时,这种方式可以显著提高数据库操作的效率。

JDBC如何进行批处理

     JDBC中的Statement语句对象提供了addBatch和executeBatch方法,继承了Statement语句对象的PreparedStatement预编译语句对象同样支持这两个方法,我们可以通过addBatch方法将要处理的sql添加到同一个批次中去,最后通过executeBatch方法一次性执行完成所有sql,executeBatch方法会返回一个int数组,数组内是每条sql影响的行数,如:

String insertSql = "insert into user values (?, ?, ?, ?)";
PreparedStatement statement = connection.prepareStatement(insertSql);
for (int i = 0; i < 1000; i++) {
    statement.setString(1, String.valueOf(i));
    statement.setString(2, "userName"+i);
    statement.setString(3, "loginName"+i);
    statement.setString(4, "password");
    statement.addBatch();
}
int[] resArr = statement.executeBatch();
int res = 0;
for (int i : resArr) {
    res += i;
}
System.out.println("成功插入" + res + "条数据");

批处理的优缺点

优点

  1. 减少网络往返:批处理减少了应用程序与数据库服务器之间的通信次数,因为是多条语句一次性发送,而不是一条一条地发送。
  2. 提高性能:通过减少网络延迟和数据库接收处理请求的次数,批处理可以显著提高数据操作的性能。
  3. 减少资源消耗:批处理还可以减少数据库资源的消耗,因为它减少了解析、编译和执行SQL语句的次数。

缺点

  1. SQL语句限制:使用Statement的批处理可以添加不同的SQL语句,但使用PreparedStatement的批处理只能执行相同的SQL语句,只是参数不同。
  2. 异常处理更复杂:如果批处理中的一条语句失败,可能会导致整个批次失败,异常处理相对更复杂。

事务控制

什么是事务

        在说明JDBC如何控制事务之前,我们先简单复习一下什么是事务:事务(Transaction)是指作为单个逻辑工作单元执行的一组数据库操作。事务通常由一系列的数据库操作(例如插入、更新、删除)组成,这些操作要么全部成功执行,要么全部失败回滚,保持数据库的一致性和完整性。事务是数据库管理系统(DBMS)中确保数据操作的原子性、一致性、隔离性和持久性(ACID特性)的重要概念。

JDBC如何控制事务

         JDBC是默认自动提交事务的,即每执行一条SQL都会自动提交一次事务,如果要关闭JDBC的自动提交,则可以通过调用connection的setAutoCommit方法并传参数false来关闭自动提交,在需要手动提交的时候,只需要调用connection的commit方法即可,而程序出现异常需要回滚操作时,则需要调用connection的rollback方法。需要注意的是,无论是否需要进行回滚,最终事务都需要进行提交操作,如我们常见的简单的转账的例子,我们需要从id为1的钱包中转出100元钱,转出到id为2的钱包中,如果在钱从1号钱包中转出来后,但是还没有到达2号钱包的时候,程序发生了异常,我们可以采用如下写法来保证转入转出操作的原子性:

//关闭自动提交
connection.setAutoCommit(false);
String sql = "update wallet set money = money+? where id = ?";
preparedStatement = connection.prepareStatement(sql);
try {
    //钱包1转出100元
    preparedStatement.setInt(1, -100);
    preparedStatement.setString(2, "1");
    preparedStatement.executeUpdate();
    
    //此处可能发生异常
    throw new RuntimeException("此时发生异常,导致钱从1号钱包转出来了,但是没有转到2号钱包");
    
    //钱包2转入100元
    preparedStatement.setInt(1, 100);
    preparedStatement.setString(2, "2");
    preparedStatement.executeUpdate();
}catch (Exception e){
    try {
      //回滚
      connection.rollback();
    }catch (SQLException e2){
        e.printStackTrace();
    }
}finally {
    try {
        //提交
        connection.commit();
    }catch (SQLException e){
        e.printStackTrace();
    }
}

如何设置回滚点

        现在我们想一下另一个场景:1号钱包要给2号钱包转账1000次,每次转100元,但是我们需要每10次转账视为一个回滚点,即每转1000元确认一次,比如在第102次转账时发生了异常,此时不可以全部回滚所有转账操作,而是只回滚到第100次,也就是说最后的结果为转账了10000元,我们就可以每10次操作设置一次回滚点,在发生异常时只回滚到上一个回滚点,而不是回滚所有操作,我们可以这样写:

//关闭自动提交
connection.setAutoCommit(false);
String sql = "update wallet set money = money+? where id = ?";
preparedStatement = connection.prepareStatement(sql);
Savepoint savepoint = null;
try {
    for (int i = 1; i <= 1000; i++) {
        preparedStatement.setInt(1, -100);
        preparedStatement.setString(2, "1");
        preparedStatement.executeUpdate();

        //第102次转账发生了异常
        if(i==102) {
            throw new RuntimeException("第102次转账发生了异常");
        }

        preparedStatement.setInt(1, 100);
        preparedStatement.setString(2, "2");
        preparedStatement.executeUpdate();

        if(i%10==0){
            //设置回滚点        
            savepoint = connection.setSavepoint();
        }
    }
}catch (Exception e){
    try {
        //有回滚点则回滚到回滚点位置,否则全部回滚    
        if(savepoint!=null) {
            connection.rollback(savepoint);
        }else{
            connection.rollback();
        }
    }catch (SQLException e2){
        e.printStackTrace();
    }
}finally {
    try {
        //提交    
        connection.commit();
    }catch (SQLException e){
        e.printStackTrace();
    }
}

总结 

        以上就是本文的全部内容了,包括JDBC的预编译,批处理以及事务控制几部分内容,希望对您能有些许的帮助,谢谢阅读~

  • 33
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值