精华内容
下载资源
问答
  • 主要介绍了Java使用Statement接口执行SQL语句操作,结合实例形式详细分析了Java使用Statement接口针对mysql数据库进行连接与执行SQL语句增删改查等相关操作技巧与注意事项,需要的朋友可以参考下
  • 主要介绍了MySQL:Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEM,需要的朋友可以参考下
  • PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程,下面这篇文章主要给大家介绍了关于利用JDBC的PrepareStatement打印真实SQL的方法,需要的朋友可以参考借鉴,下面来一起看看吧。
  • 搭建项目时使用了mybatisplus,项目能够正常启动,但在调用mapper方法查询数据库时报Invalid bound statement (not found)错误。本文给大家分享解决方案,感兴趣的朋友跟随小编一起看看吧
  • 主要介绍了MybatisPlus BaseMapper 中的方法全部 Invalid bound statement (not found)的Error处理方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
  • 主要介绍了引入mybatis-plus报 Invalid bound statement错误问题的解决方法,需要的朋友可以参考下
  • 主要介绍了使用mybatis-plus报错Invalid bound statement (not found)错误,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • statement和prepared区别

    2018-07-27 15:09:25
    jdbc中statement和prepared区别,jdbc中statement和prepared区别
  • 项目范围说明书(Project Scope Statement)适用于各个行业的范围说明书,中英文对照标题
  • prepareStatementStatement的区别
  • 解决:com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after statement closed.
  • Vuejs报错error: Unexpected console statement (no-console) at src\... 解决办法
  • 主要介绍了详解Java的JDBC中Statement与PreparedStatement对象,PreparedStatement一般来说比使用Statement效率更高,需要的朋友可以参考下
  • 简化逻辑陈述 采取逻辑陈述并将其简化。 用法 将简化函数应用于以下概述的一种类型。 例子 消除双重否定 import { simplify , ... statement : 'statement 3' , } , } , } simplify ( myNotStatement ) // {
  • 用于跟踪使用oracle期间产生的代码
  • Statement Tracer for Oracle

    2018-01-10 15:37:12
    Statement_Tracer_for_Oracle是一款非常好用的oracle跟踪工具 目前网上基本都是英文原版的
  • statement和prepareStatement的区别

    千次阅读 多人点赞 2019-09-16 10:20:05
    statement语法 Statement stmt = connect.createStatement(); String sql= "SELECT * FROM cg_user WHERE userId=10086 AND name LIKE 'xiaoming'"; ResultSet rs = stmt.executeUpdate(sql); prepar...

    一、语法

    两者的语法区别

    1. statement语法
    Statement stmt = connect.createStatement();
    String sql= "SELECT * FROM cg_user WHERE userId=10086 AND name LIKE 'xiaoming'";
    ResultSet rs = stmt.executeUpdate(sql);
    
    1. preparedstatement
    PreparedStatement preparedStatement = connect.prepareStatement("SELECT * FROM cg_user WHERE userId= ? AND name LIKE ?");  
    preparedStatement .setInt(1, 10086 );  
    preparedStatement .setString(2, "xiaoming");  
    preparedStatement .executeUpdate();  
    

    二、访问数据库的速度

    prepareStatement会先初始化SQL,先把这个SQL提交到数据库中进行预处理,多次使用可提高效率。
    createStatement不会初始化,没有预处理,没次都是从0开始执行SQL

    PreparedStatement对象不仅包含了SQL语句,而且大多数情况下这个语句已经被预编译过,因而当其执行时,只需DBMS运行SQL语句,而不必先编译。当你需要执行Statement对象多次的时候,PreparedStatement对象将会大大降低运行时间,当然也加快了访问数据库的速度。
    这种转换也给你带来很大的便利,不必重复SQL语句的句法,而只需更改其中变量的值,便可重新执行SQL语句。选择PreparedStatement对象与否,在于相同句法的SQL语句是否执行了多次,而且两次之间的差别仅仅是变量的不同。如果仅仅执行了一次的话,它应该和普通的对象毫无差异,体现不出它预编译的优越性。

    三、prepareStatement批量执行:

    好处:Update大量的数据时, 先构建一个INSERT语句再多次的执行, 会导致很多次的网络连接.。要减少JDBC的调用次数改善性能, 可以使用PreparedStatement的AddBatch()方法一次性发送多个查询给数据库。

    // 初始实现:
    PreparedStatement ps = conn.prepareStatement(
                    "INSERT into db_user values (?, ?, ?)");
            for (n = 0; n < 100; n++) {
                ps.setString(name[n]);
                ps.setLong(id[n]);
                ps.setInt(salary[n]);
                ps.executeUpdate();
            }
    //改进实现:
    //使用Batch功能
            PreparedStatement ps = conn.prepareStatement(
                    "INSERT into db_user values (?, ?, ?)");
            for (n = 0; n < 100; n++) {
                ps.setString(username[n]);
                ps.setString(password[n]);
                ps.addBatch();
            }
            ps.executeBatch();
    

    四、SQL注入漏洞:

    Statement stmt = connect.createStatement();
    String sql= "SELECT * FROM cg_user WHERE userId"+ userId +" AND name LIKE " + name";
    ResultSet rs = stmt.executeUpdate(sql);
    

    假如入参name的值为 or '1' = '1' 那么SQL是成立的,就会返回所有数据,或者变成这个[‘;drop table cg_user ;],那么SQL拼接后就变成:

    SELECT * FROM cg_user WHERE userId='' AND name LIKE '' ; drop table cg_user ;
    

    如果使用prepareStatement预编译就不会了,因为SQL语句在程序运行前已经进行了预编译,在程序运行时第一次操作数据库之前,SQL语句已经被数据库分析和编译,对应的执行计划也会缓存下来,之后数据库就会以参数化的形式进行查询。set值永远是把占位符当成data处理。

    PreparedStatement preparedStatement = connect.prepareStatement("SELECT * FROM cg_user WHERE userId= ? AND name LIKE ?");  
    preparedStatement .setInt(1, '');  
    preparedStatement .setString(2, "; drop table cg_user");  
    preparedStatement .executeUpdate();  
    

    sql会变成:

    SELECT * FROM cg_user WHERE userId='' AND name LIKE '; drop table cg_user'  ;
    

    总结:

    1. JDBC驱动的最佳化是基于使用的是什么功能,选择PreparedStatement还是Statement取决于你要怎么使用它们,对于只执行一次的SQL语句选择Statement是最好的。相反,如果SQL语句被多次执行选用PreparedStatement是最好的。

    2. PreparedStatement的第一次执行消耗是很高的,它的性能体现在后面的重复执行,使用PreparedStatement的方式来执行一个针对数据库表的查询,JDBC驱动会发送一个网络请求到数据解析和优化这个查询,而执行时会产生另一个网络请求,在JDBC驱动中,减少网络通讯是最终的目的。如果我的程序在运行期间只需要一次请求, 那么就使用Statement,对于Statement,同一个查询只会产生一次网络到数据库的通讯。

    展开全文
  • Statement和PreparedStatement之间的区别
  • statement 、prepareStatement的用法和解释

    万次阅读 多人点赞 2018-02-11 16:54:01
    转自:http://blog.csdn.net/QH_JAVA/article/details/48245945 一、prepareStatement 的用法和解释1.PreparedStatement是预编译的,对于批量...
    转自:http://blog.csdn.net/QH_JAVA/article/details/48245945
    

    一、prepareStatement 的用法和解释

    1.PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程

    2.使用 Statement 对象。在对数据库只执行一次性存取的时侯,用 Statement 对象进行处理。PreparedStatement 对象的开销比Statement大,对于一次性操作并不会带来额外的好处。
    3.statement每次执行sql语句,相关数据库都要执行sql语句的编译,preparedstatement是预编译得, preparedstatement支持批处理

    4、

    Code Fragment 1:

    String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERE COF_NAME LIKE ′Colombian′";
    stmt.executeUpdate(updateString);

    Code Fragment 2:

    PreparedStatement updateSales = con.prepareStatement("UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? ");
    updateSales.setInt(1, 75);
    updateSales.setString(2, "Colombian");
    updateSales.executeUpdate();

    片断2和片断1的区别在于,后者使用了PreparedStatement对象,而前者是普通的Statement对象。PreparedStatement对象不仅包含了SQL语句,而且大多数情况下这个语句已经被预编译过,因而当其执行时, 只需DBMS运行SQL语句,而不必先编译。当你需要执行Statement对象多次的时候,PreparedStatement对象将会大大降低运行时间,当然也加快了访问数据库的速度。
    这种转换也给你带来很大的便利,不必重复SQL语句的句法,而只需更改其中变量的值,便可重新执行SQL语句。选择PreparedStatement对象与否,在于相同句法的SQL语句是否执行了多次,而且两次之间的差别仅仅是变量的不同。如果仅仅执行了一次的话,它应该和普通的对象毫无差异,体现不出它预编译的优越性。


    5.执行许多SQL语句的JDBC程序产生大量的Statement和PreparedStatement对象。通常认为PreparedStatement对象比Statement对象更有效,特别是如果带有不同参数的同一SQL语句被多次执行的时候。PreparedStatement对象允许数据库预编译SQL语句,这样在随后的运行中可以节省时间并增加代码的可读性。


    然而,在Oracle环境中,开发人员实际上有更大的灵活性。当使用Statement或PreparedStatement对象时,Oracle数据库会缓存SQL语句以便以后使用。在一些情况下,由于驱动器自身需要额外的处理和在Java应用程序和Oracle服务器间增加的网络活动,执行PreparedStatement对象实际上会花更长的时间。

    然而,除了缓冲的问题之外,至少还有一个更好的原因使我们在企业应用程序中更喜欢使用PreparedStatement对象 ,那就是安全性。传递给PreparedStatement对象的参数可以被强制进行类型转换,使开发人员可以确保在插入或查询数据时与底层的数据库格式匹配。

    当处理公共Web站点上的用户传来的数据的时候,安全性的问题就变得极为重要。传递给PreparedStatement的字符串参数会自动被驱动器忽略。最简单的情况下,这就意味着当你的程序试着将字符串“D'Angelo”插入到VARCHAR2中时,该语句将不会识别第一个“,”,从而导致悲惨的失败。几乎很少有必要创建你自己的字符串忽略代码。

    在Web环境中,有恶意的用户会利用那些设计不完善的、不能正确处理字符串的应用程序。特别是在公共Web站点上,在没有首先通过PreparedStatement对象处理的情况下,所有的用户输入都不应该传递给SQL语句。此外,在用户有机会修改SQL语句的地方,如HTML的隐藏区域或一个查询字符串上,SQL语句都不应该被显示出来。
    在执行SQL命令时,我们有二种选择:可以使用PreparedStatement对象,也可以使用Statement对象。无论多少次地使用同一个SQL命令,PreparedStatement都只对它解析和编译一次。当使用Statement对象时,每次执行一个SQL命令时,都会对它进行解析和编译。 

    第一:

    prepareStatement会先初始化SQL,先把这个SQL提交到数据库中进行预处理,多次使用可提高效率。 
    Statement不会初始化,没有预处理,没次都是从0开始执行SQL

    第二:

    prepareStatement可以替换变量 
    在SQL语句中可以包含?,可以用ps=conn.prepareStatement("select * from Cust where ID=?"); 
    int sid=1001; 
    ps.setInt(1, sid); 
    rs = ps.executeQuery(); 
    可以把?替换成变量。 
    而Statement只能用 
    int sid=1001; 
    Statement stmt = conn.createStatement(); 
    ResultSet rs = stmt.executeQuery("select * from Cust where ID="+sid); 
    来实现。


    二、深入理解statement 和prepareStatement


    1、使用Statement而不是PreparedStatement对象
    JDBC驱动的最佳化是基于使用的是什么功能. 选择PreparedStatement还是Statement取决于你要怎么使用它们. 对于只执行一次的SQL语句选择Statement是最好的. 相反, 如果SQL语句被多次执行选用PreparedStatement是最好的.

    PreparedStatement的第一次执行消耗是很高的. 它的性能体现在后面的重复执行. 例如, 假设我使用Employee ID, 使用prepared的方式来执行一个针对Employee表的查询. JDBC驱动会发送一个网络请求到数据解析和优化这个查询. 而执行时会产生另一个网络请求.在JDBC驱动中,减少网络通讯是最终的目的. 如果我的程序在运行期间只需要一次请求, 那么就使用Statement. 对于Statement, 同一个查询只会产生一次网络到数据库的通讯.

    对于使用PreparedStatement池的情况下, 本指导原则有点复杂. 当使用PreparedStatement池时, 如果一个查询很特殊, 并且不太会再次执行到, 那么可以使用Statement. 如果一个查询很少会被执行,但连接池中的Statement池可能被再次执行, 那么请使用PreparedStatement. 在不是Statement池的同样情况下, 请使用Statement.

    2、使用PreparedStatement的Batch功能
    Update大量的数据时, 先Prepare一个INSERT语句再多次的执行, 会导致很多次的网络连接. 要减少JDBC的调用次数改善性能, 你可以使用PreparedStatement的AddBatch()方法一次性发送多个查询给数据库. 例如, 让我们来比较一下下面的例子.

    例 1: 多次执行PreparedStatement,多次数据库请求(网络请求)
     
    1. PreparedStatement ps = conn.prepareStatement(  
    2.    "INSERT into employees values (?, ?, ?)");  
    3.   
    4. for (n = 0; n < 100; n++) {  
    5.   
    6.   ps.setString(name[n]);  
    7.   ps.setLong(id[n]);  
    8.   ps.setInt(salary[n]);  
    9.   ps.executeUpdate();  
    10. }  
    例 2: 使用Batch,以此请求执行多条
     
    1. PreparedStatement ps = conn.prepareStatement(  
    2.    "INSERT into employees values (?, ?, ?)");  
    3.   
    4. for (n = 0; n < 100; n++) {  
    5.   
    6.   ps.setString(name[n]);  
    7.   ps.setLong(id[n]);  
    8.   ps.setInt(salary[n]);  
    9.   ps.addBatch();  
    10. }  
    11. ps.executeBatch();  

      在例 1中, PreparedStatement被用来多次执行INSERT语句. 在这里, 执行了100次INSERT操作, 共有101次网络往返.

       其中,1次往返是预储PreparedStatement, 另外100次往返执行每个迭代.

    在例2中, 当在100次INSERT操作中使用addBatch()方法时, 只有两次网络往返.

       1次往返是预储PreparedStatement, 另一次是执行batch命令. 虽然Batch命令会用到更多的数据库的CPU周期, 但是通过减少网络往返,性能得到提高.记住, JDBC的性能最大的增进是减少JDBC驱动与数据库之间的网络通讯.次数


      注:Oracel 10G的JDBC Driver限制最大Batch size是16383条,如果addBatch超过这个限制,那么executeBatch时就会出现“无效的批值”(Invalid Batch Value) 异常。因此在如果使用的是Oracle10G,在此bug减少前,Batch size需要控制在一定的限度。

           同样mysql 5.5.28 批量执行的数据最大限度是多少不清楚,但自己试了1w,2w,3w 都没问题,记得在url 后面添加:rewriteBatchedStatements=true 表示批量插入,如果不添加的话即使使用addbatch() ,executeBatch() 在后台入库的地方还是不会一次请求入库而是多次请求入库。


    3、选择合适的光标类型
    的光标类型以最大限度的适用你的应用程序. 本节主要讨论三种光标类型的性能问题.
    对于从一个表中顺序读取所有记录的情况来说, Forward-Only型的光标提供了最好的性能. 获取表中的数据时, 没有哪种方法比使用Forward-Only型的光标更快. 但不管怎样, 当程序中必须按无次序的方式处理数据行时, 这种光标就无法使用了.

    对于程序中要求与数据库的数据同步以及要能够在结果集中前后移动光标, 使用JDBC的Scroll-Insensitive型光标是较理想的选择. 此类型的光标在第一次请求时就获取了所有的数据(当JDBC驱动采用'lazy'方式获取数据时或许是很多的而不是全部的数据)并且储存在客户端. 因此, 第一次请求会非常慢, 特别是请求长数据时会理严重. 而接下来的请求并不会造成任何网络往返(当使用'lazy'方法时或许只是有限的网络交通) 并且处理起来很快. 因为第一次请求速度很慢, Scroll-Insensitive型光标不应该被使用在单行数据的获取上. 当有要返回长数据时, 开发者也应避免使用Scroll-Insensitive型光标, 因为这样可能会造成内存耗尽. 有些Scroll-Insensitive型光标的实现方式是在数据库的临时表中缓存数据来避免性能问题, 但多数还是将数据缓存在应用程序中.

    Scroll-Sensitive型光标, 有时也称为Keyset-Driven光标, 使用标识符, 像数据库的ROWID之类. 当每次在结果集移动光标时, 会重新该标识符的数据. 因为每次请求都会有网络往返, 性能可能会很慢. 无论怎样, 用无序方式的返回结果行对性能的改善是没有帮助的.

    现在来解释一下这个, 来看这种情况. 一个程序要正常的返回1000行数据到程序中. 在执行时或者第一行被请求时, JDBC驱动不会执行程序提供的SELECT语句. 相反, 它会用键标识符来替换SELECT查询, 例如, ROWID. 然后修改过的查询都会被驱动程序执行,跟着会从数据库获取所有1000个键值. 每一次对一行结果的请求都会使JDBC驱动直接从本地缓存中找到相应的键值, 然后构造一个包含了'WHERE ROWID=?'子句的最佳化查询, 再接着执行这个修改过的查询, 最后从服务器取得该数据行.

    当程序无法像Scroll-Insensitive型光标一样提供足够缓存时, Scroll-Sensitive型光标可以被替代用来作为动态的可滚动的光标. 

    4、使用有效的getter方法
    JDBC提供多种方法从ResultSet中取得数据, 像getInt(), getString(), 和getObject()等等. 而getObject()方法是最泛化了的, 提供了最差的性能。 这是因为JDBC驱动必须对要取得的值的类型作额外的处理以映射为特定的对象. 所以就对特定的数据类型使用相应的方法.

    要更进一步的改善性能, 应在取得数据时提供字段的索引号, 例如, getString(1), getLong(2), 和getInt(3)等来替代字段名. 如果没有指定字段索引号, 网络交通不会受影响, 但会使转换和查找的成本增加. 例如, 假设你使用getString("foo") ... JDBC驱动可能会将字段名转为大写(如果需要), 并且在到字段名列表中逐个比较来找到"foo"字段. 如果可以, 直接使用字段索引, 将为你节省大量的处理时间.

    例如, 假设你有一个100行15列的ResultSet, 字段名不包含在其中. 你感兴趣的是三个字段 EMPLOYEENAME (字串型), EMPLOYEENUMBER (长整型), 和SALARY (整型). 如果你指定getString(“EmployeeName”), getLong(“EmployeeNumber”), 和getInt(“Salary”), 查询旱每个字段名必须被转换为metadata中相对应的大小写, 然后才进行查找. 如果你使用getString(1), getLong(2), 和getInt(15). 性能就会有显著改善.

    5、获取自动生成的键值
    有许多数据库提供了隐藏列为表中的每行记录分配一个唯一键值. 很典型, 在查询中使用这些字段类型是取得记录值的最快的方式, 因为这些隐含列通常反应了数据在磁盘上的物理位置. 在JDBC3.0之前, 应用程序只可在插入数据后通过立即执行一个SELECT语句来取得隐含列的值.

    例 3: JDBC3.0之前
     
    1. //插入行  
    2. int rowcount = stmt.executeUpdate (  
    3.    "insert into LocalGeniusList (name) values ('Karen')");  
    4. // 现在为新插入的行取得磁盘位置 - rowid  
    5. ResultSet rs = stmt.executeQuery (  
    6.    "select rowid from LocalGeniusList where name = 'Karen'");  
    这种取得隐含列的方式有两个主要缺点. 第一, 取得隐含列是在一个独立的查询中, 它要透过网络送到服务器后再执行. 第二, 因为不是主键, 查询条件可能不是表中的唯一性ID. 在后面一个例子中, 可能返回了多个隐含列的值, 程序无法知道哪个是最后插入的行的值.

    (译者:由于不同的数据库支持的程度不同,返回rowid的方式各有差异。在SQL Server中,返回最后插入的记录的id可以用这样的查询语句:SELECT @IDENTITY )

    JDBC3.0规范中的一个可选特性提供了一种能力, 可以取得刚刚插入到表中的记录的自动生成的键值. 
    例 4: JDBC3.0之后
     
    1. int rowcount = stmt.executeUpdate (  
    2.    "insert into LocalGeniusList (name) values ('Karen')",  
    3. // 插入行并返回键值  
    4. Statement.RETURN_GENERATED_KEYS);  
    5. ResultSet rs = stmt.getGeneratedKeys ();  
    6. // 得到生成的键值  
    现在, 程序中包含了一个唯一性ID, 可以用来作为查询条件来快速的存取数据行, 甚至于表中没有主键的情况也可以.

    这种取得自动生成的键值的方式给JDBC的开发者提供了灵活性, 并且使存取数据的性能得到提升.


    6、选择合适的数据类型
    接收和发送某些数据可能代价昂贵. 当你设计一个schema时, 应选择能被最有效地处理的数据类型. 例如, 整型数就比浮点数或实数处理起来要快一些. 浮点数的定义是按照数据库的内部规定的格式, 通常是一种压缩格式. 数据必须被解压和转换到另外种格式, 这样它才能被数据的协议处理.

    7、获取ResultSet
    由于数据库系统对可滚动光标的支持有限, 许多JDBC驱动程序并没有实现可滚动光标. 除非你确信数据库支持可滚动光标的结果集, 否则不要调用rs.last()和rs.getRow()方法去找出数据集的最大行数. 因为JDBC驱动程序模拟了可滚动光标, 调用rs.last()导致了驱动程序透过网络移到了数据集的最后一行. 取而代之, 你可以用ResultSet遍历一次计数或者用SELECT查询的COUNT函数来得到数据行数.

    通常情况下,请不要写那种依赖于结果集行数的代码, 因为驱动程序必须获取所有的数据集以便知道查询会返回多少行数据.

    三、preparestatement 防止sql注入

    在JDBC应用中,如果你已经是稍有水平开发者,你就应该始终以PreparedStatement代替Statement.也就是说,在任何时候都不要使用Statement.基于以下的原因:

    1、代码的可读性和可维护性.虽然用PreparedStatement来代替Statement会使代码多出几行,但这样的代码无论从可读性还是可维护性上来说.都比直接用Statement的代码高很多档次:
    stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");
    perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");

    perstmt.setString(1,var1);perstmt.setString(2,var2);perstmt.setString(3,var3);perstmt.setString(4,var4);

    perstmt.executeUpdate();
    不用我多说,对于第一种方法.别说其他人去读你的代码,就是你自己过一段时间再去读,都会觉得伤心.

    2、PreparedStatement尽最大可能提高性能.每一种数据库都会尽最大努力对预编译语句提供最大的性能优化.因为预编译语句有可能被重复调用.所以语句在被DB的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中(相当于一个涵数)就会得到执行.这并不是说只有一个 Connection中多次执行的预编译语句被缓存,而是对于整个DB中,只要预编译的语句语法和缓存中匹配.那么在任何时候就可以不需要再次编译而可以直接执行.而statement的语句中,即使是相同一操作,而由于每次操作的数据不同所以使整个语句相匹配的机会极小,几乎不太可能匹配.比如:insert into tb_name (col1,col2) values ('11','22');insert into tb_name (col1,col2) values ('11','23');即使是相同操作但因为数据内容不一样,所以整个个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存.这样每执行一次都要对传入的语句编译一次.
    当然并不是所以预编译语句都一定会被缓存,数据库本身会用一种策略,比如使用频度等因素来决定什么时候不再缓存已有的预编译结果.以保存有更多的空间存储新的预编译语句.

    3、最重要的一点是极大地提高了安全性.
    即使到目前为止,仍有一些人连基本的恶义SQL语法都不知道.String sql = "select * from tb_name where name= '"+varname+"' and passwd='"+varpasswd+"'";如果我们把[' or '1' = '1]作为varpasswd传入进来.用户名随意,看看会成为什么?
    select * from tb_name = '随意' and passwd = '' or '1' = '1';因为'1'='1'肯定成立,所以可以任何通过验证.更有甚者:把[';drop table tb_name;]作为varpasswd传入进来,则:select * from tb_name = '随意' and passwd = '';drop table tb_name;有些数据库是不会让你成功的,但也有很多数据库就可以使这些语句得到执行.
    而如果你使用预编译语句.你传入的任何内容就不会和原来的语句发生任何匹配的关系.(前提是数据库本身支持预编译,但上前可能没有什么服务端数据库不支持编译了,只有少数的桌面数据库,就是直接文件访问的那些)只要全使用预编译语句,你就用不着对传入的数据做任何过虑.而如果使用普通的statement, 有可能要对drop,;等做费尽心机的判断和过虑.
    上面的几个原因,还不足让你在任何时候都使用PreparedStatement吗?


    总结: 上面是三篇文章,三篇文章详细介绍了statement 和preparestatement 两个对象的使用以及效率、安全问题。在实际项目中如果能够使用preparestatement  还是建议使用preparestatement  原因有3:

    1)、上面说了 如果sql中只有数值在变则效率高

    2)、preparestatement 具有防sql注入

    3)、代码可读性比较好


    实例:下面这个比喻很好,很明确的说明了批量添加,并且从中也可以看出在批量添加的时候PreparedStatement为什么比Statement快的原因~

    Statement和PreparedStatement的区别就不多废话了,直接说PreparedStatement最重要的addbatch()结构的使用.

    PreparedStatement  的addBatchexecuteBatch实现批量添加

    1.建立链接   

       Connection    connection =getConnection();

    2.不自动 Commit (瓜子不是一个一个吃,全部剥开放桌子上,然后一口舔了)

    connection.setAutoCommit(false);   

    3.预编译SQL语句,只编译一回哦,效率高啊.(发明一个剥瓜子的方法,以后不要总想怎么剥瓜子好.就这样剥.)
    PreparedStatement statement = connection.prepareStatement("INSERT INTO TABLEX VALUES(?, ?)");   

    4.来一个剥一个,然后放桌子上

    //记录1
    statement.setInt(1, 1);
    statement.setString(2, "Cujo");
    statement.addBatch();
       

    //记录2
    statement.setInt(1, 2);
    statement.setString(2, "Fred");
    statement.addBatch();
       

    //记录3
    statement.setInt(1, 3);
    statement.setString(2, "Mark");
    statement.addBatch();
       

    //批量执行上面3条语句. 一口吞了,很爽
    int [] counts = statement.executeBatch();   

    //Commit it 咽下去,到肚子(DB)里面
    connection.commit();


    statement 对象的addBatch 和 executeBatch 来实现批量添加
    stmt.addBatch("update  TABLE1 set 题目="盛夏话足部保健1"   where id="3407"");
    stmt.addBatch("update  TABLE1 set 题目="夏季预防中暑膳食1" where id="3408"");
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("11","12","13","","")");
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("12","12","13","","")");
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("13","12","13","","")");
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("14","12","13","","")");
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("15","12","13","","")");
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("16","12","13","","")");
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("17","12","13","","")");
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("18","12","13","","")");

    int [] updateCounts=stmt.executeBatch();
    cn.commit();


    实例:批量添加

    [java] view plain copy
    1. public static void insertData(List<Map<String,String>> list,Logger log){  
    2.     //获取的数据  
    3.     List <Map<String,String>> nlist= list;  
    4.     String upsql="update   hrd_staff  set position =?  where id=?";  
    5.     Iterator<Map<String,String>> iter= nlist.iterator();  
    6.     Connection con= Utils.getCon();  
    7.     int count=0;  
    8.     try {  
    9.         //在皮脸添加的时候注意事务提交方式  
    10.         con.setAutoCommit(false);  
    11.         //PreparedStatement方法的使用  
    12.         PreparedStatement pstm = con.prepareStatement(upsql);  
    13.         while(iter.hasNext()){  
    14.             count++;  
    15.             Map<String,String> map= iter.next();  
    16.             String jon_name= map.get("job_name");  
    17.             String uid= map.get("uid");  
    18.             pstm.setString(1,jon_name);  
    19.             pstm.setString(2,uid);  
    20.             //添加到缓存中  
    21.             pstm.addBatch();  
    22.             // 如果数据量很大,不能一次性批量添加所以我们要分批次添加,这里就是300条一次  
    23.             if(count%300==0){  
    24.                 //持久化  
    25.                 int []res=pstm.executeBatch();  
    26.                 //提交事务,持久化数据  
    27.                 con.commit();  
    28.                 pstm.clearBatch();  
    29.                 log.info("300整除插入结果: "+res.length);  
    30.             }  
    31.         }  
    32.         //小于300条的在这里持久化  
    33.         int []ress= pstm.executeBatch();  
    34.         //事务提交持久化  
    35.         con.commit();  
    36.         pstm.clearBatch();  
    37.         log.info("插入数据结果:"+ress.length);  
    38.     } catch (SQLException e) {  
    39.         try {  
    40.             con.rollback();  
    41.         } catch (SQLException e1) {  
    42.             // TODO Auto-generated catch block  
    43.             e1.printStackTrace();  
    44.         }  
    45.         e.printStackTrace();  
    46.     }finally{  
    47.         try {  
    48.             if(null!=con){  
    49.             con.close();  
    50.             con.setAutoCommit(true);  
    51.             }  
    52.         } catch (SQLException e) {  
    53.             // TODO Auto-generated catch block  
    54.             e.printStackTrace();  
    55.         }  
    56.     }  
    57. }  
    这里除了下面说的url中的批量设置外,我们也要注意事务的设置,不能设置为自动提交,要批量添加后在提交事务

    总结:

    addBatch() 就是把你的处理内容添加到批处理单元中。即添加到了batch中。你可以循环加入很多,数据库都不会处理,直到调用如下代码executeBatch() 此时,数据库把刚才加到batch中的命令批量处理。


    使用批量插入的好处: , 当在100次INSERT操作中使用addBatch()方法时, 只有两次网络往返. 1次往返是预储statement, 另一次是执行batch命令. 虽然Batch命令会用到更多的数据库的CPU周期, 但是通过减少网络往返,性能得到提高. 记住, JDBC的性能最大的增进是减少JDBC驱动与数据库之间的网络通讯. 如果没有使用批处理则网络往返101次这样会耗很多时间,自然效率也就一般


    这里要注意:在mysql 下使用批量执行的时候要在,url 后面添加手动设置支持批量添加 实例如下:

     String url="jdbc:mysql://localhost:3306/music?rewriteBatchedStatements=true";

    // 默认情况下rewriteBatchedStatements 的值为false 也就是批量添加功能是关闭的,如果使用则要手动开启!

    还有就是事务的设置,不能使自动提交,要批量添加后才提交!!!

    展开全文
  • JDBC之使用Statement与PreparedStatement实现增删改查

    千次阅读 多人点赞 2020-03-25 10:21:34
    本文简单的介绍了Statement与PreparedStatement对象,简述了两者的关系与区别;介绍了软件开发中的分层思想,较好的代码分层,不仅可以增加代码复用性,也因为这种分层的实现让代码的鲁棒性也更好;最后简单的师范了...

    上一文中,我们对JDBC进行了详细的分析,介绍了使用JDBC操作数据库的步骤,上篇文章更多的是一个理论分析,今天我们就动手来搞代码,两相印证下,加深我们对JDBC的理解。

    资源分配图

    ​ 废话不多说了,撸起袖子,码起来。

    1.Statement与PreparedStatement的关系与区别

    ​ 一个尴尬又不是礼貌的微笑🙂。。在开始码代码之前,还是要更简单的讲解下Statement与PreparedStatement,这两个接口都是JDBC中提供的重要接口,也是我们用来执行增删改查的主力军(CallableStatement更多的用来执行存储过程)。

    资源分配图

    ​ 首先我们来讲下两者的联系,PreparedStatement接口继承(extends)了Satement接口,也就是说数据库驱动帮我们实例化的Satement对象和PreparedStatement对象就像是父类和子类之间的关系,PreparedStatement就像一个超集,其中实现了更多的方法,可以提供更强劲的功能。

    ​ 两者之间最主要的区别就是PreparedStatement相比于Satement多了一个SQL预编译的功能,并且SQL中还可以使用?作为占位符,只需在SQL执行前设置具体的参数即可。而这个功能则使PreparedStatement能非常方便处理批量数据的变更,不仅可以更好的获得执行SQL,可以节省每次执行时SQL编译的时间,可以让此次数据操作更快的完成,并且PreparedStatement对象创建时就已经对SQL进行了预编译,因此还可有效的防止SQL注入攻击

    2.一个好的JDBC操作规范

    ​ 一个好的后端代码应该是分层的,上层的代码去调用下层封装的方法,增加代码的复用率,并且方便管理,更少出错。

    ​ 这里我们把上文中JDBC编程步骤拿来,我们按照分层的思想,来对其步骤进行分解。

    1. 加载并注册数据驱动
    2. 通过DriverManager获取数据库的连接Connection
    3. 通过Connection对象获取Statment对象
    4. 使用获取的Statment对象执行SQL语句
    5. 处理返回结果
    6. 关闭连接,释放资源

    ​ 这里,我们将步骤1、2封装成为获取连接的方法,步骤6封装成关闭连接、释放资源的方法,并将这两个方法封装成一个Util类(工具类);在dao层中,封装增、删、改、查的方法,并在每个方法中执行步骤3、4、5;在sevice层实现各个功能(方法),每个方法中调用dao的方法;在Controller(Servlet)层中调用sevice中的方法。各层的调用关系如下如所示:

    资源分配图

    ​ 这里将获取连接和关闭连接的方法封装在JDBCUtil中下面我们给出JDBCUtil.java的代码:

    import java.sql.CallableStatement;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.sql.Types;
    
    public class JDBCUtil {
    	/**
    	 * 获取连接
    	 * 
    	 * @return
    	 * @throws ClassNotFoundException
    	 * @throws SQLException
    	 */
    	public static Connection getConnection() throws ClassNotFoundException, SQLException {
        //步骤一,注册驱动
    		Class.forName("com.mysql.jdbc.Driver");
    		String url = "jdbc:mysql://localhost:3306/java_web?useSSL=false&characterEncoding=utf-8";
    		String userName = "root";
    		String password = "123456";
        //步骤二,创建连接
    		Connection connection = DriverManager.getConnection(url, userName, password);
    		return connection;
    	}
    
    	/**
    	 * 步骤六:关闭连接,释放资源 
    	 * 
    	 * @param statement
    	 * @param connection
    	 */
    	public static void release(ResultSet resultSet, Statement statement, Connection connection) {
    		if (resultSet != null) {
    			try {
    				resultSet.close();
    			} catch (SQLException e) {
    				e.printStackTrace();
    			}
    			resultSet = null;
    		}
    		if (statement != null) {
    			try {
    				statement.close();
    			} catch (SQLException e) {
    				e.printStackTrace();
    			}
    			statement = null;
    		}
    		if (connection != null) {
    			try {
    				connection.close();
    			} catch (SQLException e) {
    				e.printStackTrace();
    			}
    			connection = null;
    		}
    	}
    }
    
    

    3.增删改查的实现

    ​ 下面我们就以用户(User)对象来实现dao和简单的实现Service。

    ​ User的创建SQL如下:

    CREATE TABLE `users` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(32) DEFAULT NULL,
      `password` varchar(64) DEFAULT NULL,
      `email` varchar(32) DEFAULT NULL,
      `birthday` date DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
    

    ​ 我们创建与之对应的Java类User.java代码如下(省略了getter、setter和toString方法):

    import java.util.Date;
    
    /**
     * 用户类
     * 
     * @author lizishu
     *
     */
    public class User {
    
    	private int id;
    
    	private String userName;
    
    	private String password;
    
    	private String email;
    
    	private Date birthday;
    
    	//getXxx getter方法
      
      //setXxx setter方法
    
    	//toString
    }
    

    ​ 为了充分的展示如何使用Statement和PreparedStatement中的方法实现对users的增删改查的,UserDao中实现了如下方法,方法截图如下:

    资源分配图

    ​ UserDao的代码如下:

    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.ArrayList;
    import java.util.List;
    
    import com.zzxy.web.domain.User;
    import com.zzxy.web.utils.JDBCUtil;
    
    public class UserDao {
    
    	/**
    	 * 使用Statement来进行用户查询
    	 * 根据入参的id查询对应的用户信息
    	 * 
    	 * @param id
    	 * @return
    	 * @throws ClassNotFoundException
    	 * @throws SQLException
    	 */
    	public User findUserById(int id) throws ClassNotFoundException, SQLException {
    		Connection connection = null;
    		Statement statement = null;
    		ResultSet resultSet = null;
    		try {
    			// 获取数据库连接
    			connection = JDBCUtil.getConnection();
    			// 创建Statement对象
    			statement = connection.createStatement();
    			String sql = "SELECT * FROM users WHERE id = " + id;
    			// 获取查询结果集
    			resultSet = statement.executeQuery(sql);
    			User user = new User();
    			while (resultSet.next()) {
    				user.setId(resultSet.getInt("id"));
    				user.setUserName(resultSet.getString("name"));
    				user.setPassword(resultSet.getString("password"));
    				user.setEmail(resultSet.getString("email"));
    				user.setBirthday(resultSet.getDate("birthday"));
    			}
    			return user;
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    			throw e;
    		} catch (SQLException e) {
    			e.printStackTrace();
    			throw e;
    		} finally {
    			JDBCUtil.release(resultSet, statement, connection);
    		}
    	}
    
    	/**
    	 * 使用Statement查找表中的所有员工
    	 * 注:一般此功能需要增加分页
    	 * 
    	 * @return
    	 * @throws ClassNotFoundException
    	 * @throws SQLException
    	 */
    	public List<User> findAllUsers() throws ClassNotFoundException, SQLException {
    		Connection connection = null;
    		Statement statement = null;
    		ResultSet resultSet = null;
    		try {
    			// 获取数据库连接
    			connection = JDBCUtil.getConnection();
    			// 创建Statement对象 加上其中的两个参数可以让ResultSet中的游标可以自由移动
    			statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, 
    			ResultSet.CONCUR_READ_ONLY);
    			String sql = "SELECT * FROM users";
    			// 获取查询结果集
    			resultSet = statement.executeQuery(sql);
    			// 这些方法都可使用,具体功能可参考上篇博文
    			// resultSet.previous();
    			// resultSet.relative(1);
    			// resultSet.absolute(1);
    			// resultSet.beforeFirst();
    			// resultSet.afterLast();
    			// resultSet.first();
    			// resultSet.last();
    			List<User> userList = new ArrayList<User>();
    			// 迭代获取所有的用户
    			while (resultSet.next()) {
    				// 注意此处User必须重新new一个,否则list中所有的user都是最后一个user
    				User user = new User();
    				user.setId(resultSet.getInt("id"));
    				user.setUserName(resultSet.getString("name"));
    				user.setPassword(resultSet.getString("password"));
    				user.setEmail(resultSet.getString("email"));
    				user.setBirthday(resultSet.getDate("birthday"));
    				// 添加到列表中
    				userList.add(user);
    			}
    			// System.out.println("获取的用户信息为:" + user);
    			return userList;
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    			throw e;
    		} catch (SQLException e) {
    			e.printStackTrace();
    			throw e;
    		} finally {
    			JDBCUtil.release(resultSet, statement, connection);
    		}
    	}
    
    	/**
    	 * 使用Statement新增一个用户
    	 * 
    	 * @param userList
    	 * @return
    	 * @throws ClassNotFoundException
    	 * @throws SQLException
    	 */
    	public boolean insertUser(User user) throws ClassNotFoundException, SQLException {
    		Connection connection = null;
    		Statement statement = null;
    		ResultSet resultSet = null;
    		try {
    			// 获取数据库连接
    			connection = JDBCUtil.getConnection();
    			// 创建PreparedStatement对象
    			statement = connection.createStatement();
    			// 拼接SQL
    			// 注意下面java.tile.Date 转换为 java.sql.Date
    			String sql = "INSERT INTO users (name, password, email, birthday) VALUES ('" + user.getUserName() + "', '"
    					+ user.getPassword() + "', '" + user.getEmail() + "', '"
    					+ new java.sql.Date(user.getBirthday().getTime()) + "');";
    			// 执行SQL,并获取影响的行数
    			int result = statement.executeUpdate(sql);
    			// 如果影响的行数大于0,表示此用户插入成功
    			if (result > 0) {
    				return true;
    			}
    
    			// 返回插入成功的数量
    			return false;
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    			throw e;
    		} catch (SQLException e) {
    			e.printStackTrace();
    			throw e;
    		} finally {
    			JDBCUtil.release(resultSet, statement, connection);
    		}
    	}
    
    	/**
    	 * 使用PreparedStatement预编译sql
    	 * 一次新增多个用户
    	 * 
    	 * @param userList
    	 * @return
    	 * @throws ClassNotFoundException
    	 * @throws SQLException
    	 */
    	public int insertUser(List<User> userList) throws ClassNotFoundException, SQLException {
    		Connection connection = null;
    		PreparedStatement statement = null;
    		ResultSet resultSet = null;
    		try {
    			// 获取数据库连接
    			connection = JDBCUtil.getConnection();
    			// 构建Sql,参数使用占位符?代替
    			String sql = "INSERT INTO users (name, password, email, birthday) VALUES (?, ?, ?, ?);";
    			// 创建PreparedStatement对象
    			statement = connection.prepareStatement(sql);
    			// 总的插入结果
    			int resultCount = 0;
    			// 循环设置SQL中的参数
    			for (User tmpUser : userList) {
    				// 设置sql中占位符对应的值,index从1开始
    				statement.setString(1, tmpUser.getUserName());
    				statement.setString(2, tmpUser.getPassword());
    				statement.setString(3, tmpUser.getEmail());
    				statement.setDate(4, new java.sql.Date(tmpUser.getBirthday().getTime()));
    				// 执行SQL,并获取影响的行数
    				int result = statement.executeUpdate();
    				// 如果影响的行数大于0,表示此用户插入成功
    				if (result > 0) {
    					resultCount++;
    				}
    			}
    			return resultCount;
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    			throw e;
    		} catch (SQLException e) {
    			e.printStackTrace();
    			throw e;
    		} finally {
    			JDBCUtil.release(resultSet, statement, connection);
    		}
    	}
    
    	/**
    	 * 使用PreparedStatement更新用户
    	 * 只更新User中的非空字段
    	 * 拼接SQL中参数个数不确定时的情况
    	 * 
    	 * @param user
    	 * @return
    	 * @throws ClassNotFoundException
    	 * @throws SQLException
    	 */
    	public boolean updateUser(User user) throws ClassNotFoundException, SQLException {
    		Connection connection = null;
    		PreparedStatement statement = null;
    		ResultSet resultSet = null;
    		try {
    			// 获取数据库连接
    			connection = JDBCUtil.getConnection();
    			// 构建Sql,参数使用占位符代替
    			// 手动拼接SQL
    			String sql = "update users set ";
    			List<Object> paramList = new ArrayList<Object>();
          //如果某个属性为null,则跳过,不更新此字段
    			if (user.getUserName() != null) {
    				sql += "name = ?,";
    				paramList.add(user.getUserName());
    			}
    			if (user.getPassword() != null) {
    				sql += "password = ?,";
    				paramList.add(user.getPassword());
    			}
    			if (user.getEmail() != null) {
    				sql += "email = ?,";
    				paramList.add(user.getEmail());
    			}
    			if (user.getBirthday() != null) {
    				sql += "birthday = ?,";
    				paramList.add(user.getBirthday());
    			}
    			// 去除多余的逗号
    			sql = sql.substring(0, sql.length() - 1);
    			sql += " where id = ?";
    			// 添加id到参数列表中
    			paramList.add(user.getId());
    			// 创建PreparedStatement对象
    			statement = connection.prepareStatement(sql);
    			// 设置sql中占位符对应的值
    			for (int i = 0; i < paramList.size(); i++) {
    				statement.setObject(i + 1, paramList.get(i));
    			}
    			int result = statement.executeUpdate();
    			// 如果影响的行数大于0,表示此用户更新成功
    			if (result > 0) {
    				return true;
    			}
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    			throw e;
    		} catch (SQLException e) {
    			e.printStackTrace();
    			throw e;
    		} finally {
    			JDBCUtil.release(resultSet, statement, connection);
    		}
    		return false;
    	}
    
    	/**
    	 * 使用PreparedStatement删除用户
    	 * SQL中无占位符
    	 * 
    	 * @param id
    	 * @return
    	 * @throws ClassNotFoundException
    	 * @throws SQLException
    	 */
    	public boolean deleteUser(int id) throws ClassNotFoundException, SQLException {
    		Connection connection = null;
    		PreparedStatement statement = null;
    		ResultSet resultSet = null;
    		try {
    			// 获取数据库连接
    			connection = JDBCUtil.getConnection();
    			// 构建Sql,手动拼接SQL
    			String sql = "delete from users where id = " + id;
    			// 创建PreparedStatement对象
    			statement = connection.prepareStatement(sql);
    			int result = statement.executeUpdate();
    			// 如果影响的行数大于0,表示此用户更新成功
    			if (result > 0) {
    				return true;
    			}
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    			throw e;
    		} catch (SQLException e) {
    			e.printStackTrace();
    			throw e;
    		} finally {
    			JDBCUtil.release(resultSet, statement, connection);
    		}
    		return false;
    	}
    
    }
    
    

    ​ 上面,我们分别实现了findUserById(int)findAllUsers()insertUser(User)insertUser(List<User>)updateUser(User)deleteUser(int)这六个方法,使用Satement、PreparedStatement实现对user的增删改查,并且演示了SQL拼接、具体占位符数量未知如何写SQL、批量插入、ResultSet中游标的使用等。

    ​ 下面我们简单的实现一个UserService类(具体的Service方法还需要根据业务来写,此处的Service主要是为了测试),其中实现的方法如下:

    资源分配图

    ​ 其代码如下,这里使用了单例模式,使用懒汉式创建UserService。

    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    
    import com.zzxy.web.dao.UserDao;
    import com.zzxy.web.domain.User;
    
    /**
     * User的Service类 使用单例模式,保证service在整个项目中唯一
     * 
     * @author lizishu
     *
     */
    public class UserService {
    
    	private static UserService instance;
    
    	private static UserDao userDao = null;
    
    	// 私有的构造函数,在UserService类外部无法调用
    	private UserService() {
    	}
    
    	// 单例 懒汉式
    	public static UserService getInstance() {
    		// 第一次调用时初始化UserService、UserDao
    		if (instance == null) {
    			instance = new UserService();
    			userDao = new UserDao();
    		}
    		return instance;
    	}
    
    	/**
    	 * 根据id查询用户测试
    	 * 
    	 * @param id
    	 * @throws SQLException
    	 * @throws ClassNotFoundException
    	 */
    	public void testSelectUserById(int id) throws ClassNotFoundException, SQLException {
    		System.out.println("查用用户,入参为:" + id);
    		User user = userDao.findUserById(id);
    		// ...
    		System.out.println("用户查询成功,查询到的用户为:" + user);
    	}
    
    	/**
    	 * 查询所有用户测试
    	 * 
    	 * @throws ClassNotFoundException
    	 * @throws SQLException
    	 */
    	public List<User> testFindAllUsers() throws ClassNotFoundException, SQLException {
    		System.out.println("查用所有用户");
    		List<User> userList = userDao.findAllUsers();
    		// ...
    		System.out.println("FindAllUsers执行成功,查询到的用户数量为:" + userList.size());
    		return userList;
    	}
    
    	/**
    	 * 测试使用Statement一次插入一个用户
    	 * 
    	 * @throws SQLException
    	 * @throws ClassNotFoundException
    	 */
    	public void testInsertUser(User user) throws ClassNotFoundException, SQLException {
    		System.out.println("新增用户");
    		boolean result = userDao.insertUser(user);
    		// ...
    		System.out.println("新增用户执行结果为" + result);
    	}
    
    	/**
    	 * 测试使用PreparedStatement一次插入多个用户
    	 * 
    	 * @throws SQLException
    	 * @throws ClassNotFoundException
    	 */
    	public void testInsertUser(List<User> userList) throws ClassNotFoundException, SQLException {
    		System.out.println("批量插入多个用户,开始时间为:" + System.currentTimeMillis());
    		long beginTimes = System.currentTimeMillis();
    		// 如果传入的list为null或其中无数据,方法结束
    		if (userList == null || userList.isEmpty()) {
    			return;
    		}
    		int result = userDao.insertUser(userList);
    		// ...
    		System.out.println("批量插入执行成功,结束时间为:" + System.currentTimeMillis() + ", 插入结果为" + result);
    		long millis = System.currentTimeMillis() - beginTimes;
    		System.out.println("PreparedStatement新增用户成功,耗时:" + millis + "豪秒");
    	}
    
    	/**
    	 * 测试更新用户
    	 * 
    	 * @param user
    	 * @throws SQLException
    	 * @throws ClassNotFoundException
    	 */
    	public void testUpdateUser(User user) throws ClassNotFoundException, SQLException {
    		System.out.println("更新指定用户信息");
    		boolean result = userDao.updateUser(user);
    		// ...
    		System.out.println("用户更新执行结果为:" + result);
    	}
    
    	/**
    	 * 测试删除用户
    	 * 
    	 * @param user
    	 * @throws SQLException
    	 * @throws ClassNotFoundException
    	 */
    	public void testDeleteUser(int id) throws ClassNotFoundException, SQLException {
    		System.out.println("删除指定用户信息:id=" + id);
    		boolean result = userDao.deleteUser(id);
    		// ...
    		System.out.println("删除用户执行结果为:" + result);
    	}
    
    }
    
    

    4.测试

    ​ 关于User类的操作,dao层、Service层都已实现,一般来说,就可以在Servlet中进行Service的加载,并调用其中封装好的方法,完成业务逻辑并将处理结果返回给客户端即可。

    ​ 这里为了方便测试,我们就使用最简单的main函数来进行调用(这里推荐使用单元测试–Junit),下面代码的测试顺序为:用户的插入–>查询所有用户–>更新某个用户–>查询更新用户–>删除某个用户。

    public static void main(String[] args) {
      UserService userService = UserService.getInstance();
      // 批量构造10个user,并存入userList中
      List<User> userList = new ArrayList<User>();
      String initName = "test-";
      for (int i = 1; i <= 10; i++) {
        User user = new User();
        user.setUserName(initName + i);
        user.setPassword("123456");
        user.setEmail("xxxxxxx");
        user.setBirthday(new Date());
        userList.add(user);
      }
      // 一般来讲,异常抛到Controller层就需要处理了,否则发生异常时会将异常的堆栈信息抛给用户
      try {
        // 使用过Statement对象插入单用户--该对象是从userList中获取的队首的用户
        userService.testInsertUser(userList.get(0));
        // 使用过PreparedStatement对象插入用户列表
        userService.testInsertUser(userList);
        // 查询所有用户,即上面新插入的十一条用户数据
        userList = userService.testFindAllUsers();
        for (User tmp : userList) {
          System.out.println("查询到的用户:" + tmp);
        }
        // 更新userList中index为5数据
        User user = userList.get(5);
        System.out.println("更新数据的id为:" + user.getId());
        user.setPassword("11111");
        // 注意""与null 在UserDao中updateUser中产生的不同效果
        user.setEmail("");
        user.setBirthday(null);
        userService.testUpdateUser(user);
        // 查询上一条更新后的数据
        userService.testSelectUserById(user.getId());
        // 删除userList中index为7数据
        userService.testDeleteUser(userList.get(7).getId());
      } catch (ClassNotFoundException | SQLException e) {
        // 记录异常信息,一般使用error级别
        System.out.println("方法执行异常,异常信息为:" + e.getMessage());
        e.printStackTrace();
      }
    }
    

    ​ 上面测试代码运行后,控制台输出信息如下,从下图可以看到,单次插入了一个用户,批量插入了10个用户,因此查询处的所有用户工11个,对id为6的员工进行更新,查询后可以发现,password顺利修改了,而email和birthday也是一个被更新为空字符串,一个无变化,最后删除了id为8的用户。

    资源分配图

    ​ 为了展示上面代码对数据库的操作,我们来看下数据库中users表的截图,可以看到,id为6的用户已经被更新了,而id为8的用户也已被删除。

    资源分配图

    4.总结

    ​ 本文简单的介绍了Statement与PreparedStatement对象,简述了两者的关系与区别;介绍了软件开发中的分层思想,较好的代码分层,不仅可以增加代码复用性,也因为这种分层的实现让代码的鲁棒性也更好;最后简单的师范了下如何分层开发代码,并演示了使用Statement与PreparedStatement完成数据库的增删改查。

    ​ 在使用JDBC操作数据库时,Statement与PreparedStatement两个相比,推荐大家使用PreparedStatement,因为其更方便、更高效、更安全。

    ​ Statement效果一般,PreparedStatement效果不错,但是若要和ORM框架比起来,我还是选Mybatis。。。

    参考阅读:

    1. Java Web数据库开发(MySQL)之环境准备
    2. 写给求甚解的你—JDBC详解

    ​ 又到了分隔线以下,本文到此就结束了,本文内容全部都是由博主自己进行整理并结合自身的理解进行总结,如果有什么错误,还请批评指正。

    ​ Java web这一专栏会是一个系列博客,喜欢的话可以持续关注,如果本文对你有所帮助,还请还请点赞、评论加关注。

    ​ 有任何疑问,可以评论区留言。

    展开全文
  • Java数据库JDBC——prepareStatement的用法和解释

    万次阅读 多人点赞 2019-01-10 17:06:11
    一、prepareStatement 的用法和解释 1.PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程 2.使用 Statement 对象。在对数据库只执行一次性存取的时侯,用 Statement 对象进行处理。...

    转自:http://blog.csdn.net/QH_JAVA/article/details/48245945

    一、prepareStatement 的用法和解释

    1.PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程

    2.使用 Statement 对象。在对数据库只执行一次性存取的时侯,用 Statement 对象进行处理。PreparedStatement 对象的开销比Statement大,对于一次性操作并不会带来额外的好处。
    3.statement每次执行sql语句,相关数据库都要执行sql语句的编译,preparedstatement是预编译得, preparedstatement支持批处理

    4、

    Code Fragment 1:

    String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERE COF_NAME LIKE ′Colombian′";
    stmt.executeUpdate(updateString);
    


    Code Fragment 2:

    PreparedStatement updateSales = con.prepareStatement("UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? ");
    updateSales.setInt(1, 75);
    updateSales.setString(2, "Colombian");
    updateSales.executeUpdate();


    片断2和片断1的区别在于,后者使用了PreparedStatement对象,而前者是普通的Statement对象。PreparedStatement对象不仅包含了SQL语句,而且大多数情况下这个语句已经被预编译过,因而当其执行时,只需DBMS运行SQL语句,而不必先编译。当你需要执行Statement对象多次的时候,PreparedStatement对象将会大大降低运行时间,当然也加快了访问数据库的速度。
    这种转换也给你带来很大的便利,不必重复SQL语句的句法,而只需更改其中变量的值,便可重新执行SQL语句。选择PreparedStatement对象与否在于相同句法SQL语句是否执行了多次,而且两次之间的差别仅仅是变量的不同。如果仅仅执行了一次的话,它应该和普通的对象毫无差异,体现不出它预编译的优越性。

     

    5.执行许多SQL语句的JDBC程序产生大量的Statement和PreparedStatement对象。通常认为PreparedStatement对象比Statement对象更有效,特别是如果带有不同参数的同一SQL语句被多次执行的时候。PreparedStatement对象允许数据库预编译SQL语句,这样在随后的运行中可以节省时间并增加代码的可读性。


    然而,在Oracle环境中,开发人员实际上有更大的灵活性。当使用Statement或PreparedStatement对象时,Oracle数据库会缓存SQL语句以便以后使用。在一些情况下,由于驱动器自身需要额外的处理和在Java应用程序和Oracle服务器间增加的网络活动,执行PreparedStatement对象实际上会花更长的时间。

    然而,除了缓冲的问题之外,至少还有一个更好的原因使我们在企业应用程序中更喜欢使用PreparedStatement对象,那就是安全性。传递给PreparedStatement对象的参数可以被强制进行类型转换,使开发人员可以确保在插入或查询数据时与底层的数据库格式匹配。

    当处理公共Web站点上的用户传来的数据的时候,安全性的问题就变得极为重要。传递给PreparedStatement的字符串参数会自动被驱动器忽略。最简单的情况下,这就意味着当你的程序试着将字符串“D'Angelo”插入到VARCHAR2中时,该语句将不会识别第一个“,”,从而导致悲惨的失败。几乎很少有必要创建你自己的字符串忽略代码。

    在Web环境中,有恶意的用户会利用那些设计不完善的、不能正确处理字符串的应用程序。特别是在公共Web站点上,在没有首先通过PreparedStatement对象处理的情况下,所有的用户输入都不应该传递给SQL语句。此外,在用户有机会修改SQL语句的地方,如HTML的隐藏区域或一个查询字符串上,SQL语句都不应该被显示出来。
    在执行SQL命令时,我们有二种选择:可以使用PreparedStatement对象,也可以使用Statement对象。无论多少次地使用同一个SQL命令,PreparedStatement都只对它解析和编译一次。当使用Statement对象时,每次执行一个SQL命令时,都会对它进行解析和编译。 

    第一:

    prepareStatement会先初始化SQL,先把这个SQL提交到数据库中进行预处理,多次使用可提高效率。  
    Statement不会初始化,没有预处理,没次都是从0开始执行SQL

    第二:

    prepareStatement可以替换变量  
    在SQL语句中可以包含?,可以用ps=conn.prepareStatement("select * from Cust where ID=?");  
    int sid=1001;  
    ps.setInt(1, sid);  
    rs = ps.executeQuery();  
    可以把?替换成变量。  
    而Statement只能用  
    int sid=1001;  
    Statement stmt = conn.createStatement();  
    ResultSet rs = stmt.executeQuery("select * from Cust where ID="+sid);  
    来实现。

     

    二、深入理解statement 和prepareStatement

     

    1、使用Statement而不是PreparedStatement对象
    JDBC驱动的最佳化是基于使用的是什么功能. 选择PreparedStatement还是Statement取决于你要怎么使用它们. 对于只执行一次的SQL语句选择Statement是最好的. 相反, 如果SQL语句被多次执行选用PreparedStatement是最好的.

    PreparedStatement的第一次执行消耗是很高的. 它的性能体现在后面的重复执行. 例如, 假设我使用Employee ID, 使用prepared的方式来执行一个针对Employee表的查询. JDBC驱动会发送一个网络请求到数据解析和优化这个查询. 而执行时会产生另一个网络请求.在JDBC驱动中,减少网络通讯是最终的目的. 如果我的程序在运行期间只需要一次请求, 那么就使用Statement. 对于Statement, 同一个查询只会产生一次网络到数据库的通讯.

    对于使用PreparedStatement池的情况下, 本指导原则有点复杂. 当使用PreparedStatement池时, 如果一个查询很特殊, 并且不太会再次执行到, 那么可以使用Statement. 如果一个查询很少会被执行,但连接池中的Statement池可能被再次执行, 那么请使用PreparedStatement. 在不是Statement池的同样情况下, 请使用Statement.

    2、使用PreparedStatement的Batch功能
    Update大量的数据时, 先Prepare一个INSERT语句再多次的执行, 会导致很多次的网络连接. 要减少JDBC的调用次数改善性能, 你可以使用PreparedStatement的AddBatch()方法一次性发送多个查询给数据库. 例如, 让我们来比较一下下面的例子.

    例 1: 多次执行PreparedStatement,多次数据库请求(网络请求)

    PreparedStatement ps = conn.prepareStatement(  
       "INSERT into employees values (?, ?, ?)");  
      
    for (n = 0; n < 100; n++) {  
      
      ps.setString(name[n]);  
      ps.setLong(id[n]);  
      ps.setInt(salary[n]);  
      ps.executeUpdate();  
    }  

    例 2: 使用Batch,以此请求执行多条

    PreparedStatement ps = conn.prepareStatement(  
       "INSERT into employees values (?, ?, ?)");  
      
    for (n = 0; n < 100; n++) {  
      
      ps.setString(name[n]);  
      ps.setLong(id[n]);  
      ps.setInt(salary[n]);  
      ps.addBatch();  
    }  
    ps.executeBatch();  

      在例 1中, PreparedStatement被用来多次执行INSERT语句. 在这里, 执行了100次INSERT操作, 共有101次网络往返.

       其中,1次往返是预储PreparedStatement, 另外100次往返执行每个迭代.

    在例2中, 当在100次INSERT操作中使用addBatch()方法时, 只有两次网络往返.

       1次往返是预储PreparedStatement, 另一次是执行batch命令. 虽然Batch命令会用到更多的数据库的CPU周期, 但是通过减少网络往返,性能得到提高.记住, JDBC的性能最大的增进是减少JDBC驱动与数据库之间的网络通讯.次数


      注:Oracel 10G的JDBC Driver限制最大Batch size是16383条,如果addBatch超过这个限制,那么executeBatch时就会出现“无效的批值”(Invalid Batch Value) 异常。因此在如果使用的是Oracle10G,在此bug减少前,Batch size需要控制在一定的限度。

           同样mysql 5.5.28 批量执行的数据最大限度是多少不清楚,但自己试了1w,2w,3w 都没问题,记得在url 后面添加:rewriteBatchedStatements=true 表示批量插入,如果不添加的话即使使用addbatch() ,executeBatch() 在后台入库的地方还是不会一次请求入库而是多次请求入库。

     

    3、选择合适的光标类型
    的光标类型以最大限度的适用你的应用程序. 本节主要讨论三种光标类型的性能问题.
    对于从一个表中顺序读取所有记录的情况来说, Forward-Only型的光标提供了最好的性能. 获取表中的数据时, 没有哪种方法比使用Forward-Only型的光标更快. 但不管怎样, 当程序中必须按无次序的方式处理数据行时, 这种光标就无法使用了.

    对于程序中要求与数据库的数据同步以及要能够在结果集中前后移动光标, 使用JDBC的Scroll-Insensitive型光标是较理想的选择. 此类型的光标在第一次请求时就获取了所有的数据(当JDBC驱动采用'lazy'方式获取数据时或许是很多的而不是全部的数据)并且储存在客户端. 因此, 第一次请求会非常慢, 特别是请求长数据时会理严重. 而接下来的请求并不会造成任何网络往返(当使用'lazy'方法时或许只是有限的网络交通) 并且处理起来很快. 因为第一次请求速度很慢, Scroll-Insensitive型光标不应该被使用在单行数据的获取上. 当有要返回长数据时, 开发者也应避免使用Scroll-Insensitive型光标, 因为这样可能会造成内存耗尽. 有些Scroll-Insensitive型光标的实现方式是在数据库的临时表中缓存数据来避免性能问题, 但多数还是将数据缓存在应用程序中.

    Scroll-Sensitive型光标, 有时也称为Keyset-Driven光标, 使用标识符, 像数据库的ROWID之类. 当每次在结果集移动光标时, 会重新该标识符的数据. 因为每次请求都会有网络往返, 性能可能会很慢. 无论怎样, 用无序方式的返回结果行对性能的改善是没有帮助的.

    现在来解释一下这个, 来看这种情况. 一个程序要正常的返回1000行数据到程序中. 在执行时或者第一行被请求时, JDBC驱动不会执行程序提供的SELECT语句. 相反, 它会用键标识符来替换SELECT查询, 例如, ROWID. 然后修改过的查询都会被驱动程序执行,跟着会从数据库获取所有1000个键值. 每一次对一行结果的请求都会使JDBC驱动直接从本地缓存中找到相应的键值, 然后构造一个包含了'WHERE ROWID=?'子句的最佳化查询, 再接着执行这个修改过的查询, 最后从服务器取得该数据行.

    当程序无法像Scroll-Insensitive型光标一样提供足够缓存时, Scroll-Sensitive型光标可以被替代用来作为动态的可滚动的光标. 

    4、使用有效的getter方法
    JDBC提供多种方法从ResultSet中取得数据, 像getInt(), getString(), 和getObject()等等. 而getObject()方法是最泛化了的, 提供了最差的性能。 这是因为JDBC驱动必须对要取得的值的类型作额外的处理以映射为特定的对象. 所以就对特定的数据类型使用相应的方法.

    更进一步的改善性能, 应在取得数据时提供字段的索引号, 例如, getString(1), getLong(2), 和getInt(3)等来替代字段名. 如果没有指定字段索引号, 网络交通不会受影响, 但会使转换和查找的成本增加. 例如, 假设你使用getString("foo") ... JDBC驱动可能会将字段名转为大写(如果需要), 并且在到字段名列表中逐个比较来找到"foo"字段. 如果可以, 直接使用字段索引, 将为你节省大量的处理时间.

    例如, 假设你有一个100行15列的ResultSet, 字段名不包含在其中. 你感兴趣的是三个字段 EMPLOYEENAME (字串型), EMPLOYEENUMBER (长整型), 和SALARY (整型). 如果你指定getString(“EmployeeName”), getLong(“EmployeeNumber”), 和getInt(“Salary”), 查询旱每个字段名必须被转换为metadata中相对应的大小写, 然后才进行查找. 如果你使用getString(1), getLong(2), 和getInt(15). 性能就会有显著改善.

    5、获取自动生成的键值
    有许多数据库提供了隐藏列为表中的每行记录分配一个唯一键值. 很典型, 在查询中使用这些字段类型是取得记录值的最快的方式, 因为这些隐含列通常反应了数据在磁盘上的物理位置. 在JDBC3.0之前, 应用程序只可在插入数据后通过立即执行一个SELECT语句来取得隐含列的值.

    例 3: JDBC3.0之前

    //插入行  
    int rowcount = stmt.executeUpdate (  
       "insert into LocalGeniusList (name) values ('Karen')");  
    // 现在为新插入的行取得磁盘位置 - rowid  
    ResultSet rs = stmt.executeQuery (  
       "select rowid from LocalGeniusList where name = 'Karen'");  

    这种取得隐含列的方式有两个主要缺点. 第一, 取得隐含列是在一个独立的查询中, 它要透过网络送到服务器后再执行. 第二, 因为不是主键, 查询条件可能不是表中的唯一性ID. 在后面一个例子中, 可能返回了多个隐含列的值, 程序无法知道哪个是最后插入的行的值.


    (译者:由于不同的数据库支持的程度不同,返回rowid的方式各有差异。在SQL Server中,返回最后插入的记录的id可以用这样的查询语句:SELECT @IDENTITY )

    JDBC3.0规范中的一个可选特性提供了一种能力, 可以取得刚刚插入到表中的记录的自动生成的键值. 

    例 4: JDBC3.0之后

    int rowcount = stmt.executeUpdate (  
       "insert into LocalGeniusList (name) values ('Karen')",  
    // 插入行并返回键值  
    Statement.RETURN_GENERATED_KEYS);  
    ResultSet rs = stmt.getGeneratedKeys ();  
    // 得到生成的键值  

    现在, 程序中包含了一个唯一性ID, 可以用来作为查询条件来快速的存取数据行, 甚至于表中没有主键的情况也可以.

    这种取得自动生成的键值的方式给JDBC的开发者提供了灵活性, 并且使存取数据的性能得到提升.

    6、选择合适的数据类型
    接收和发送某些数据可能代价昂贵. 当你设计一个schema时, 应选择能被最有效地处理的数据类型. 例如, 整型数就比浮点数或实数处理起来要快一些. 浮点数的定义是按照数据库的内部规定的格式, 通常是一种压缩格式. 数据必须被解压和转换到另外种格式, 这样它才能被数据的协议处理.

    7、获取ResultSet
    由于数据库系统对可滚动光标的支持有限, 许多JDBC驱动程序并没有实现可滚动光标. 除非你确信数据库支持可滚动光标的结果集, 否则不要调用rs.last()和rs.getRow()方法去找出数据集的最大行数. 因为JDBC驱动程序模拟了可滚动光标, 调用rs.last()导致了驱动程序透过网络移到了数据集的最后一行. 取而代之, 你可以用ResultSet遍历一次计数或者用SELECT查询的COUNT函数来得到数据行数.

    通常情况下,请不要写那种依赖于结果集行数的代码, 因为驱动程序必须获取所有的数据集以便知道查询会返回多少行数据.
     

    三、preparestatement 防止sql注入

    在JDBC应用中,如果你已经是稍有水平开发者,你就应该始终以PreparedStatement代替Statement.也就是说,在任何时候都不要使用Statement.基于以下的原因:

    1、代码的可读性和可维护性.虽然用PreparedStatement来代替Statement会使代码多出几行,但这样的代码无论从可读性还是可维护性上来说.都比直接用Statement的代码高很多档次:
    stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");
    perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");

    perstmt.setString(1,var1);perstmt.setString(2,var2);perstmt.setString(3,var3);perstmt.setString(4,var4);

    perstmt.executeUpdate();
    不用我多说,对于第一种方法.别说其他人去读你的代码,就是你自己过一段时间再去读,都会觉得伤心.

    2、PreparedStatement尽最大可能提高性能.每一种数据库都会尽最大努力对预编译语句提供最大的性能优化.因为预编译语句有可能被重复调用.所以语句在被DB的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中(相当于一个涵数)就会得到执行.这并不是说只有一个 Connection中多次执行的预编译语句被缓存,而是对于整个DB中,只要预编译的语句语法和缓存中匹配.那么在任何时候就可以不需要再次编译而可以直接执行.而statement的语句中,即使是相同一操作,而由于每次操作的数据不同所以使整个语句相匹配的机会极小,几乎不太可能匹配.比如:insert into tb_name (col1,col2) values ('11','22');insert into tb_name (col1,col2) values ('11','23');即使是相同操作但因为数据内容不一样,所以整个个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存.这样每执行一次都要对传入的语句编译一次.
    当然并不是所以预编译语句都一定会被缓存,数据库本身会用一种策略,比如使用频度等因素来决定什么时候不再缓存已有的预编译结果.以保存有更多的空间存储新的预编译语句.

    3、最重要的一点是极大地提高了安全性.
    即使到目前为止,仍有一些人连基本的恶义SQL语法都不知道.String sql = "select * from tb_name where name= '"+varname+"' and passwd='"+varpasswd+"'";如果我们把[' or '1' = '1]作为varpasswd传入进来.用户名随意,看看会成为什么?
    select * from tb_name = '随意' and passwd = '' or '1' = '1';因为'1'='1'肯定成立,所以可以任何通过验证.更有甚者:把[';drop table tb_name;]作为varpasswd传入进来,则:select * from tb_name = '随意' and passwd = '';drop table tb_name;有些数据库是不会让你成功的,但也有很多数据库就可以使这些语句得到执行.
    而如果你使用预编译语句.你传入的任何内容就不会和原来的语句发生任何匹配的关系.(前提是数据库本身支持预编译,但上前可能没有什么服务端数据库不支持编译了,只有少数的桌面数据库,就是直接文件访问的那些)只要全使用预编译语句,你就用不着对传入的数据做任何过虑.而如果使用普通的statement, 有可能要对drop,;等做费尽心机的判断和过虑.
    上面的几个原因,还不足让你在任何时候都使用PreparedStatement吗?

     

    总结: 上面是三篇文章,三篇文章详细介绍了statement 和preparestatement 两个对象的使用以及效率、安全问题。在实际项目中如果能够使用preparestatement  还是建议使用preparestatement  原因有3:

    1)、上面说了 如果sql中只有数值在变则效率高

    2)、preparestatement 具有防sql注入

    3)、代码可读性比较好

     

    实例:下面这个比喻很好,很明确的说明了批量添加,并且从中也可以看出在批量添加的时候PreparedStatement为什么比Statement快的原因~

    Statement和PreparedStatement的区别就不多废话了,直接说PreparedStatement最重要的addbatch()结构的使用.

    PreparedStatement  的addBatch和executeBatch实现批量添加

    1.建立链接   

       Connection    connection =getConnection();

    2.不自动 Commit (瓜子不是一个一个吃,全部剥开放桌子上,然后一口舔了)

    connection.setAutoCommit(false);   

    3.预编译SQL语句,只编译一回哦,效率高啊.(发明一个剥瓜子的方法,以后不要总想怎么剥瓜子好.就这样剥.)
    PreparedStatement statement = connection.prepareStatement("INSERT INTO TABLEX VALUES(?, ?)");   

    4.来一个剥一个,然后放桌子上

    //记录1
    statement.setInt(1, 1); 
    statement.setString(2, "Cujo"); 
    statement.addBatch();   

    //记录2
    statement.setInt(1, 2); 
    statement.setString(2, "Fred"); 
    statement.addBatch();   

    //记录3
    statement.setInt(1, 3); 
    statement.setString(2, "Mark"); 
    statement.addBatch();   

    //批量执行上面3条语句. 一口吞了,很爽
    int [] counts = statement.executeBatch();   

    //Commit it 咽下去,到肚子(DB)里面
    connection.commit();

     

    statement 对象的addBatch 和 executeBatch 来实现批量添加
    stmt.addBatch("update  TABLE1 set 题目="盛夏话足部保健1"   where id="3407"");
    stmt.addBatch("update  TABLE1 set 题目="夏季预防中暑膳食1" where id="3408""); 
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("11","12","13","","")"); 
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("12","12","13","","")"); 
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("13","12","13","","")"); 
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("14","12","13","","")"); 
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("15","12","13","","")"); 
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("16","12","13","","")"); 
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("17","12","13","","")"); 
    stmt.addBatch("INSERT INTO  TABLE1  VALUES("18","12","13","","")"); 

    int [] updateCounts=stmt.executeBatch(); 
    cn.commit();

     

    实例:批量添加

    public static void insertData(List<Map<String,String>> list,Logger log){  
        //获取的数据  
        List <Map<String,String>> nlist= list;  
        String upsql="update   hrd_staff  set position =?  where id=?";  
        Iterator<Map<String,String>> iter= nlist.iterator();  
        Connection con= Utils.getCon();  
        int count=0;  
        try {  
            //在皮脸添加的时候注意事务提交方式  
            con.setAutoCommit(false);  
            //PreparedStatement方法的使用  
            PreparedStatement pstm = con.prepareStatement(upsql);  
            while(iter.hasNext()){  
                count++;  
                Map<String,String> map= iter.next();  
                String jon_name= map.get("job_name");  
                String uid= map.get("uid");  
                pstm.setString(1,jon_name);  
                pstm.setString(2,uid);  
                //添加到缓存中  
                pstm.addBatch();  
                // 如果数据量很大,不能一次性批量添加所以我们要分批次添加,这里就是300条一次  
                if(count%300==0){  
                    //持久化  
                    int []res=pstm.executeBatch();  
                    //提交事务,持久化数据  
                    con.commit();  
                    pstm.clearBatch();  
                    log.info("300整除插入结果: "+res.length);  
                }  
            }  
            //小于300条的在这里持久化  
            int []ress= pstm.executeBatch();  
            //事务提交持久化  
            con.commit();  
            pstm.clearBatch();  
            log.info("插入数据结果:"+ress.length);  
        } catch (SQLException e) {  
            try {  
                con.rollback();  
            } catch (SQLException e1) {  
                // TODO Auto-generated catch block  
                e1.printStackTrace();  
            }  
            e.printStackTrace();  
        }finally{  
            try {  
                if(null!=con){  
                con.close();  
                con.setAutoCommit(true);  
                }  
            } catch (SQLException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }  
    }  

    这里除了下面说的url中的批量设置外,我们也要注意事务的设置,不能设置为自动提交,要批量添加后在提交事务

     

    总结:

    addBatch() 就是把你的处理内容添加到批处理单元中。即添加到了batch中。你可以循环加入很多,数据库都不会处理,直到调用如下代码executeBatch() 此时,数据库把刚才加到batch中的命令批量处理。

     

    使用批量插入的好处: , 当在100次INSERT操作中使用addBatch()方法时, 只有两次网络往返. 1次往返是预储statement, 另一次是执行batch命令. 虽然Batch命令会用到更多的数据库的CPU周期, 但是通过减少网络往返,性能得到提高. 记住, JDBC的性能最大的增进是减少JDBC驱动与数据库之间的网络通讯. 如果没有使用批处理则网络往返101次这样会耗很多时间,自然效率也就一般

     

    这里要注意:在mysql 下使用批量执行的时候要在,url 后面添加手动设置支持批量添加 实例如下:

     String url="jdbc:mysql://localhost:3306/music?rewriteBatchedStatements=true";

    // 默认情况下rewriteBatchedStatements 的值为false 也就是批量添加功能是关闭的,如果使用则要手动开启!

    还有就是事务的设置,不能使自动提交,要批量添加后才提交!!!

    展开全文
  • java中PreparedStatement和Statement详细讲解

    万次阅读 多人点赞 2019-03-05 22:12:14
    大家都知道PreparedStatement对象可以防止sql注入,而Statement不能防止sql注入,那么大家知道为什么PreparedStatement对象可以防止sql注入 ...
  • preparedStatement和Statement区别

    万次阅读 多人点赞 2018-12-16 20:45:26
      一、概念 PreparedStatement是用来执行SQL查询语句的API之一,Java提供了 Statement、PreparedStatement 和 CallableStatement三种方式来执行查询语句,其中 Statement 用于通用查询, Prep...
  • 描述Connection、Statement、ResultSet接口的作用
  • Statement_Tracer_for_Oracle

    2012-08-13 16:59:05
    [一个能监控本机进程访问Oracle数据库的软件(查看执行的Sql语句)
  • Statement对象详解

    千次阅读 2020-09-25 17:01:47
    jdbc中的statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。 CRUD操作 – create 使用executeUpdate(String sql)方法完成数据添加操作,示例...
  • Statement和PrepareStatement的区别详解

    万次阅读 2019-07-30 22:21:04
    1.PreparedStatement继承自Statement,两者都是接口。 2.内部都要建立类似于Sockt连接,效率都不是特别高。 从资源利用和安全的角度区分两者的不同: 关于批处理时如何选择,以数据量大小位标准分三种情况: 1)...
  • 功能 mybatis执行语句的操作是由StatementHandler完成的,它会去...获取连接,根据配置准备数据库可执行的statement语句。 执行语句 通过ResultSetHandler根据resultMapping封装成为你想要的的实体对象 UML Stateme
  • PreparedStatement和Statement的区别和效率

    千次阅读 2018-01-22 20:57:35
    同样也是在一次面试中问到的,当时回答说PreparedStatement比Statement效率高; 其实这个回答是错误的!掌握的还是不够! 一、PreparedStatement相比于Statement,有三个优点:一)代码的可读性和可维护性。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 570,908
精华内容 228,363
关键字:

statement