JDBC:深入理解PreparedStatement和Statement

轉(zhuǎn)載于JDBC:深入理解PreparedStatement和Statement
主要優(yōu)化排版與閱讀

前言
最近聽(tīng)一個(gè)老師講了公開(kāi)課锄奢,在其中講到了 PreparedStatement 的執(zhí)行原理和 Statement 的區(qū)別。

當(dāng)時(shí)聽(tīng)公開(kāi)課老師講的時(shí)候感覺(jué)以前就只知道 PreparedStatement 是 “預(yù)編譯類”痒蓬,能夠?qū)?sql 語(yǔ)句進(jìn)行預(yù)編譯晨继,預(yù)編譯后能夠提高數(shù)據(jù)庫(kù) sql 語(yǔ)句執(zhí)行效率蜂大。

但是,聽(tīng)了那個(gè)老師講后我就突然很想問(wèn)自己源譬,預(yù)編譯千贯??是誰(shuí)對(duì) sql 語(yǔ)句的預(yù)編譯晌坤?逢艘?是數(shù)據(jù)庫(kù)?還是 PreparedStatement 對(duì)象骤菠?它改?到底什么是預(yù)編譯?商乎?為什么能夠提高效率央拖??為什么在數(shù)據(jù)庫(kù)操作時(shí)能夠防止 sql 注入攻擊鹉戚?鲜戒?這就引起了我對(duì) Preparedstatement 的疑惑。

公開(kāi)課老師講的時(shí)候說(shuō):”PreparedStatement 會(huì)對(duì) sql 文進(jìn)行預(yù)編譯抹凳,預(yù)編譯后遏餐,會(huì)存儲(chǔ)在 PreparedStatement 對(duì)象中,等下次再執(zhí)行這個(gè) PreparedStatement 對(duì)象時(shí)赢底,會(huì)提高很多效率”失都。這句話我聽(tīng)了后更疑惑了,預(yù)編譯是什么我不知道就算了幸冻,竟然還說(shuō):對(duì) sql 預(yù)編譯后會(huì)存儲(chǔ)在 PreparedStatement 對(duì)象中粹庞??我就想問(wèn)問(wèn) sql 預(yù)編譯后是什么洽损?庞溜?什么被存儲(chǔ)在 PreparedStatement 對(duì)象中?碑定?

更讓人感覺(jué)疑惑的是 Statement流码。對(duì)就是 Statement,公開(kāi)課老師說(shuō):“同一條 sql 語(yǔ)句(字符串都是相同的)在 Statement 對(duì)象中多次執(zhí)行時(shí)延刘,Statement 只會(huì)對(duì)當(dāng)前 sql 文編譯一次旅掂,編譯后存儲(chǔ)在 Statement 中,在之后的執(zhí)行過(guò)程中访娶,都不會(huì)進(jìn)行編譯而是直接運(yùn)行 sql 語(yǔ)句”商虐。什么?崖疤?我沒(méi)聽(tīng)錯(cuò)吧秘车?Statement 還有編譯?劫哼?等等等等叮趴。。权烧。眯亦。我當(dāng)時(shí)真的是聽(tīng)的懷疑人生伤溉。

PreparedStatement
在說(shuō) PreparedStatement 之前,我們來(lái)看看什么是預(yù)編譯妻率。其實(shí)預(yù)編譯是 MySQL 數(shù)據(jù)庫(kù)本身都支持的乱顾。但是 MySQL Server 4.1 之前的版本是不支持預(yù)編譯的。(具體是否包括 4.1 還得讀者們親自試驗(yàn))

在這里宫静,筆者用的是 MySQL5.6 綠色版走净。

MySQL 中的預(yù)編譯功能是這樣的
預(yù)編譯的好處:

大家平時(shí)都使用過(guò) JDBC 中的 PreparedStatement 接口,它有預(yù)編譯功能孤里。什么是預(yù)編譯功能呢伏伯?它有什么好處呢?
當(dāng)客戶發(fā)送一條 SQL 語(yǔ)句給服務(wù)器后捌袜,服務(wù)器總是需要校驗(yàn) SQL 語(yǔ)句的語(yǔ)法格式是否正確说搅,然后把 SQL 語(yǔ)句編譯成可執(zhí)行的函數(shù),最后才是執(zhí)行 SQL 語(yǔ)句虏等。其中校驗(yàn)語(yǔ)法蜓堕,和編譯所花的時(shí)間可能比執(zhí)行 SQL 語(yǔ)句花的時(shí)間還要多。
注意:可執(zhí)行函數(shù)存儲(chǔ)在 MySQL 服務(wù)器中博其,并且當(dāng)前連接斷開(kāi)后套才,MySQL 服務(wù)器會(huì)清除已經(jīng)存儲(chǔ)的可執(zhí)行函數(shù)。
如果我們需要執(zhí)行多次 insert 語(yǔ)句慕淡,但只是每次插入的值不同背伴,MySQL 服務(wù)器也是需要每次都去校驗(yàn) SQL 語(yǔ)句的語(yǔ)法格式,以及編譯峰髓,這就浪費(fèi)了太多的時(shí)間傻寂。如果使用預(yù)編譯功能,那么只對(duì) SQL 語(yǔ)句進(jìn)行一次語(yǔ)法校驗(yàn)和編譯携兵,所以效率要高疾掰。

MySQL 執(zhí)行預(yù)編譯
MySQL 執(zhí)行預(yù)編譯分為如三步:

  1. 執(zhí)行預(yù)編譯語(yǔ)句,例如:prepare showUsersByLikeName from 'select * from user where username like ?';
  2. 設(shè)置變量徐紧,例如:set @username='% 小明 %';
  3. 執(zhí)行語(yǔ)句静檬,例如:execute showUsersByLikeName using @username;

如果需要再次執(zhí)行 myfun,那么就不再需要第一步并级,即不需要再編譯語(yǔ)句了:

  1. 設(shè)置變量拂檩,例如:set @username='% 小宋 %';
  2. 執(zhí)行語(yǔ)句,例如:execute showUsersByLikeName using @username;

如果你看 MySQL 日志記錄嘲碧,你就會(huì)看到:

配置 MySQL 日志記錄

路徑地址可以自己修改稻励。

log-output=FILE 
general-log=1 
general_log_file="E:\mysql.log" 
slow-query-log=1 
slow_query_log_file="E:\mysql_slow.log" 
long_query_time=2 

配置之后就重啟 MySQL 服務(wù)器:

在 cmd 管理員界面執(zhí)行以下操作。

net stop mysql 
net start mysql 

使用 PreparedStatement 執(zhí)行 sql 查詢
JDBC MySQL 驅(qū)動(dòng) 5.0.5 以后的版本默認(rèn) PreparedStatement 是關(guān)閉預(yù)編譯功能的愈涩,所以需要我們手動(dòng)開(kāi)啟望抽。而之前的 JDBC MySQL 驅(qū)動(dòng)版本默認(rèn)是開(kāi)啟預(yù)編譯功能的加矛。
MySQL 數(shù)據(jù)庫(kù)服務(wù)器的預(yù)編譯功能在 4.1 之后才支持預(yù)編譯功能的。如果數(shù)據(jù)庫(kù)服務(wù)器不支持預(yù)編譯功能時(shí)煤篙,并且使用 PreparedStatement 開(kāi)啟預(yù)編譯功能是會(huì)拋出異常的斟览。這點(diǎn)非常重要。筆者用的是 mysql-connector-jar-5.1.13 版本的 JDBC 驅(qū)動(dòng)舰蟆。
在我們以前寫(xiě)項(xiàng)目的時(shí)候,貌似都沒(méi)有注意是否開(kāi)啟 PreparedStatement 的預(yù)編譯功能狸棍,以為它一直都是在使用的身害,現(xiàn)在看看不開(kāi)啟 PreparedStatement 的預(yù)編譯,查看 MySQL 的日志輸出到底是怎么樣的草戈。

@Test
public void showUser(){
    //數(shù)據(jù)庫(kù)連接
    Connection connection = null;
    //預(yù)編譯的Statement塌鸯,使用預(yù)編譯的Statement提高數(shù)據(jù)庫(kù)性能
    PreparedStatement preparedStatement = null;
    //結(jié)果 集
    ResultSet resultSet = null;

    try {
        //加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)
        Class.forName("com.mysql.jdbc.Driver");

        //通過(guò)驅(qū)動(dòng)管理類獲取數(shù)據(jù)庫(kù)鏈接
        connection =  DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "");
        //定義sql語(yǔ)句 ?表示占位符
        String sql = "select * from user where username = ?";
        //獲取預(yù)處理statement
        preparedStatement = connection.prepareStatement(sql);

        //設(shè)置參數(shù),第一個(gè)參數(shù)為sql語(yǔ)句中參數(shù)的序號(hào)(從1開(kāi)始)唐片,第二個(gè)參數(shù)為設(shè)置的參數(shù)值
        preparedStatement.setString(1, "王五");
        //向數(shù)據(jù)庫(kù)發(fā)出sql執(zhí)行查詢丙猬,查詢出結(jié)果集
        resultSet =  preparedStatement.executeQuery();

        preparedStatement.setString(1, "張三");
        resultSet =  preparedStatement.executeQuery();
        //遍歷查詢結(jié)果集
        while(resultSet.next()){
            System.out.println(resultSet.getString("id")+"  "+resultSet.getString("username"));
        }
        resultSet.close();
        preparedStatement.close();

        System.out.println("#############################");

    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        //釋放資源
        if(resultSet!=null){
            try {
                resultSet.close();
            } catch (SQLException e) {
            }
        }
        if(preparedStatement!=null){
            try {
                preparedStatement.close();
            } catch (SQLException e) {
            }
        }
        if(connection!=null){
            try {
                connection.close();
            } catch (SQLException e) {
            }
        }

    }
}


這是輸出日志:

Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */
SELECT @@session.auto_increment_increment
Query SHOW COLLATION
Query SET NAMES utf8mb4
Query SET character_set_results = NULL
Query SET autocommit=1
Query select * from user where username = '王五'
Query select * from user where username = '張三'
Quit  

可以看到,在日志中并沒(méi)有看到 "prepare" 命令來(lái)預(yù)編譯 "select * from user where username = ?" 這個(gè) sql 模板费韭。所以我們一般用的 PreparedStatement 并沒(méi)有用到預(yù)編譯功能的茧球,只是用到了防止 sql 注入攻擊的功能。防止 sql 注入攻擊的實(shí)現(xiàn)是在 PreparedStatement 中實(shí)現(xiàn)的星持,和服務(wù)器無(wú)關(guān)抢埋。筆者在源碼中看到,PreparedStatement 對(duì)敏感字符已經(jīng)轉(zhuǎn)義過(guò)了督暂。

在 PreparedStatement 中開(kāi)啟預(yù)編譯功能
設(shè)置 MySQL 連接 URL 參數(shù):useServerPrepStmts=true揪垄,如下所示。

jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true

這樣才能保證 mysql 驅(qū)動(dòng)會(huì)先把 SQL 語(yǔ)句發(fā)送給服務(wù)器進(jìn)行預(yù)編譯逻翁,然后在執(zhí)行 executeQuery() 時(shí)只是把參數(shù)發(fā)送給服務(wù)器饥努。
再次執(zhí)行上面的程序看下 MySQL 日志輸出:

Query SHOW WARNINGS
Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */
SELECT @@session.auto_increment_increment
Query SHOW COLLATION
Query SET NAMES utf8mb4
Query SET character_set_results = NULL
Query SET autocommit=1
Prepare   select * from user where username = ?
Execute   select * from user where username = '王五'
Execute   select * from user where username = '張三'
Close stmt    
Quit 

很明顯已經(jīng)進(jìn)行了預(yù)編譯,Prepare select * from user where username = ?八回,這一句就是對(duì) sql 語(yǔ)句模板進(jìn)行預(yù)編譯的日志酷愧。好的非常 Nice。

注意:

我們?cè)O(shè)置的是 MySQL 連接參數(shù)缠诅,目的是告訴 MySQL JDBC 的 PreparedStatement 使用預(yù)編譯功能(5.0.5 之后的 JDBC 驅(qū)動(dòng)版本需要手動(dòng)開(kāi)啟伟墙,而之前的默認(rèn)是開(kāi)啟的),不管我們是否使用預(yù)編譯功能滴铅,MySQL Server4.1 版本以后都是支持預(yù)編譯功能的戳葵。

cachePrepStmts 參數(shù)
當(dāng)使用不同的 PreparedStatement 對(duì)象來(lái)執(zhí)行相同的 SQL 語(yǔ)句時(shí),還是會(huì)出現(xiàn)編譯兩次的現(xiàn)象汉匙,這是因?yàn)轵?qū)動(dòng)沒(méi)有緩存編譯后的函數(shù) key拱烁,導(dǎo)致二次編譯生蚁。如果希望緩存編譯后函數(shù)的 key,那么就要設(shè)置 cachePrepStmts 參數(shù)為 true戏自。例如:

jdbc:mysql://localhost:3306/mybatis?useServerPrepStmts=true&cachePrepStmts=true

程序代碼:

@Test
public void showUser(){
    //數(shù)據(jù)庫(kù)連接
    Connection connection = null;
    //預(yù)編譯的Statement邦投,使用預(yù)編譯的Statement提高數(shù)據(jù)庫(kù)性能
    PreparedStatement preparedStatement = null;
    //結(jié)果 集
    ResultSet resultSet = null;

    try {
        //加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)
        Class.forName("com.mysql.jdbc.Driver");

        //通過(guò)驅(qū)動(dòng)管理類獲取數(shù)據(jù)庫(kù)鏈接
        connection =  DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true", "root", "");

        preparedStatement=connection.prepareStatement("select * from user where username like ?");
        preparedStatement.setString(1, "%小明%");
        resultSet =  preparedStatement.executeQuery();
        //遍歷查詢結(jié)果集
        while(resultSet.next()){
            System.out.println(resultSet.getString("id")+"  "+resultSet.getString("username"));
        }
        //注意這里必須要關(guān)閉當(dāng)前PreparedStatement對(duì)象流,否則下次再次創(chuàng)建PreparedStatement對(duì)象的時(shí)候還是會(huì)再次預(yù)編譯sql模板擅笔,使用PreparedStatement對(duì)象后不關(guān)閉當(dāng)前PreparedStatement對(duì)象流是不會(huì)緩存預(yù)編譯后的函數(shù)key的
        resultSet.close();
        preparedStatement.close();

        preparedStatement=connection.prepareStatement("select * from user where username like ?");
        preparedStatement.setString(1, "%三%");
        resultSet =  preparedStatement.executeQuery();
        //遍歷查詢結(jié)果集
        while(resultSet.next()){
            System.out.println(resultSet.getString("id")+"  "+resultSet.getString("username"));
        }

        resultSet.close();
        preparedStatement.close();

    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        //釋放資源
        if(resultSet!=null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        if(preparedStatement!=null){
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        if(connection!=null){
            try {
                connection.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }
}


日志輸出:

Query SHOW WARNINGS
Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */
SELECT @@session.auto_increment_increment
Query SHOW COLLATION
Query SET NAMES utf8mb4
Query SET character_set_results = NULL
Query SET autocommit=1
Prepare   select * from user where username like ?
Execute   select * from user where username like '%小明%'
Execute   select * from user where username like '%三%'
Quit 

注意:每次使用 PreparedStatement 對(duì)象后都要關(guān)閉該 PreparedStatement 對(duì)象流志衣,否則預(yù)編譯后的函數(shù) key 是不會(huì)緩存的。

Statement 執(zhí)行 sql 語(yǔ)句是否會(huì)對(duì)編譯后的函數(shù)進(jìn)行緩存
這個(gè)不好說(shuō)猛们,對(duì)于每個(gè)數(shù)據(jù)庫(kù)的具體實(shí)現(xiàn)都是不一樣的念脯,對(duì)于預(yù)編譯肯定都大體相同,但是對(duì)于 Statement 和普通 sql弯淘,數(shù)據(jù)庫(kù)一般都是先檢查 sql 語(yǔ)句是否正確绿店,然后編譯 sql 語(yǔ)句成為函數(shù),最后執(zhí)行函數(shù)庐橙。其實(shí)也不乏某些數(shù)據(jù)庫(kù)很瘋狂假勿,對(duì)于普通 sql 的函數(shù)進(jìn)行緩存。但是目前的主流數(shù)據(jù)庫(kù)都不會(huì)對(duì) sql 函數(shù)進(jìn)行緩存的态鳖。因?yàn)?sql 語(yǔ)句變化那么多转培,如果對(duì)所有函數(shù)緩存,那么對(duì)于內(nèi)存的消耗也是非常巨大的浆竭。

如果你不確定普通 sql 語(yǔ)句的函數(shù)是否被存儲(chǔ)堡距,那要怎么做呢?兆蕉?

其實(shí)還是一個(gè)道理羽戒,查看 MySQL 日志記錄:檢查第二次執(zhí)行相同 sql 語(yǔ)句時(shí),是否是直接通過(guò) execute 來(lái)進(jìn)行查詢的虎韵。

    public void showUser() {
        //數(shù)據(jù)庫(kù)連接
        Connection connection = null;
        //預(yù)編譯的Statement易稠,使用預(yù)編譯的Statement提高數(shù)據(jù)庫(kù)性能
        PreparedStatement preparedStatement = null;
        //結(jié)果 集
        ResultSet resultSet = null;

        try {
            //加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)
            Class.forName("com.mysql.jdbc.Driver");

            //通過(guò)驅(qū)動(dòng)管理類獲取數(shù)據(jù)庫(kù)鏈接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true", "root", "");

            Statement statement = connection.createStatement();


            resultSet = statement.executeQuery("select * from user where user);
                    //遍歷查詢結(jié)果集
            while (resultSet.next()) {
                System.out.println(resultSet.getString("id") + "  " + resultSet.getString("username"));
            }

            resultSet.close();
            statement.close();

            statement = connection.createStatement();

            resultSet = statement.executeQuery("select * from user where user);
                    //遍歷查詢結(jié)果集
            while (resultSet.next()) {
                System.out.println(resultSet.getString("id") + "  " + resultSet.getString("username"));
            }

            resultSet.close();
            statement.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //釋放資源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                }
            }
        }
    }

日志記錄:

Query SHOW WARNINGS
Query /* mysql-connector-java-5.1.13 ( Revision: ${bzr.revision-id} ) */SELECT @@session.auto_increment_increment
Query SHOW COLLATION
Query SET NAMES utf8mb4
Query SET character_set_results = NULL
Query SET autocommit=1
Query select * from user where username='小天'
Query select * from user where username='小天'
Quit  

看日志就會(huì)知道,都是 Query 命令包蓝,所以并沒(méi)有存儲(chǔ)函數(shù)驶社。

總結(jié):
所以到了這里我的疑惑都解開(kāi)了,PreparedStatement 的預(yù)編譯是數(shù)據(jù)庫(kù)進(jìn)行的测萎,編譯后的函數(shù) key 是緩存在 PreparedStatement 中的亡电,編譯后的函數(shù)是緩存在數(shù)據(jù)庫(kù)服務(wù)器中的。預(yù)編譯前有檢查 sql 語(yǔ)句語(yǔ)法是否正確的操作硅瞧。只有數(shù)據(jù)庫(kù)服務(wù)器支持預(yù)編譯功能時(shí)份乒,JDBC 驅(qū)動(dòng)才能夠使用數(shù)據(jù)庫(kù)的預(yù)編譯功能,否則會(huì)報(bào)錯(cuò)。預(yù)編譯在比較新的 JDBC 驅(qū)動(dòng)版本中默認(rèn)是關(guān)閉的或辖,需要配置連接參數(shù)才能夠打開(kāi)瘾英。在已經(jīng)配置好了數(shù)據(jù)庫(kù)連接參數(shù)的情況下,Statement 對(duì)于 MySQL 數(shù)據(jù)庫(kù)是不會(huì)對(duì)編譯后的函數(shù)進(jìn)行緩存的颂暇,數(shù)據(jù)庫(kù)不會(huì)緩存函數(shù)缺谴,Statement 也不會(huì)緩存函數(shù)的 key,所以多次執(zhí)行相同的一條 sql 語(yǔ)句的時(shí)候耳鸯,還是會(huì)先檢查 sql 語(yǔ)句語(yǔ)法是否正確湿蛔,然后編譯 sql 語(yǔ)句成函數(shù),最后執(zhí)行函數(shù)县爬。
對(duì)于 PreparedStatement 在設(shè)置參數(shù)的時(shí)候會(huì)對(duì)參數(shù)進(jìn)行轉(zhuǎn)義處理阳啥。
因?yàn)?PreparedStatement 已經(jīng)對(duì) sql 模板進(jìn)行了編譯,并且存儲(chǔ)了函數(shù)捌省,所以 PreparedStatement 做的就是把參數(shù)進(jìn)行轉(zhuǎn)義后直接傳入?yún)?shù)到數(shù)據(jù)庫(kù)苫纤,然后讓函數(shù)執(zhí)行碉钠。這就是為什么 PreparedStatement 能夠防止 sql 注入攻擊的原因了纲缓。
PreparedStatement 的預(yù)編譯還有注意的問(wèn)題,在數(shù)據(jù)庫(kù)端存儲(chǔ)的函數(shù)和在 PreparedStatement 中存儲(chǔ)的 key 值喊废,都是建立在數(shù)據(jù)庫(kù)連接的基礎(chǔ)上的祝高,如果當(dāng)前數(shù)據(jù)庫(kù)連接斷開(kāi)了,數(shù)據(jù)庫(kù)端的函數(shù)會(huì)清空污筷,建立在連接上的 PreparedStatement 里面的函數(shù) key 也會(huì)被清空工闺,各個(gè)連接之間的預(yù)編譯都是互相獨(dú)立的。
使用 Statement 執(zhí)行預(yù)編譯
使用 Statement 執(zhí)行預(yù)編譯就是把上面的原始 SQL 語(yǔ)句預(yù)編譯執(zhí)行一次瓣蛀。

 Connection con = JdbcUtils.getConnection();
 Statement stmt = con.createStatement();
 stmt.executeUpdate("prepare myfun from'select * from t_book where bid=?'");
 stmt.executeUpdate("set @str='b1'");
 ResultSet rs = stmt.executeQuery("execute myfun using @str");
 while (rs.next()) {
     System.out.print(rs.getString(1) + ",");
     System.out.print(rs.getString(2) + ",");
     System.out.print(rs.getString(3) + ",");
     System.out.println(rs.getString(4));
 }
 
 stmt.executeUpdate("set @str='b2'");
 rs = stmt.executeQuery("execute myfun using @str");
 while (rs.next()) {
     System.out.print(rs.getString(1) + ", ");
     System.out.print(rs.getString(2) + ", ");
     System.out.print(rs.getString(3) + ", ");
     System.out.println(rs.getString(4));
 }

 rs.close();
 stmt.close();
 con.close();


在持久層框架中存在的問(wèn)題
很多主流持久層框架 (MyBatis陆蟆,Hibernate) 其實(shí)都沒(méi)有真正的用上預(yù)編譯,預(yù)編譯是要我們自己在參數(shù)列表上面配置的惋增,如果我們不手動(dòng)開(kāi)啟叠殷,JDBC 驅(qū)動(dòng)程序 5.0.5 以后版本 默認(rèn)預(yù)編譯都是關(guān)閉的。

所以我們要在參數(shù)列表中配置诈皿,例如:

jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true

注意:

在 MySQL 中林束,既要開(kāi)啟預(yù)編譯也要開(kāi)啟緩存。因?yàn)槿绻皇情_(kāi)啟預(yù)編譯的話效率還沒(méi)有不開(kāi)啟預(yù)編譯效率高稽亏,大家可以做一下性能測(cè)試壶冒,其中性能測(cè)試結(jié)果在這篇博客中有寫(xiě)到,探究 mysql 預(yù)編譯截歉,而在 MySQL 中開(kāi)啟預(yù)編譯和開(kāi)啟緩存胖腾,其中的查詢效率和不開(kāi)啟預(yù)編譯和不開(kāi)啟緩存的效率是持平的。這里用的測(cè)試類是 PreparedStatement。

參考資料:

探究 mysql 預(yù)編譯

PreparedStatement 是如何大幅度提高性能的

參考中文文檔下載:MySQL 預(yù)編譯功能

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胸嘁,一起剝皮案震驚了整個(gè)濱河市瓶摆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌性宏,老刑警劉巖群井,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異毫胜,居然都是意外死亡书斜,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門酵使,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)荐吉,“玉大人,你說(shuō)我怎么就攤上這事口渔⊙溃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵缺脉,是天一觀的道長(zhǎng)痪欲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)攻礼,這世上最難降的妖魔是什么业踢? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮礁扮,結(jié)果婚禮上知举,老公的妹妹穿的比我還像新娘。我一直安慰自己太伊,他們只是感情好雇锡,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著僚焦,像睡著了一般锰提。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叠赐,一...
    開(kāi)封第一講書(shū)人閱讀 52,696評(píng)論 1 312
  • 那天欲账,我揣著相機(jī)與錄音,去河邊找鬼芭概。 笑死赛不,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的罢洲。 我是一名探鬼主播踢故,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼文黎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了殿较?” 一聲冷哼從身側(cè)響起耸峭,我...
    開(kāi)封第一講書(shū)人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淋纲,沒(méi)想到半個(gè)月后劳闹,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洽瞬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年本涕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伙窃。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡菩颖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出为障,到底是詐尸還是另有隱情晦闰,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布鳍怨,位于F島的核電站呻右,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏京景。R本人自食惡果不足惜窿冯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一骗奖、第九天 我趴在偏房一處隱蔽的房頂上張望确徙。 院中可真熱鬧,春花似錦执桌、人聲如沸鄙皇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)伴逸。三九已至,卻和暖如春膘壶,著一層夾襖步出監(jiān)牢的瞬間错蝴,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工颓芭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顷锰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓亡问,卻偏偏與公主長(zhǎng)得像官紫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容