JDBC:深入理解PreparedStatement和Statement

前言
最近聽一個老師講了公開課,在其中講到了PreparedStatement的執(zhí)行原理和Statement的區(qū)別。

當時聽公開課老師講的時候感覺以前就只知道PreparedStatement是“預編譯類”,能夠?qū)ql語句進行預編譯,預編譯后能夠提高數(shù)據(jù)庫sql語句執(zhí)行效率。

但是,聽了那個老師講后我就突然很想問自己,預編譯??是誰對sql語句的預編譯??是數(shù)據(jù)庫?還是PreparedStatement對象??到底什么是預編譯??為什么能夠提高效率??為什么在數(shù)據(jù)庫操作時能夠防止sql注入攻擊??這就引起了我對Preparedstatement的疑惑。

公開課老師講的時候說:”PreparedStatement會對sql文進行預編譯,預編譯后,會存儲在PreparedStatement對象中,等下次再執(zhí)行這個PreparedStatement對象時,會提高很多效率”。這句話我聽了后更疑惑了,預編譯是什么我不知道就算了,竟然還說:對sql預編譯后會存儲在PreparedStatement對象中??我就想問問sql預編譯后是什么??什么被存儲在PreparedStatement對象中??

更讓人感覺疑惑的是Statement。對就是Statement,公開課老師說:“同一條sql語句(字符串都是相同的)在Statement對象中多次執(zhí)行時,Statement只會對當前sql文編譯一次,編譯后存儲在Statement中,在之后的執(zhí)行過程中,都不會進行編譯而是直接運行sql語句”。什么??我沒聽錯吧?Statement還有編譯??等等等等。。。。我當時真的是聽的懷疑人生。

PreparedStatement
在說PreparedStatement之前,我們來看看什么是預編譯。其實預編譯是MySQL數(shù)據(jù)庫本身都支持的。但是MySQL Server 4.1之前的版本是不支持預編譯的。(具體是否包括4.1還得讀者們親自試驗)

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

MySQL中的預編譯功能是這樣的
預編譯的好處:

大家平時都使用過JDBC中的PreparedStatement接口,它有預編譯功能。什么是預編譯功能呢?它有什么好處呢?
當客戶發(fā)送一條SQL語句給服務器后,服務器總是需要校驗SQL語句的語法格式是否正確,然后把SQL語句編譯成可執(zhí)行的函數(shù),最后才是執(zhí)行SQL語句。其中校驗語法,和編譯所花的時間可能比執(zhí)行SQL語句花的時間還要多。
注意:可執(zhí)行函數(shù)存儲在MySQL服務器中,并且當前連接斷開后,MySQL服務器會清除已經(jīng)存儲的可執(zhí)行函數(shù)。
如果我們需要執(zhí)行多次insert語句,但只是每次插入的值不同,MySQL服務器也是需要每次都去校驗SQL語句的語法格式,以及編譯,這就浪費了太多的時間。如果使用預編譯功能,那么只對SQL語句進行一次語法校驗和編譯,所以效率要高。
1
2
3
4
MySQL執(zhí)行預編譯
MySQL執(zhí)行預編譯分為如三步:

1.執(zhí)行預編譯語句,例如:prepare showUsersByLikeName from 'select * from user where username like ?';
2.設(shè)置變量,例如:set @username='%小明%';
3.執(zhí)行語句,例如:execute showUsersByLikeName using @username;
1
2
3
如果需要再次執(zhí)行myfun,那么就不再需要第一步,即不需要再編譯語句了:

1.設(shè)置變量,例如:set @username='%小宋%';
2.執(zhí)行語句,例如:execute showUsersByLikeName using @username;
1
2
如果你看MySQL日志記錄,你就會看到:

配置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
1
2
3
4
5
6
配置之后就重啟MySQL服務器:

在cmd管理員界面執(zhí)行以下操作。
net stop mysql
net start mysql
1
2
使用PreparedStatement執(zhí)行sql查詢
JDBC MySQL驅(qū)動5.0.5以后的版本默認PreparedStatement是關(guān)閉預編譯功能的,所以需要我們手動開啟。而之前的JDBC MySQL驅(qū)動版本默認是開啟預編譯功能的。
MySQL數(shù)據(jù)庫服務器的預編譯功能在4.1之后才支持預編譯功能的。如果數(shù)據(jù)庫服務器不支持預編譯功能時,并且使用PreparedStatement開啟預編譯功能是會拋出異常的。這點非常重要。筆者用的是mysql-connector-jar-5.1.13版本的JDBC驅(qū)動。
在我們以前寫項目的時候,貌似都沒有注意是否開啟PreparedStatement的預編譯功能,以為它一直都是在使用的,現(xiàn)在看看不開啟PreparedStatement的預編譯,查看MySQL的日志輸出到底是怎么樣的。

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

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

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

        //設(shè)置參數(shù),第一個參數(shù)為sql語句中參數(shù)的序號(從1開始),第二個參數(shù)為設(shè)置的參數(shù)值
        preparedStatement.setString(1, "王五");
        //向數(shù)據(jù)庫發(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) {
                // 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();
            }
        }

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
這是輸出日志:

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

1
2
3
4
5
6
7
8
可以看到,在日志中并沒有看到"prepare"命令來預編譯"select * from user where username = ?"這個sql模板。所以我們一般用的PreparedStatement并沒有用到預編譯功能的,只是用到了防止sql注入攻擊的功能。防止sql注入攻擊的實現(xiàn)是在PreparedStatement中實現(xiàn)的,和服務器無關(guān)。筆者在源碼中看到,PreparedStatement對敏感字符已經(jīng)轉(zhuǎn)義過了。

在PreparedStatement中開啟預編譯功能
設(shè)置MySQL連接URL參數(shù):useServerPrepStmts=true,如下所示。
jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true
這樣才能保證mysql驅(qū)動會先把SQL語句發(fā)送給服務器進行預編譯,然后在執(zhí)行executeQuery()時只是把參數(shù)發(fā)送給服務器。
再次執(zhí)行上面的程序看下MySQL日志輸出:

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

1
2
3
4
5
6
7
8
9
10
11
很明顯已經(jīng)進行了預編譯,Prepare select * from user where username = ?,這一句就是對sql語句模板進行預編譯的日志。好的非常Nice。

注意:

我們設(shè)置的是MySQL連接參數(shù),目的是告訴MySQL JDBC的PreparedStatement使用預編譯功能(5.0.5之后的JDBC驅(qū)動版本需要手動開啟,而之前的默認是開啟的),不管我們是否使用預編譯功能,MySQL Server4.1版本以后都是支持預編譯功能的。

cachePrepStmts參數(shù)
當使用不同的PreparedStatement對象來執(zhí)行相同的SQL語句時,還是會出現(xiàn)編譯兩次的現(xiàn)象,這是因為驅(qū)動沒有緩存編譯后的函數(shù)key,導致二次編譯。如果希望緩存編譯后函數(shù)的key,那么就要設(shè)置cachePrepStmts參數(shù)為true。例如:

jdbc:mysql://localhost:3306/mybatis?useServerPrepStmts=true&cachePrepStmts=true
1
程序代碼:

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

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

        //通過驅(qū)動管理類獲取數(shù)據(jù)庫鏈接
        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)閉當前PreparedStatement對象流,否則下次再次創(chuàng)建PreparedStatement對象的時候還是會再次預編譯sql模板,使用PreparedStatement對象后不關(guān)閉當前PreparedStatement對象流是不會緩存預編譯后的函數(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();
            }
        }

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
日志輸出:

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

1
2
3
4
5
6
7
8
9
10
注意:每次使用PreparedStatement對象后都要關(guān)閉該PreparedStatement對象流,否則預編譯后的函數(shù)key是不會緩存的。

Statement執(zhí)行sql語句是否會對編譯后的函數(shù)進行緩存
這個不好說,對于每個數(shù)據(jù)庫的具體實現(xiàn)都是不一樣的,對于預編譯肯定都大體相同,但是對于Statement和普通sql,數(shù)據(jù)庫一般都是先檢查sql語句是否正確,然后編譯sql語句成為函數(shù),最后執(zhí)行函數(shù)。其實也不乏某些數(shù)據(jù)庫很瘋狂,對于普通sql的函數(shù)進行緩存。但是目前的主流數(shù)據(jù)庫都不會對sql函數(shù)進行緩存的。因為sql語句變化那么多,如果對所有函數(shù)緩存,那么對于內(nèi)存的消耗也是非常巨大的。

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

其實還是一個道理,查看MySQL日志記錄:檢查第二次執(zhí)行相同sql語句時,是否是直接通過execute來進行查詢的。

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

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

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

        Statement statement=connection.createStatement();


        resultSet =  statement.executeQuery("select * from user where username='小天'");
        //遍歷查詢結(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 username='小天'");
        //遍歷查詢結(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) {
                // 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();
            }
        }

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
日志記錄:

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

1
2
3
4
5
6
7
8
9
看日志就會知道,都是Query命令,所以并沒有存儲函數(shù)。

總結(jié):
所以到了這里我的疑惑都解開了,PreparedStatement的預編譯是數(shù)據(jù)庫進行的,編譯后的函數(shù)key是緩存在PreparedStatement中的,編譯后的函數(shù)是緩存在數(shù)據(jù)庫服務器中的。預編譯前有檢查sql語句語法是否正確的操作。只有數(shù)據(jù)庫服務器支持預編譯功能時,JDBC驅(qū)動才能夠使用數(shù)據(jù)庫的預編譯功能,否則會報錯。預編譯在比較新的JDBC驅(qū)動版本中默認是關(guān)閉的,需要配置連接參數(shù)才能夠打開。在已經(jīng)配置好了數(shù)據(jù)庫連接參數(shù)的情況下,Statement對于MySQL數(shù)據(jù)庫是不會對編譯后的函數(shù)進行緩存的,數(shù)據(jù)庫不會緩存函數(shù),Statement也不會緩存函數(shù)的key,所以多次執(zhí)行相同的一條sql語句的時候,還是會先檢查sql語句語法是否正確,然后編譯sql語句成函數(shù),最后執(zhí)行函數(shù)。
對于PreparedStatement在設(shè)置參數(shù)的時候會對參數(shù)進行轉(zhuǎn)義處理。
因為PreparedStatement已經(jīng)對sql模板進行了編譯,并且存儲了函數(shù),所以PreparedStatement做的就是把參數(shù)進行轉(zhuǎn)義后直接傳入?yún)?shù)到數(shù)據(jù)庫,然后讓函數(shù)執(zhí)行。這就是為什么PreparedStatement能夠防止sql注入攻擊的原因了。
PreparedStatement的預編譯還有注意的問題,在數(shù)據(jù)庫端存儲的函數(shù)和在PreparedStatement中存儲的key值,都是建立在數(shù)據(jù)庫連接的基礎(chǔ)上的,如果當前數(shù)據(jù)庫連接斷開了,數(shù)據(jù)庫端的函數(shù)會清空,建立在連接上的PreparedStatement里面的函數(shù)key也會被清空,各個連接之間的預編譯都是互相獨立的。
使用Statement執(zhí)行預編譯
使用Statement執(zhí)行預編譯就是把上面的原始SQL語句預編譯執(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();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
在持久層框架中存在的問題
很多主流持久層框架(MyBatis,Hibernate)其實都沒有真正的用上預編譯,預編譯是要我們自己在參數(shù)列表上面配置的,如果我們不手動開啟,JDBC驅(qū)動程序5.0.5以后版本 默認預編譯都是關(guān)閉的。

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

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

在MySQL中,既要開啟預編譯也要開啟緩存。因為如果只是開啟預編譯的話效率還沒有不開啟預編譯效率高,大家可以做一下性能測試,其中性能測試結(jié)果在這篇博客中有寫到,探究mysql預編譯,而在MySQL中開啟預編譯和開啟緩存,其中的查詢效率和不開啟預編譯和不開啟緩存的效率是持平的。這里用的測試類是PreparedStatement。

參考資料:

探究mysql預編譯

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

參考中文文檔下載:MySQL預編譯功能

在寫這篇文章的時候發(fā)生了很多讓人惱火的事情,比如網(wǎng)上很多的答案基本上都是錯誤的,竟然還有人說好??不知道就不要亂說,亂發(fā)表博客,誤人子弟!!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

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

  • JDBC基礎(chǔ)知識 一、采用JDBC訪問數(shù)據(jù)庫的基本步驟: A.載入JDBC驅(qū)動程序 B.定義連接URL ...
    java日記閱讀 3,887評論 0 20
  • JDBC概述 在Java中,數(shù)據(jù)庫存取技術(shù)可分為如下幾類:JDBC直接訪問數(shù)據(jù)庫、JDO技術(shù)、第三方O/R工具,如...
    usopp閱讀 3,546評論 3 75
  • 本文主要內(nèi)容1、JDBC2、DBUtils 01JDBC概念和數(shù)據(jù)庫驅(qū)動程序 A: JDBC概念和數(shù)據(jù)庫驅(qū)動程序a...
    乘風破浪的姐姐閱讀 809評論 0 6
  • 你的破解版密碼管理軟件還好用不? 適逢七夕??,還在趕稿,你問我此時的心情,我只想說工作讓我很開心,今天我要加班到凌...
    大海連著天閱讀 878評論 0 0
  • 不知是專業(yè)原因還是什么,上了大學我就睡得越來越晚了,沒有什么理由,卻總是到深夜,后來晚上熄燈,就成了只有周五周六不...
    YDWX閱讀 488評論 0 1