精华内容
下载资源
问答
  • JDBC预编译语句

    万次阅读 2013-02-28 10:37:22
    预编译语句PreparedStatement 是java.sql的一个接口,它是Statement的子接口。通过Statement对象执行SQL语句时,需要将SQL语句发送给DBMS,由DBMS首先进行编译后再执行。预编译语句和Statement不同,创建...
    什么是预编译语句
    预编译语句PreparedStatement 是java.sql中的一个接口,它是Statement的子接口。通过Statement对象执行SQL语句时,需要将SQL语句发送给DBMS,由DBMS首先进行编译后再执行。预编译语句和Statement不同,在创建PreparedStatement 对象时就指定了SQL语句,该语句立即发送给DBMS进行编译。当该编译语句被执行时,DBMS直接运行编译后的SQL语句,而不需要像其他SQL语句那样首先将其编译。
     
    什么时候使用预编译语句
    一般是在需要反复使用一个SQL语句时才使用预编译语句,预编译语句常常放在一个for或者while循环里面使用,通过反复设置参数从而多次使用该SQL语句;为了防止SQL注入漏洞,在某些数据操作中也使用预编译语句。

    JDBC使用预编译SQL的好处


    1. 执行效率

    PreparedStatement可以尽可能的提高访问数据库的性能,数据库在处理SQL语句时都有一个预编译的过程,而预编译对象就是把一些格式固定的SQL编译后,存放在内存池中即数据库缓冲池,当我们再次执行相同的SQL语句时就不需要预编译的过程了,只需DBMS运行SQL语句。所以当你需要执行Statement对象多次的时候,PreparedStatement对象将会大大降低运行时间,特别是的大型的数据库中,它可以有效的也加快了访问数据库的速度。


    2. 代码可读性、可维护性
    比如向记录用户信息的表中插入记录: user(id, password, name, email, address)。使用Statement的SQ语句如下:
    String sqlString ="insert into user values('"+
    user.id + "', '"+
    user.password + "', '" +
    user.name + "', '" +
    user.email + "', '" +
    user.address + "')";
    

    使用PrearedStatement的SQL语句如下:

    String sqlString ="insert into user(id, password, name, email, address) values(?, ?, ?, ?, ?)";
    PreparedStatement pstmt = connection.PreparedStatement(sqlString);
    pstmt.setString(1, user.id);
    pstmt.setString(2, user.password);
    pstmt.setString(3, user.name);
    pstmt.setString(4, user.email);
    pstmt.setString(5, user.address);


    使用占位符?代替参数,将参数与SQL语句分离出来,这样就可以方便对程序的更改和延续,同样,也可以减少不必要的错误。


    3. SQL执行的安全性
    SQL注入攻击:是从客户端输入一些非法的特殊字符,从而使服务器端在构造SQL语句时仍能正确构造,从而收集程序和服务器的信息或数据。比如在Web信息系统的登录入口处,要求用户输入用户名和密码,客户端输入后,服务器端根据用户输入的信息来构造SQL语句,在数据库中查询是否存在此用户名以及密码是否正确。假设使用上述例子中的表“user”构造SQL语句的Java程序可能是:
    1
    sqlString = "select * from user where user.id = '" + userID + "' and user.password = "' + userPassword + "'";
    其中userID, userPassword是从用户输入的用户名及密码。如果用户和密码输的都是 '1' or '1'='1',则服务器端生成的SQL语句如下:
    1
    sqlString = "select * from user where user.id ='1'or'1'='1'and user.password ='1'or'1'='1'

    这个SQL语句中的where字句没有起到数据筛选的作用,因为只要数据库中有记录,就会返回一个结果不为空的记录集,查询就会通过。上面的例子说明:在Web环境中,有恶意的用户会利用那些设计不完善的、不能正确处理字符串的应用程序。特别是在公共Web站点上,在没有首先通过PreparedStatement对象处理的情况下,所有的用户输入都不应该传递给SQL语句。此外,在用户有机会修改SQL语句的地方,如HTML的隐藏区域或一个查询字符串上,SQL语句都不应该被显示出来。


    3. 减少硬解析,节约大量的CPU资源

    使用预编译可以利用数据库的软解析降低资源消耗,所谓软解析,就是因为相同文本的SQL语句存在于library cache中,所以本次SQL语句的解析就可以去掉硬解析中的一个或多个步骤(主要为选择执行计划步骤),从而节省大量的资源的耗费。

    关于软解析和硬解析,可参见:http://blog.csdn.net/cyzero/article/details/8621171



    附:

    Statement,PreparedStatement 与 PreparedStatement + 批处理 的用法与比较

    java方面

    1.使用Statement对象

    2.预编译PreparedStatement

    3.使用PreparedStatement + 批处理

    为了区分出这三者之间的效率,下面的事例执行过程都是在数据库表t1中插入1万条记录,并记录出所需的时间(此时间与电脑硬件有关)


    1.使用Statement对象

    使用范围:当执行相似SQL(结构相同,具体值不同)语句的次数比较少

    优点:语法简单

    缺点:采用硬编码效率低,安全性较差。

    原理:硬编码,每次执行时相似SQL都会进行编译   

            

    事例执行过程:

       public void exec(Connection conn){

               try {

                         Long beginTime = System.currentTimeMillis();

                               conn.setAutoCommit(false);//设置手动提交

                        Statement st = conn.createStatement();

                                for(int i=0;i<10000;i++){

                           String sql="insert into t1(id) values ("+i+")";

                           st.executeUpdate(sql); 

                        }

                               Long endTime = System.currentTimeMillis();

                       System.out.println("Statement用时:"+(endTime-beginTime)/1000+"秒");//计算时间

                       st.close();

                      conn.close();

                    } catch (SQLException e) {               

                      e.printStackTrace();

              } 

       }

     

    执行时间:Statement用时:31秒

     

    2.预编译PreparedStatement

    使用范围:当执行相似sql语句的次数比较多(例如用户登陆,对表频繁操作..)语句一样,只是具体的值不一样,被称为动态SQL

    优点:语句只编译一次,减少编译次数。提高了安全性(阻止了SQL注入)

    缺点: 执行非相似SQL语句时,速度较慢。

    原理:相似SQL只编译一次,减少编译次数

    名词解释:

    SQL注入:select * from user where username="张三" and password="123" or 1=1;

    前面这条语句红色部分就是利用sql注入,使得这条词句使终都会返回一条记录,从而降低了安全性。

    事例执行过程:

          public void exec2(Connection conn){

             try {

                       Long beginTime = System.currentTimeMillis();

                        conn.setAutoCommit(false);//手动提交

                        PreparedStatement pst = conn.prepareStatement("insert into t1(id) values (?)");

                        for(int i=0;i<10000;i++){

                           pst.setInt(1, i);

                           pst.execute();   

                        }

                          conn.commit();

                          Long endTime = System.currentTimeMillis();

                          System.out.println("Pst用时:"+(endTime-beginTime)+"秒");//计算时间

                          pst.close();

                          conn.close();

              } catch (SQLException e) {                 

                         e.printStackTrace();

              }

        }

    执行时间:Pst用时:14秒

     

    3.使用PreparedStatement + 批处理

    使用范围:一次需要更新数据库表多条记录

    优点:减少和SQL引擎交互的次数,再次提高效率,相似语句只编译一次,减少编译次数。提高了安全性(阻止了SQL注入)

    缺点:

    原理:

    批处理: 减少和SQL引擎交互的次数,一次传递给SQL引擎多条SQL。

    名词解释:

    PL/SQL引擎:在oracle中执行pl/sql代码的引擎,在执行中发现标准的sql会交给sql引擎进行处理。

    SQL引擎:执行标准sql的引擎。

    事例执行过程:

    public void exec3(Connection conn){

                         try {

                          conn.setAutoCommit(false);

                          Long beginTime = System.currentTimeMillis();

                          PreparedStatement pst = conn.prepareStatement("insert into t1(id) values (?)");

                         

                          for(int i=1;i<=10000;i++){   

                           pst.setInt(1, i);

                           pst.addBatch();//加入批处理,进行打包

                           if(i%1000==0){//可以设置不同的大小;如50,100,500,1000等等

                            pst.executeBatch();

                            conn.commit();

                            pst.clearBatch();

                           }

                          }

                         pst.executeBatch();

                          Long endTime = System.currentTimeMillis();

                          System.out.println("pst+batch用时:"+(endTime-beginTime)+"毫秒");

                          pst.close();

                          conn.close();

                         } catch (SQLException e) {

                          // TODO Auto-generated catch block

                          e.printStackTrace();

                         }

     }

    执行时间:pst+batch用时:485毫秒


     

    数据库方面


    1 静态SQL

    使用范围:

    优点:每次执行相似sql都会进行编译。

    缺点:效率低,占用破费cpu资源,耗费SGA 资源。

    原理:(检查SQL 语句在SGA 中的共享池中是否存在, 如果不存在,则编译、解析后执行:硬解析,

         如果已存在,则取出编译结果执行:软解析)

    事例执行过程:

    DECLARE

     time1 NUMBER;

     time2 NUMBER;

    BEGIN

      time1 := dbms_utility.get_time;

      EXECUTE IMMEDIATE 'truncate table t_demo';

      FOR i IN 1..10000

      LOOP

         EXECUTE IMMEDIATE

           'insert into t_demo(u_id) values('||i||')'; 

      END LOOP;

      time2 := dbms_utility.get_time;

      dbms_output.put_line((time2-time1)/100||'秒');

    END

    执行时间:pst+batch用时:20.93秒


    2 动态SQL

    使用范围:

    优点:语句只编译一次,减少编译次数,提高效率。

    缺点:

    原理:减少编译次数(检查SQL 语句在SGA 中的共享池中是否存在, 如果不存在,则编译、解析后执行:硬解析,

         如果已存在,则取出编译结果执行:软解析)

    事例执行过程:

    DECLARE

     time1 NUMBER;

     time2 NUMBER;

    BEGIN

      time1 := dbms_utility.get_time;

      EXECUTE IMMEDIATE 'truncate table t_demo';

      FOR i IN 1..10000

      LOOP

         EXECUTE IMMEDIATE

           'insert into t1(u_id) values(:x)' USING i; 

      END LOOP;

      time2 := dbms_utility.get_time;

      dbms_output.put_line((time2-time1)/100||'秒');

    END;        

    执行时间:pst+batch用时:10.55秒

     

    3 利用forall进行批处理<相似java中的批处理>

    使用范围:当执行相似sql语句的次数比较多(例如用户登陆,对表频繁操作..)FORALL只能运行一条sql语句。

    优点:减少与SQL引擎转替次数,提交效率。

    缺点:循环时从开始到结束,一次性读完,不能取其中的某些循环进行操作。

    原理:使用ForAll语句可以让PL/SQL引擎一次将多条SQL转发给SQL引擎,从而减少转发次数,提高系统性能。

    事例执行过程:

    CREATE  OR  REPLACE  PROCEDURE  p_test

    AS

       --定义新的类型(不是定义变量!)

       TYPE id_table_type IS TABLE OF NUMBER(6) INDEX BY BINARY_INTEGER; 

       --用新的类型来定义变量

       id_table id_table_type;

       time1 NUMBER;

     time2 NUMBER;

    BEGIN

        FOR i IN 1..10000

        LOOP

           --往两个数组中存放数据

           id_table(i) := i;  

        END LOOP;

       --★:一次性向SQL引擎传递了多条数据而不是一条

        time1 := dbms_utility.get_time;

        FORALL i IN 1..id_table.COUNT

            INSERT INTO t_demo(u_id) VALUES(id_table(i));

      time2 := dbms_utility.get_time;

      dbms_output.put_line((time2-time1)*10||'毫秒');

    end p_test;

     

    call p_test();

    执行时间:pst+batch用时:170毫秒

     

    4 利用BULK COLLECT

    使用范围:处理数据量比较少时。

    优点:一次读取多行数据,提高效率。

    缺点: 需要较大的内存开销

    原理:将多个行引入一个或多个集合中。

    事例执行过程:

    DECLARE

       TYPE books_aat

     

          IS TABLE OF book%ROWTYPE

          INDEX BY PLS_INTEGER;

       book books_aat;

    BEGIN

       SELECT *

         BULK COLLECT INTO book

         FROM books

        WHERE title LIKE '%PL/SQL%';

       ...

    END;


    注:本文整理自网络



    展开全文
  •  在Java编程,绝大多数使用了使用了PreparedStatement连接MySQL的应用代码没有启用预编译,无论你是直接使用JDBC还是使用框架。 我所能见到的项目,几乎没有见过启用MySQL预编译功能的。网上更有文章说MySQL...

        在Java编程中,应用代码绝大多数使用了PreparedStatement,无论你是直接使用JDBC还是使用框架。
        在Java编程中,绝大多数使用了使用了PreparedStatement连接MySQL的应用代码没有启用预编译,无论你是直接使用JDBC还是使用框架。

        在我所能见到的项目中,几乎没有见过启用MySQL预编译功能的。网上更有文章说MySQL不支持预编译,实在是害人不浅。

        要想知道你的应用是否真正的使用了预编译,请执行:show global status like '%prepare%';看看曾经编译过几条,当前Prepared_stmt_count 是多少。大多数是0吧?

        这篇文章分以下几个方面:
        
        一.MySQL是支持预编译的

        打开MySQL日志功能,启动MySQL,然后 tail -f mysql.log.path(默认:/var/log/mysql/mysql.log).
        
        create table axman_test (ID int(4) auto_increment primary key, name varchar(20),age int(4));
        insert into axman_test (name,age) values ('axman',1000);

        prepare myPreparedStmt from 'select * from axman_test where name = ?';    
        set @name='axman';    
        execute myPreparedStmt using @name;

        控制台可以正确地输出:

    mysql> execute myPreparedStmt using @name;
    +----+-------+------+
    | ID | name  | age  |
    +----+-------+------+
    |  1 | axman | 1000 |
    +----+-------+------+
    1 row in set (0.00 sec)        

        而log文件中也忠实地记录如下:
        
    111028  9:25:06       51 Query    prepare myPreparedStmt from 'select * from axman_test where name = ?'
               51 Prepare    select * from axman_test where name = ?
               51 Query    set @name='axman'
    111028  9:25:08       51 Query    execute myPreparedStmt using @name
               51 Execute    select * from axman_test where name = 'axman'



        二.通过JDBC本身是可以预编译的,这个不用多说。相当于我们把控制台输入的命令直接通过JDBC语句来执行:

            Class.forName("org.gjt.mm.mysql.Driver");
            String url = "jdbc:mysql://localhost:3306/mysql";
            Connection conn = null;
            try {
                conn = DriverManager.getConnection(url, "root", "12345678");
                Statement stmt = conn.createStatement();
                /*以下忽略返回值处理*/
                stmt.executeUpdate("prepare mystmt from 'select * from axman_test where name = ?'");
                stmt.execute("set @name='axman'");
                stmt.executeQuery("execute mystmt using @name");
                stmt.close();
            } finally {
                if (conn != null) {
                    conn.close();
                }
            }

        看日志输出:

    111028  9:30:19       52 Connect    root@localhost on mysql
               52 Query    /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
               52 Query    /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
               52 Query    SHOW COLLATION
               52 Query    SET NAMES latin1
               52 Query    SET character_set_results = NULL
               52 Query    SET autocommit=1
               52 Query    SET sql_mode='STRICT_TRANS_TABLES'
               52 Query    prepare mystmt from 'select * from axman_test where name = ?'
               52 Prepare    select * from axman_test where name = ?
               52 Query    set @name='axman'
               52 Query    execute mystmt using @name
               52 Execute    select * from axman_test where name = 'axman'
               52 Quit    



        三.默认的PrearedStatement不能开启MySQL预编译功能:
          
           虽然第二节中我们通过JDBC手工指定MySQL进行预编译,但是PrearedStatement却并不自动帮我们做这件事。
            Class.forName("org.gjt.mm.mysql.Driver");
            String url = "jdbc:mysql://localhost:3306/mysql";
            Connection conn = null;
            try {
                conn = DriverManager.getConnection(url, "root", "12345678");
                PreparedStatement ps = conn.prepareStatement("select * from axman_test where name = ?");
                ps.setString(1, "axman' or 1==1");
                ResultSet rs = ps.executeQuery();
                if (rs.next()) {
                    System.out.println(rs.getString(1));
                }
                Thread.sleep(1000);
                rs.close();
                ps.clearParameters();
                ps.setString(1, "axman");
                rs = ps.executeQuery();
                if (rs.next()) {
                    System.out.println(rs.getString(1));
                }
                rs.close();
                ps.close();
            } finally {
                if (conn != null) {
                    conn.close();
                }
            }

        废话少说,直接看日志:
    111028  9:54:03       53 Connect    root@localhost on mysql
               53 Query    /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
               53 Query    /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
               53 Query    SHOW COLLATION
               53 Query    SET NAMES latin1
               53 Query    SET character_set_results = NULL
               53 Query    SET autocommit=1
               53 Query    SET sql_mode='STRICT_TRANS_TABLES'
               53 Query    select * from axman_test where name = 'axman\' or 1==1'
    111028  9:54:04       53 Query    select * from axman_test where name = 'axman'
               53 Quit    

        两条语句都是直接执行,而没有预编译。注意我的第一条语句select * from axman_test where name = 'axman\' or 1==1',下面还会说到它。
        接着我们改变一下jdbc.url的选项:
        String url = "jdbc:mysql://localhost:3306/mysql?cachePrepStmts=true&prepStmtCacheSize=25&prepStmtCacheSqlLimit=256";
        执行上面的代码还是没有开启Mysql的预编译。


        四.只有使用了useServerPrepStmts=true才能开启Mysql的预编译。

        上面的代码其它不变,只修改String url = "jdbc:mysql://localhost:3306/mysql?useServerPrepStmts=true";
        查看日志:
        
    111028 10:04:52       54 Connect    root@localhost on mysql
               54 Query    /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
               54 Query    /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
               54 Query    SHOW COLLATION
               54 Query    SET NAMES latin1
               54 Query    SET character_set_results = NULL
               54 Query    SET autocommit=1
               54 Query    SET sql_mode='STRICT_TRANS_TABLES'
               54 Prepare    select * from axman_test where name = ?
               54 Execute    select * from axman_test where name = 'axman\' or 1==1'
    111028 10:04:53       54 Execute    select * from axman_test where name = 'axman'
               54 Close stmt    
               54 Quit    

        如果useServerPrepStmts=true,ConneciontImpl在prepareStatement时会产生一个ServerPreparedStatement.在这个ServerPreparedStatement对象构造时首先会把当前SQL语句发送给MySQL进行预编译,然后将返回的结果缓存起来,其中包含预编译的名称(我们可以看成是当前SQL语句编译后的函数名),签名(参数列表),然后执行的时候就会直接把参数传给这个函数请求MySQL执行这个函数。否则返回的是客户端预编译语句,它仅做参数化工作,见第五节。
        ServerPreparedStatement在请求预编译和执行预编译后的SQL 函数时,虽然和我们上面手工预编译工作相同,但它与MySQL交互使用的是压缩格式,如prepare指令码是22,这样可以减少交互时传输的数据量。

        
        注意上面的代码中,两次执行使用的是同一个PreparedStatement句柄.如果使用个不同的PreparedStatement句柄,把代码改成:
            Class.forName("org.gjt.mm.mysql.Driver");
            String url = "jdbc:mysql://localhost:3306/mysql?useServerPrepStmts=true";
            Connection conn = null;
            try {
                conn = DriverManager.getConnection(url, "root", "12345678");
                PreparedStatement ps = conn.prepareStatement("select * from axman_test where name = ?");
                ps.setString(1, "axman' or 1==1");
                ResultSet rs = ps.executeQuery();
                if (rs.next()) {
                    System.out.println(rs.getString(1));
                }
                Thread.sleep(1000);
                rs.close();
                ps.close();
                ps = conn.prepareStatement("select * from axman_test where name = ?");
                ps.setString(1, "axman");
                rs = ps.executeQuery();
                if (rs.next()) {
                    System.out.println(rs.getString(1));
                }
                rs.close();
                ps.close();
            } finally {
                if (conn != null) {
                    conn.close();
                }
            }    

        再看日志输出:
         Connect    root@localhost on mysql
               55 Query    /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
               55 Query    /* @MYSQL_CJ_FULL_PROD_NAME@ ( Revision: @MYSQL_CJ_REVISION@ ) */SELECT @@session.auto_increment_increment
               55 Query    SHOW COLLATION
               55 Query    SET NAMES latin1
               55 Query    SET character_set_results = NULL
               55 Query    SET autocommit=1
               55 Query    SET sql_mode='STRICT_TRANS_TABLES'
               55 Prepare    select * from axman_test where name = ?
               55 Execute    select * from axman_test where name = 'axman\' or 1==1'
    111028 10:10:24       55 Close stmt    
               55 Prepare    select * from axman_test where name = ?
               55 Execute    select * from axman_test where name = 'axman'
               55 Close stmt    
               55 Quit    
               55 Quit
        同一个SQL语句发生了两次预编译。这不是我们想要的效果,要想对同一SQL语句多次执行不是每次都预编译,就要使用cachePrepStmts=true,这个选项可以让JVM端缓存每个SQL语句的预编译结果,说白了就是以SQL语句为key, 将预编译结果缓存起来,下次遇到相同的SQL语句时作为key去get一下看看有没有这个SQL语句的预编译结果,有就直接合出来用。我们还是以事实来说明:
        上面的代码只修改String url = "jdbc:mysql://localhost:3306/mysql?useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSize=25&prepStmtCacheSqlLimit=256";
    这行代码中有其它参数自己去读文档,我不多啰嗦,执行的结果:
    111028 10:27:23       58 Connect    root@localhost on mysql
               58 Query    /* mysql-connector-java-5.1.18 ( Revision: tonci.grgin@oracle.com-20110930151701-jfj14ddfq48ifkfq ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'init_connect'
               58 Query    /* mysql-connector-java-5.1.18 ( Revision: tonci.grgin@oracle.com-20110930151701-jfj14ddfq48ifkfq ) */SELECT @@session.auto_increment_increment
               58 Query    SHOW COLLATION
               58 Query    SET NAMES latin1
               58 Query    SET character_set_results = NULL
               58 Query    SET autocommit=1
               58 Query    SET sql_mode='STRICT_TRANS_TABLES'
               58 Prepare    select * from axman_test where name = ?
               58 Execute    select * from axman_test where name = 'axman\' or 1==1'
    111028 10:27:24       58 Execute    select * from axman_test where name = 'axman'
               58 Quit    

        注意仅发生一次预编译,尽管代码本身在第一次执行后关闭了ps.close();但因为使用了cachePrepStmts=true,底层并没有真实关闭。

        千万注意,同一条SQL语句尽量在一个全局的地方定义,然后在不同地方引用,这样做一是为了DBA方便地对SQL做统一检查和优化,就象IBatis把SQL语句定义在XML文件中一样。二是同一语句不同写法,即使空格不同,大小写不同也会重新预编译,因为JVM端缓存是直接以SQL本身为key而不会对SQL格式化以后再做为key。

        我们来看下面的输出:

               35 Prepare    select * from axman_test where name = ?
               35 Execute    select * from axman_test where name = 'axman\' or 1==1'
    111029  9:54:31       35 Prepare    select * FROM axman_test where name = ?
               35 Execute    select * FROM axman_test where name = 'axman'

        第一条语句和第二条语句的差别是FROM在第二条语句中被大写了,这样还是发生了两次预编译。

               37 Prepare    select * from axman_test where name = ?
               37 Execute    select * from axman_test where name = 'axman\' or 1==1'
    111029  9:59:00       37 Prepare    select * from    axman_test where name = ?
               37 Execute    select * from    axman_test where name = 'axman'
         这里两条语句只是第二条的from后面多了个空格,因为你现在看到是HTML格式,如果不加转义符,两个空格也显示一个空格,所以你能可看不到区别,但你可以在自己的机器上试一下。

        五.即使没有开启MySQL的预编译,坚持使用PreparedStatement仍然非常必要。
        在第三节的最后我说到"注意我的第一条语句select * from axman_test where name = 'axman\' or 1==1',下面还会说到它。",现在我们回过头来看,即使没有开启MySQL端的预编译,我们仍然要坚持使用PreparedStatement,因为JVM端对PreparedStatement的SQL语句进行了参数化,即用占位符替换参数,以后任何内容输入都是字符串或其它类型的值,而不会和原始的SQL语句拚接产生SQL注入,对字符串中的任何字符都会做检查,如果可能是SQL语句使用的标识符,会进行转义。然后发送一个合法的安全的SQL语句给数据库执行。


        



    展开全文
  • 1. 运行环境切换到某个目录下; 2. 该目录建立文件夹,比如 object ; 3. object文件夹下新建Java代码文件,例如MyJava.java代码开头添加package object;...5. cmd运行环境运行命令: java o...

    1. 运行环境切换到某个目录下;

    2. 在该目录中建立文件夹,比如 object ;

    3. 在object文件夹下新建Java代码文件,例如MyJava.java,在代码开头添加 package object; 语句;
    4.在cmd运行环境运行命令: javac ./object/MyJava.java  ,得到MyJava.class 文件

    5. 在cmd运行环境中运行命令: java object.MyJava  即可。
     

    展开全文
  • JDBC:预编译语句和批量更新-

    千次阅读 2009-07-03 08:58:00
    进一步提高JDBC应用程序的性能 (四) http://www.daima.com.cn/Info/55/Info15348/ bootcool@263.net 四:使用预编译语句和批量更新 首先我们得大致的了解数据库是怎么处理各种数据库操作语句的。当数据库接收到一个...

    进一步提高JDBC应用程序的性能 (四)

    http://www.daima.com.cn/Info/55/Info15348/

    bootcool@263.net

    四:使用预编译语句和批量更新

    首先我们得大致的了解数据库是怎么处理各种数据库操作语句的。当数据库接收到一个语句时,数据库引擎首先解析该语句,然后分析是否有语法,语义错误。如果没有错误,数据库将计算出如何高效的执行该语句。一旦得出执行策略,就由数据库引擎执行该语句,最后把执行结果反馈给用户。虽然数据库厂商对各自的数据库做了最大的优化,但是可以想象这的确是一个开销很大的工作。

    于是,我们考虑如何使我们的数据库操作变得更高效呢?如果一条语句执行一次后,数据库就记录下该语句的执行策略,那么以后执行相同语句时,就可以省去上面的种种麻烦了。

    Java里提供了这样的接口――PreparedStatement.。通过预编译PreparedStatement 对象, 我们能够很容易的提高语句执行效率。同时,需要指出的是Java里还有另一个实现数据库操作的接口――Statement,但是当语句格式固定时我们更倾向于使用PreparedStatement,只有当语句格式无法预见时,我们才考虑采用Statement。

    以下是执行1000次语句结构相同的Insert,Update和Select语句的测试结果:



    接口类型



    Insert语句



    Update语句



    Select语句

    第一次测试耗时



    第二次测试耗时



    第一次测试耗时



    第二次测试耗时



    第一次测试耗时



    第二次测试耗时

    Statement



    2360 ms



    2190 ms



    3790 ms



    3460 ms



    3570 ms



    2530 ms

    PreparedStatement



    1270 ms



    1040 ms



    2600 ms



    2410 ms



    440 ms



    380 ms

                                         (表8)

    分析: PreparedStatement 的效率明显比Statement要高出很多。另外,对于查询语句我们还得深入地看看JDBC是如何实现的。JDBC执行一次查询后,将返回一个 ResultSet(结果集)。为了建立这个结果集,JDBC将对数据库访问两次。第一次要求数据库对结果集中的各列进行说明,第二次告诉数据库,当程序需要获取数据时应如何安置这些数据。由此我们能够算出执行一次或多次查询,JDBC需要访问数据库的次数。

    访问数据库次数 = 结果集中的列数 * 语句执行的次数 * 2



    如果同样执行100次相同查询,结果集中的列数也相同时,假设为20列:

    使用Statement:  访问数据库次数 = 20 * 100 * 2 = 4000  

    使用Prepared Statement:  访问数据库次数 = 20 * 1* 2 = 400





    以下是相关的测试结果:

    方式



    Select语句

    执行100次语句结构相同的查询耗时



    执行1000次语句结构相同的查询耗时

    第一次测试



    第二次测试



    第一次测试



    第二次测试

    方式1



    1100 ms



    330 ms



    3510 ms



    3020 ms

    方式2



    110 ms



    50 ms



    440 ms



    380 ms

                                       (表9)

    分析:测试结果说明,如果不正确的使用了PreparedStatement接口,那么其执行效率和使用Statement没有什么差别,而PreparedStatement接口的优势也不会得到充分发挥。

    最后我们还得补充一点,当我们需要成批插入或者更新记录时。我们考虑采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。如果我们再配合使用PreparedStatement接口,将进一步提高程序的性能。我们同样给出一个小程序予以说明。

    import java.sql.*;

    public class JDBCTEST3 {

      public static void main(String[] args) {
        try {String[] values = {"BeiJing","KunMing"};
              int[] results;

             Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
             Connection con = DriverManager.getConnection("jdbc:odbc:test");
               Statement st = con.createStatement ();
              for (int i = 0; i < values.length; i++){
                 //把一个SQL命令加入命令列表
                 st.addBatch ("INSERT INTO CITY VALUES('" + values[i] + "')");
              }
              //执行批量更新
              st.executeBatch ();

              PreparedStatement pst = con.prepareStatement("INSERT INTO CITY"
                                                           +"VALUES (?)");
      for (int i = 0; i < values.length; i++) {
                pst.setString(1, values[i]);
                //把一个SQL命令加入命令列表
    pst.addBatch();
              }
              //执行批量更新
    pst.executeBatch();
            
              st.close();
              pst.close();
              con.close();
       }
       catch(Exception ex){
        ex.printStackTrace();
       }
      }
    }

      我们还要注意一点,如果使用PreparedStatement接口的方法不当,则不能达到提高执行效率的目的。我们用一个简单的测试程序说明:

    import java.sql.*;
    public class JDBCTEST2 {
    private static Connection con = null;
    private static String dbUrl = null;

    public JDBCTEST2(){
    }
    public static void main(String args[]){
              try{
                   dbUrl = "jdbc:odbc:test";
                   Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                     preparedStatementInsertTest_1();
                     System.out.println("===================");
                     preparedStatementInsertTest_2();
              }
              catch(Exception ex){
               ex.printStackTrace();
              }
      }

    //方法1以不恰当的方式使用了PreparedStatement接口
    public static void  preparedStatementInsertTest_1(){
          try{
                con = DriverManager.getConnection(dbUrl);
                  PreparedStatement pst = null;
                  long start = System.currentTimeMillis();
                
    //执行1000次语句结构相同的查询
    for(int i=0;i<1000;i++){
                     pst = con.prepareStatement("select * from s where s1 = " + i);
                     pst.executeQuery();
                     pst.close();
                  }
                  System.out.println("Methord_1 Execute Ellapse:"
                                    +(System.currentTimeMillis()-start)
                                    +"ms");
                  con.close();
              }
            catch(Exception ex){
              ex.printStackTrace();
            }   
    }

    //方法2以正确的方式使用了PreparedStatement接口
    public static void  preparedStatementInsertTest_2(){
          try{
                con = DriverManager.getConnection(dbUrl);
                 long start = System.currentTimeMillis();
                    PreparedStatement pst = null;
    pst = con.prepareStatement("select * from s where s1 = ?");
                  
    //执行1000次语句结构相同的查询
    for(int i=0;i<1000;i++){
                      pst.setInt(1,i);
                      pst.executeQuery();
                  }
                  System.out.println("Methord_2 Execute Ellapse:"
                                    +(System.currentTimeMillis()-start)
                                    +"ms");
                  pst.close();
                  con.close();
              }
            catch(Exception ex){
              ex.printStackTrace();
            }   
      }
    }

    展开全文
  • 预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止sql注入。 https://www.cnblogs.com/Prozhu/p/5541916.html 转载于:https://www.cnblogs.com...
  • 在上篇文章了解到了Java前端编译 JIT编译 AOT编译各有什么优点和缺点,下面详细了解Java前端编译Java源代码编译成Class文件的过程;我们从官方JDK提供的前端编译器javac入手,用javac编译一些测试程序,调试跟踪...
  • 何谓预编译

    千次阅读 2013-11-05 17:38:11
    何谓预编译
  • 文章目录预编译编译的好处MySQL界面执行预编译JDBC驱动执行预编译使用Statement...什么是预编译功能呢?它有什么好处呢? 当客户发送一条SQL语句给服务器后,服务器总是需要校验SQL语句的语法格式是否正确,然后把SQ
  • Java编译(二)Java前端编译:Java源代码编译成Class文件的过程 在上篇文章《Java三种编译方式:前端编译 JIT编译 AOT编译》了解到了它们各有什么优点和缺点,以及前端编译+JIT编译方式的运作过程。 下面我们...
  • 深度探究Java finally 语句

    千次阅读 2016-11-03 12:57:21
    乍看这个题目,是不是有人会问,这个谁不知道啊,大凡熟悉 Java 编程的人都知道 finally 语句块的作用和用法。有什么可深度辨析的呢?事实并非如此,我发现即使写了很多年 Java 程序的人,也不一定能够透彻的理解 ...
  • java中语句的分类

    千次阅读 2016-06-28 21:28:10
    在Java中,一个语句是指一个过程的完整描述,就如流程图的一个环节。 总的来说,java中语句的分类有六类:①方法调用语句②表达式语句③复合语句④空语句⑤控制语句⑥package语句和import语句。 ①方法调用语句:...
  • 现在拿到一份虚机环境然后想把环境的war还原成代码,反编译嘛,我觉得大部分人都接触过,看看源码啊啥的。先简单说说本次我用到的反编译工具。首先说推荐的好用的工具JD,基本语法都正常,而且保留了编译后的那些...
  • Java中switch case 语句总结

    千次阅读 2017-01-23 11:40:02
    java中使用switch-case的用法及注意事项超全总结 http://m.blog.csdn.net/blog/tianyazaiheruan/8988420 今天陈红军老师用到switch的时候,这种设计到最基本的内容,可能忘记它的一些基本语法,出现了...
  • 1、Statement为每一条Sql语句生成执行计划, 如果要执行两条sql语句 select colume from table where colume=1; select colume from table where colume=2; 会生成两个执行计划,一千个查询就生成一千个执行...
  • Java 里面的标签语句

    千次阅读 2018-07-31 16:29:41
    在Java中,虽然goto仍旧是一个保留字,但是实际上在Java语言,并未使用到它。 所以Java里面没有goto。 在Java里面,标签语句唯一起作用的地方是迭代语句之前(标签必须刚好位于迭代语句之前,中间有任...
  • JAVA源文件动态编译

    千次阅读 2017-12-06 20:18:43
    一般的Java应用开发过程,开发人员使用Java的方式比较简单。打开惯用的IDE,编写Java源代码,再利用IDE提供的功能直接运行Java 程序就可以了。这种开发模式背后的过程是:开发人员编写的是Java源代码文件(.java...
  • 放进数据库跑一跑就明白了
  • 之前对package等语句的了解并不是...下编译运行带有包语句的程序,更有利于对 包的相关语句的理解噢!~ 1. package语句 通过关键字package声明包语句。 package 语句作为Java源文件的第一条语句,指明该源文件定义的
  • java编译原理

    千次阅读 2018-03-27 16:47:31
    4.Java编译原理1.javac是什么?(1)javac是一种编译器,能够将一种语言规范转换成另一种用语言规范,通常编译器是将便于人们理解的语言规范成机器容易理解的语言规范。(2)javac的任务就是将java源代码语言转换成jvm...
  • 参考如下两段代码: public class Test{ public static void main(String [] lkl){ boolean flag = false;...总结:Java的条件判断括号内表达式最终返回的结果应该为boolean类型,否则会编译出错
  • 如何保护Java程序 防止Java编译

    千次阅读 2016-06-15 17:00:47
    本节介绍了几种常用的方法,用于保护Java字节码不被反编译。通常,这些方法不能够绝对防止程序被反编译,而是加大反编译的难度而已,因为这些方法都有自己的使用环境和弱点。  隔离Java程序  最简单的方法...
  • 1、选项 A args[0] = “MyTest a b c” B args[0] = “MyTest” C args[0] = “a” ...Javac Test.javaJava源文件编译为字节码文件Test.class Java Test执行字节码文件 public class Test{ ...
  • Java中import语句的学习

    千次阅读 2007-10-14 11:15:00
    现在,学习import语句。假设有两个存放D:/src下的源文件Cited.java和Citing.java。Cited.java文件内的程序: package classes.shang;public class Cited{ public void print() { System.out.println("Hell
  • MyBatis预编译机制详解

    千次阅读 热门讨论 2019-04-03 11:52:58
    MyBatis预编译机制详解 一. "#{}“和”${}"的区别 "#{}"是将传入的值按照字符串的形式进行处理,如下面这条语句: select user_id,user_name from t_user where user_id = #{user_id} MyBaits会首先对其进行...
  • sql预编译和注入

    千次阅读 2014-11-03 14:51:30
    而熟悉JDBC编程的大侠们都会选择使用PreparedStatement对象,主要因为使用预编译对象PreparedStatement时,有以下几个优点: 首先是效率性  PreparedStatement可以尽可能的提高访问数据库的性能,我们
  • Hollis原创|深入分析Java编译原理

    千次阅读 多人点赞 2019-05-14 09:46:07
    GitHub 2.6k Star 的Java工程师成神之路 ,不来了解一下吗?...Java代码的编译与反编译,有过关于Java语言的编译和反编译的介绍。我们可以通过javac命令将Java程序的源代码编译Java字节码,...
  • 我们把C代码编译java字节码,这样我们的C语言便具备了可跨品台属性。通过把C语言编译java字节码,我们不但能够继续学习和掌握编译原理相关的算法技术,于此同时,还能深入理解java虚拟机的基本原理,此乃...
  • java中package语句的使用问题

    千次阅读 2013-07-13 15:47:38
      用Ultra Edit编程有包和没有包是一样的写。...只是在编译上有一点区别: package com.util; public class myJava{ } 如果是有包的程序:javac -d . myJava.java 就会当前目录下生成一个com的文件夹,co
  • Javac编译器工作原理(2)Java语言的编译过程 明白了高级语言到低级语言的编译原理,我们来了解一下... javac(发音为“java-see”)是Oracle Corporation的Java Development Kit(JDK)包含的主要Java编译器。 Ma

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 122,957
精华内容 49,182
关键字:

在java中预编译语句正确的是

java 订阅