Java JDBC 操作MySQL熙宇,Properties,MySQL的存儲(chǔ)過程和函數(shù)

數(shù)據(jù)庫(kù)基本操作

數(shù)據(jù)庫(kù)
    RDBMS溉浙,relation database management system烫止,關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng)。
        存放的是結(jié)構(gòu)化數(shù)據(jù)
    
SQL:Structured Query Language戳稽,結(jié)構(gòu)化查詢語(yǔ)言

CRUD
    insert into table_name(field_name,...) values(value,...)
    
    select id,... from table_name where id=xxx,...
    
    update table_name set id=xxx,... where id=xxx
    
    delete from test where ...

JDBC介紹

 * JDBC是Java應(yīng)用程序和數(shù)據(jù)庫(kù)之間的通信橋梁馆蠕,是Java應(yīng)用程序訪問數(shù)據(jù)庫(kù)的通道

 * JDBC代表Java數(shù)據(jù)庫(kù)連接(Java Database Connectivity),它是用于Java編程語(yǔ)言和數(shù)據(jù)庫(kù)之間的
 * 數(shù)據(jù)庫(kù)無(wú)關(guān)連接的標(biāo)準(zhǔn)Java API,換句話說:JDBC是用于在Java語(yǔ)言編程中與數(shù)據(jù)庫(kù)連接的API互躬。
 * 
 * 1.JDBC標(biāo)準(zhǔn)主要由一組接口組成播赁,其好處就是統(tǒng)一了各種數(shù)據(jù)庫(kù)訪問方式。
 * 2.JDBC接口的實(shí)現(xiàn)類稱為數(shù)據(jù)庫(kù)驅(qū)動(dòng)吼渡,有各個(gè)數(shù)據(jù)庫(kù)廠商提供容为,使用JDBC必須導(dǎo)入特定的驅(qū)動(dòng)

JDBC與數(shù)據(jù)庫(kù)通信就是進(jìn)程間的通信,用的就是套接字寺酪。

對(duì)于數(shù)據(jù)庫(kù)坎背,我用的是phpstudy自帶的MySQL數(shù)據(jù)庫(kù),phpstudy的安裝十分簡(jiǎn)單房维,一直點(diǎn)擊下一步就行沼瘫,數(shù)據(jù)庫(kù)賬號(hào)密碼默認(rèn)都是root

java JDBC連接數(shù)據(jù)庫(kù)的步驟

 * 1.導(dǎo)入JDBC驅(qū)動(dòng)jar
 * 2.注冊(cè)JDBC驅(qū)動(dòng)
 *      -參數(shù):"驅(qū)動(dòng)程序類名"
 *      -Class.forName("驅(qū)動(dòng)程序類名")
 * 3.獲得Connection對(duì)象
 *      -需要三個(gè)參數(shù):url,username,password
 *      -連接到數(shù)據(jù)庫(kù)
 * 4.創(chuàng)建Statement(語(yǔ)句)對(duì)象
 *      -conn.getStatement()方法創(chuàng)建對(duì)象
 *      -用于執(zhí)行SQL語(yǔ)句
 *      -execute(ddl) 執(zhí)行任何SQL,常用執(zhí)行DDL咙俩,DCL
 *      -executeUpdate(dml) 執(zhí)行DML語(yǔ)句种柑,如:insert,update,delete
 *      -executeQuery(dql) 執(zhí)行DQL語(yǔ)句,如:select
 * 5.處理SQL執(zhí)行結(jié)果
 *      -execute(ddl) 如果沒有異常則執(zhí)行成功
 *      -executeUpdate 返回?cái)?shù)字堕澄,表示更新"行"數(shù)量瀑志,拋出異常則失敗
 *      -executeQuery(dql) 返回ResultSet(結(jié)果集)對(duì)象,代表執(zhí)行結(jié)果
 *          使用for遍歷處理脖阵,如果查詢失敗拋出異常皂股!
 * 6.關(guān)閉數(shù)據(jù)連接,
 *      -conn.close();

maven導(dǎo)包命黔,引入MySQL驅(qū)動(dòng)

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.46</version>
</dependency>
package com.libai;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

public class Demo1 {
    public static void main(String[] args) {
        try {
             * 注冊(cè)驅(qū)動(dòng)程序
            Class.forName("com.mysql.jdbc.Driver");

             * 連接到數(shù)據(jù)庫(kù)
             * getConnection()方法查找并且嘗試連接到數(shù)據(jù)庫(kù)
            String url = "jdbc:mysql://localhost:3306/test";
            String username = "root";
            String password = "root";
            Connection conn = DriverManager.getConnection(url, username, password);

             * 輸出conn引用對(duì)象的實(shí)際類型
             * 證明:驅(qū)動(dòng)程序提供了Connection接口的實(shí)現(xiàn)類
            System.out.println(conn.getClass());
            
             * 創(chuàng)建Statement語(yǔ)句對(duì)象
            Statement st = conn.createStatement();
            String ddl = "create table rabbit (id INT PRIMARY KEY auto_increment,name varchar(100))";
            String delsql = "drop table rabbit";
            boolean b = st.execute(delsql);

             * 返回結(jié)果:true 表示有結(jié)果集
             *          false 沒有結(jié)果集
             *          創(chuàng)建失敗拋出異常
             * 如果沒有異常呜呐,則創(chuàng)建成功!--- 也就是false

            System.out.println(b);
            st.close();
            conn.close();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打雍纺肌:
class com.mysql.jdbc.JDBC4Connection
false

插入數(shù)據(jù)蘑辑,使用executeUpdate()

String sql = "insert into rabbit set id=2,name='frame2'";
int n = st.executeUpdate(sql);
System.out.println(n);

打印:
1   --- 修改了一行坠宴,輸出正確

select查詢數(shù)據(jù)洋魂,使用executeQuery()

String sql = "select id,name from rabbit";
ResultSet n = st.executeQuery(sql);

* 處理結(jié)果····
* n.next():移動(dòng)結(jié)果集游標(biāo)到下一行,默認(rèn)在第一行之前
* 檢查是否有數(shù)據(jù)喜鼓,如果有返回true副砍,否則false
while(n.next()) {
    * getXXX(列名):返回結(jié)果集當(dāng)前行指定列名的數(shù)據(jù)
    System.out.print(n.getInt("id"));
    System.out.print(n.getString("name"));
}

Properties 是Java專門讀取配置文件的API

db.properties文件,建立在與main同級(jí)的resources下

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/test
jdbc.username=root
jdbc.password=root
package com.libai;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

* Properties 其目的就是為了讀取 .properties文件庄岖,本身實(shí)現(xiàn)了Map接口
* 內(nèi)部是散列表豁翎,限定了key和value都是String類型。
* 方法:load(流)將文件讀取為散列表
* getProperty(key)查詢value

public class Demo5 {
    public static void main(String[] args) {
        try {
            Properties prop = new Properties();
            System.out.println(prop);
            System.out.println(prop.size());
            System.out.println(prop.isEmpty());
            
             * 從包中加載資源
            InputStream input = Demo5.class.getClassLoader().getResourceAsStream("db.properties");

             * 將文件內(nèi)容讀取到散列表中
            prop.load(input);
            
             * 查詢數(shù)據(jù),讀取文件內(nèi)容
            String driver = prop.getProperty("jdbc.driver");
            System.out.println(driver);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


修改數(shù)據(jù)參數(shù)后重新寫入到文件中
Properties prop = new Properties();
prop.load(new FileInputStream(path));
prop.setProperty("name", "libai");
prop.setProperty("age", 22 + "");
prop.store(new FileOutputStream(path), "我的注釋");

將Properties與JDBC查詢數(shù)據(jù)庫(kù)結(jié)合起來隅忿,方便之后修改數(shù)據(jù)庫(kù)連接信息(總不能老是去修改java代碼吧)

 * Properties讀取配置文件,
 * 
 * Properties是java中專門用于讀取配置文件的API
 * 管理數(shù)據(jù)庫(kù)連接
 * 在軟件中數(shù)據(jù)庫(kù)連接使用非常頻繁谨垃,如果每次都創(chuàng)建連接启搂,就會(huì)造成代碼的大量冗余,
 * 常規(guī)的做法是建立數(shù)據(jù)庫(kù)連接工具類刘陶,封裝數(shù)據(jù)庫(kù)連接過程胳赌,統(tǒng)一數(shù)據(jù)庫(kù)連接過程,使用時(shí)候就可以簡(jiǎn)化代碼匙隔。
 * 
 * 
 * 實(shí)現(xiàn)步驟:
 * 1.創(chuàng)建數(shù)據(jù)庫(kù)連接參數(shù)文件疑苫,db.properties
 * 2.創(chuàng)建DbUtils.java封裝數(shù)據(jù)庫(kù)連接方法
 *      -利用Properties讀取配置文件中的數(shù)據(jù)庫(kù)連接參數(shù)
 *      -創(chuàng)建方法 getConnection封裝數(shù)據(jù)庫(kù)連接過程
 * 3.使用getConnection方法

下面是封裝了Properties讀取數(shù)據(jù)庫(kù)配置信息的和連接數(shù)據(jù)庫(kù)的類

package com.libai;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;

public class DbUtils {

     * 封裝創(chuàng)建數(shù)據(jù)庫(kù)連接的過程,簡(jiǎn)化數(shù)據(jù)庫(kù)連接
     * 因?yàn)橹恍枰环莘自穑瑒t定義為靜態(tài)變量
    static String driver;
    static String url;
    static String username;
    static String password;
    static {
        try {
            Properties cfg = new Properties();
            InputStream input = DbUtils.class.getClassLoader().getResourceAsStream("db.properties");
            cfg.load(input);
            driver = cfg.getProperty("jdbc.driver");
            url = cfg.getProperty("jdbc.url");
            username = cfg.getProperty("jdbc.username");
            password = cfg.getProperty("jdbc.password");
            System.out.println(cfg);
----上面這句打印{jdbc.password=root, jdbc.username=root, jdbc.url=jdbc:mysql://localhost/test, initSize=5, jdbc.driver=com.mysql.jdbc.Driver, maxActive=10}
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() {
        try {
            Class.forName(driver);
            Connection conn = DriverManager.getConnection(url,username,password);
            return conn;
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
     * 關(guān)閉數(shù)據(jù)庫(kù)的連接
    public static void close(Connection conn) {
        try {
            if(conn != null) {
                conn.close();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 * ResultSet代表DQL查詢結(jié)果捍掺,是2維結(jié)果,其內(nèi)部維護(hù)了一個(gè)讀取數(shù)據(jù)的游標(biāo)再膳,
 * 默認(rèn)情況挺勿,游標(biāo)在第一行數(shù)據(jù)之前,當(dāng)調(diào)用next()方法時(shí)候喂柒,游標(biāo)會(huì)向下移動(dòng)不瓶,
 * 并將返回結(jié)果集中是否包含數(shù)據(jù),如果包含數(shù)據(jù)就返回true灾杰,
 * 結(jié)果集還提供了很好get方法用于獲取結(jié)果集游標(biāo)指向當(dāng)前行數(shù)據(jù)蚊丐。

調(diào)用上面的類實(shí)現(xiàn)連接數(shù)據(jù)庫(kù)

package com.libai;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class Demo4 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DbUtils.getConnection();
            Statement st = conn.createStatement();
            String sql = "select * from rabbit";
            ResultSet rs = st.executeQuery(sql);
            while(rs.next()) {
                System.out.println(rs.getInt("id")+", "+rs.getString("name"));
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            DbUtils.close(conn);
        }
    }
}

打印:
1, libai
2, ttt

數(shù)據(jù)庫(kù)連接池DBCP

數(shù)據(jù)庫(kù)連接池中存放一定數(shù)量的數(shù)據(jù)庫(kù)連接艳吠,需要使用時(shí)從其中取麦备,用完了就放回池中,循環(huán)使用(數(shù)據(jù)庫(kù)連接的連接和關(guān)閉都是十分耗費(fèi)資源的)

單例模式:singleton

池(集合)化模式:
    使用有限的對(duì)象數(shù)量服務(wù)于大量的客戶端請(qǐng)求昭娩。
    
Datasource
    1.內(nèi)部是連接池
    2.java.sql.Datasource, Connection getConnection();
    3.Connection.close()

使用DBCP

 * 使用DBCP
 * 
 * 1.導(dǎo)入連接池jar
 * 2.創(chuàng)建連接池對(duì)象
 * 3.設(shè)置數(shù)據(jù)庫(kù)必須的連接參數(shù)
 * 4.設(shè)置可選的連接池管理策略參數(shù)
 * 5.從連接池中獲得活動(dòng)的數(shù)據(jù)庫(kù)連接
 * 6.使用連接范圍數(shù)據(jù)庫(kù)
 * 7.使用以后關(guān)閉數(shù)據(jù)庫(kù)連接凛篙,這個(gè)不是真的關(guān)閉連接,而是將使用過的連接歸還到連接池栏渺。
 * 
 * 為了便捷的使用連接池呛梆,經(jīng)常將連接池封裝為一個(gè)連接管理工具類。

maven導(dǎo)入包DBCP

<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>

一個(gè)數(shù)據(jù)庫(kù)連接池的簡(jiǎn)單例子

package day02;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.commons.dbcp.BasicDataSource;

public class Demo1 {
    public static void main(String[] args) throws SQLException {
        BasicDataSource ds = new BasicDataSource();
        
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost/test";
        String username = "root";
        String password = "root";

         * 設(shè)置必須的參數(shù)
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        
         * 設(shè)置連接池的管理策略參數(shù)
        ds.setInitialSize(2);  --- 一開始就設(shè)置池中存放兩個(gè)連接
        ds.setMaxActive(100);   ---最多可以有100個(gè)連接
        
         * 使用連接池中的數(shù)據(jù)庫(kù)連接迈嘹,取不到就堵塞
        Connection conn = ds.getConnection();
        
         * 執(zhí)行SQL
        Statement st = conn.createStatement();
        String sql = "select * from rabbit";
        ResultSet rs = st.executeQuery(sql);
        while(rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            System.out.println(id+", "+name);
        }
        
         * 歸還連接到數(shù)據(jù)庫(kù)連接池!全庸!
        conn.close();
    }
}

打有阒佟:
1, libai
2, talent
3, where
4, HOW

db.properties文件添加兩條配置

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/test
jdbc.username=root
jdbc.password=root

# parameter for BasicDataSource,initSize是初始化建立1個(gè)連接壶笼,最大2個(gè)連接
initSize=1
maxActive=2

DBUtils.java 連接池模塊神僵,直接調(diào)用靜態(tài)方法就行,配置參數(shù)依據(jù)上面的db.properties文件覆劈,下面所有的代碼用到的全都是下面這個(gè)DBUtils.java 連接池模塊保礼。

package day02;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import org.apache.commons.dbcp.BasicDataSource;

 * 連接池版本的數(shù)據(jù)庫(kù)連接管理工具類沛励,適合于并發(fā)場(chǎng)合
public class DBUtils {
    private static String driver;
    private static String url;
    private static String username;
    private static String password;
    private static int initSize;
    private static int maxActive;
    
    private static BasicDataSource ds;
    
    static {        
        try {
            Properties cfg = new Properties();
            InputStream input = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
            cfg.load(input);
            driver = cfg.getProperty("jdbc.driver");
            url = cfg.getProperty("jdbc.url");
            username = cfg.getProperty("jdbc.username");
            password = cfg.getProperty("jdbc.password");
            initSize = Integer.parseInt(cfg.getProperty("initSize"));
            maxActive = Integer.parseInt(cfg.getProperty("maxActive"));
            
            ds = new BasicDataSource();
            
             * 初始化連接池
            ds.setDriverClassName(driver);
            ds.setUrl(url);
            ds.setUsername(username);
            ds.setPassword(password);
            ds.setInitialSize(initSize);
            ds.setMaxActive(maxActive);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static Connection getConnection() {
        Connection conn = null;
        try {
             * getConnection()從連接池中獲取重用的連接,如果連接池滿了炮障,
             * 則等待目派,如果有連接歸還,則獲取重用的連接
            conn = ds.getConnection();
            return conn;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    
     * 將用過的連接歸還到連接池
    public static void close(Connection conn) {
        try {
            if(conn != null) {
                conn.close();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

調(diào)用上面的靜態(tài)方法連接池

package day02;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo2 {
    public static void main(String[] args) {
        Connection conn = DBUtils.getConnection();
        Statement st;
        try {
            st = conn.createStatement();
            String sql = "select * from rabbit where id>2";
            ResultSet rs= st.executeQuery(sql);
            while(rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                System.out.println(id+", "+name);
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtils.close(conn);
        }
    }
}

打有灿:
3, where
4, HOW

線程測(cè)試數(shù)據(jù)連接池的效果

package day02;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

 * 線程并發(fā)測(cè)試
public class Demo3 {
    public static void main(String[] args) {
        for(int i=0; i<5; i++) {
            Thread t = new DemoThread(5000);
            t.start();
        }
    }
}

class DemoThread extends Thread{
    int wait;

    public DemoThread(int wait) {
        this.wait = wait;
    }

    public void run() {
        Connection conn = null;
        try {
             * getConnection方法在連接池中沒有連接可以使用的時(shí)候企蹭,會(huì)堵塞等待
            conn = DBUtils.getConnection();
            Statement st = conn.createStatement();
            String sql = "select 'Helllo' as a from dual";
            ResultSet rs = st.executeQuery(sql);
            while(rs.next()) {
                System.out.println(Thread.currentThread().getName()+" "+rs.getString("a"));
            }
            Thread.sleep(wait);
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName()+" 歸還連接");
            DBUtils.close(conn);
        }
    }
}

 * 連接池中最大只有兩個(gè)連接時(shí),第三個(gè)線程獲取連接時(shí)會(huì)等待智末,
 * 等待到有連接歸還為止

---- 上面的db.preproties文件中最大連接數(shù)設(shè)置為2了谅摄,所以此處兩個(gè)兩個(gè)執(zhí)行
打印:
Thread-0 Helllo
Thread-3 Helllo
Thread-3 歸還連接
Thread-0 歸還連接
Thread-1 Helllo
Thread-2 Helllo
Thread-2 歸還連接
Thread-1 歸還連接
Thread-4 Helllo
Thread-4 歸還連接

使用PreparedStatement提高數(shù)據(jù)庫(kù)系統(tǒng)性能系馆,重用執(zhí)行計(jì)劃

SQL語(yǔ)句翻譯成執(zhí)行計(jì)劃送漠,才能執(zhí)行
SQL語(yǔ)句完全一樣(大小寫和空格都一樣)的情況下,會(huì)重用執(zhí)行計(jì)劃
使用帶參數(shù)的SQL語(yǔ)句可以使得重用執(zhí)行計(jì)劃由蘑,提高數(shù)據(jù)庫(kù)性能

用法跟上面的Statement差不多

package day02;

import java.sql.Connection;
import java.sql.PreparedStatement;

public class Demo4 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();

             * 帶參數(shù)的SQL語(yǔ)句
            String sql = "insert into rabbit (id,name) values (?,?)";
            
             * 將SQL語(yǔ)句發(fā)送到數(shù)據(jù)庫(kù)闽寡,創(chuàng)建執(zhí)行計(jì)劃,返回值ps就代表執(zhí)行計(jì)劃
            PreparedStatement ps= conn.prepareStatement(sql);

             * 替換[執(zhí)行計(jì)劃]中的參數(shù)纵穿,2個(gè)參數(shù)下隧,按照序號(hào)發(fā)送參數(shù)
            ps.setInt(1, 5);
            ps.setString(2, "zhizhang");

             * 執(zhí)行執(zhí)行計(jì)劃
            int n1 = ps.executeUpdate();
            System.out.println(n1);
            
             * 重用
            ps.setInt(1, 6);
            ps.setString(2, "hello");
            int n2 = ps.executeUpdate();
            System.out.println(n2);
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}

打印:  --執(zhí)行成功谓媒,返回修改的行數(shù)
1
1


換成update淆院,也是類似的
conn = DBUtils.getConnection();
String sql = "update rabbit set name=? where id=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "demo change1");
ps.setInt(2, 5);
int n1 = ps.executeUpdate();
System.out.println(n1);

ps.setString(1, "demo change2");
ps.setInt(2, 6);
int n2 = ps.executeUpdate();
System.out.println(n2);

note

 * JDBC
 * 數(shù)據(jù)庫(kù)連接池
 * 
 * DriverManager管理數(shù)據(jù)庫(kù)連接適合單線程情況,而在多線程并發(fā)情況下句惯,為了能夠重用數(shù)據(jù)庫(kù)連接土辩,
 * 同時(shí)控制并發(fā)連接總數(shù),避免數(shù)據(jù)庫(kù)連接超載抢野,一定要使用數(shù)據(jù)庫(kù)連接池拷淘。
 * 
 * 連接池原理:
 * 數(shù)據(jù)庫(kù)連接池的開源實(shí)現(xiàn)非常多,DBCP是常用的連接池之一指孤。

只要用到了SQL語(yǔ)句拼接启涯,要思考是否有SQL注入的風(fēng)險(xiǎn),PreparedStatement可以避免SQL注入的風(fēng)險(xiǎn)恃轩,它將單引號(hào)等特殊字符轉(zhuǎn)義结洼。

* PreparedStatement 對(duì)象用于執(zhí)行帶參數(shù)的預(yù)編譯執(zhí)行計(jì)劃
 * 
 * 關(guān)于執(zhí)行計(jì)劃:
 * 1.任何SQL執(zhí)行過程都是先編譯“執(zhí)行計(jì)劃”,再執(zhí)行“執(zhí)行計(jì)劃”
 * 2.數(shù)據(jù)庫(kù)為了優(yōu)化性能叉跛,在SQL相同的時(shí)候松忍,會(huì)重用執(zhí)行計(jì)劃
 *      -執(zhí)行計(jì)劃編譯較慢
 *      -重用執(zhí)行計(jì)劃可以提提高數(shù)據(jù)庫(kù)性能
 * 3.數(shù)據(jù)庫(kù)只在SQL語(yǔ)句完全一樣時(shí)候才重用相同的執(zhí)行計(jì)劃
 *      -如果SQL語(yǔ)句中有一個(gè)字符的更改,會(huì)創(chuàng)建不同的執(zhí)行計(jì)劃
 *      -SQL中一個(gè)空格或者一個(gè)大小寫的不同也會(huì)創(chuàng)建不同的執(zhí)行計(jì)劃
 * 
 * PreparedStatement 好處是可以重復(fù)使用執(zhí)行計(jì)劃筷厘,提高DB效率
 * 
 * 使用步驟:
 * 1.將帶參數(shù)的SQL發(fā)送到數(shù)據(jù)庫(kù)創(chuàng)建執(zhí)行計(jì)劃
 * 2.替換執(zhí)行計(jì)劃中的參數(shù)
 * 3.執(zhí)行執(zhí)行計(jì)劃鸣峭,得到結(jié)果
 * 
 * PreparedStatement 可以避免SQL注入攻擊宏所,它將單引號(hào)等特殊字符加上反斜杠轉(zhuǎn)義了

SQL注入例子

package day02;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;

public class Demo6 {
    public static void main(String[] args) {
        if(login()) {
            System.out.println("登陸成功");
        }else {
            System.out.println("賬戶名或密碼錯(cuò)誤");
        }
    }
    
    
    public static boolean login() {
        Scanner scan = new Scanner(System.in);
        System.out.print("請(qǐng)輸入用戶名:");
        String username = scan.nextLine().trim();
        System.out.print("請(qǐng)輸入密碼:");
        String password = scan.nextLine().trim();
        
        Connection conn = null;
        int back = 0;
        try {
            conn = DBUtils.getConnection();
            String sql1 = "select count(*) as back from rabbit where username=? and password=?";
            
            --- 第一種方式,PreparedStatement
            PreparedStatement ps = conn.prepareStatement(sql1);
            ps.setString(1, username);      
            ps.setString(2, password);
            System.out.println("自動(dòng)拼接:"+ps);
            // ResultSet rs = ps.executeQuery();   ---未執(zhí)行
            
            --- 第一種方式摊溶,Statement
            Statement st = conn.createStatement();
            String sql2 = "select count(*) as back from rabbit where username='"+ username +"' and password='"+ password +"'";
            System.out.println("手動(dòng)拼接:"+sql2);
            ResultSet rs = st.executeQuery(sql2);

            rs.next();
            back = rs.getInt("back");
            System.out.println("back = " + back);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            DBUtils.close(conn);
        }
        
        if(back >= 1) {
            return true;
        }else {
            return false;
        }
    }
}

打印;
請(qǐng)輸入用戶名:aa
請(qǐng)輸入密碼:123' or '1'='1
自動(dòng)拼接:com.mysql.jdbc.JDBC42PreparedStatement@3daa422a: select count(*) as back from rabbit where username='aa' and password='123\' or \'1\'=\'1'
手動(dòng)拼接:select count(*) as back from rabbit where username='aa' and password='123' or '1'='1'
back = 6
登陸成功

ResultSet中存在可滾動(dòng)的結(jié)果集爬骤,但從來不用,其性能差

獲取的ResultSet結(jié)果集中更扁,存在獲取的數(shù)據(jù)庫(kù)信息的元數(shù)據(jù)盖腕,可用ResultSetMetaData API調(diào)用獲取

 * 結(jié)果集元數(shù)據(jù)
 * ResultSetMetaData用于描述查詢結(jié)果的相關(guān)性信息,其中包含列名稱浓镜,列數(shù)量溃列,類數(shù)據(jù)類型等

package day03;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class Demo1 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();
            String sql = "select * from rabbit where id=1";
            PreparedStatement ps = conn.prepareStatement(sql);
            ResultSet rs = ps.executeQuery();
            --- 調(diào)用元數(shù)據(jù),具體的信息都可以從接口中取得膛薛,具體可以查看java.sql文檔
            System.out.println(rs.getMetaData());

            while(rs.next()) {
                String username = rs.getString("username");
                String password = rs.getString("password");
                System.out.println("username = "+username+" password = "+password);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}

打犹:
com.mysql.jdbc.ResultSetMetaData@654f0d9c - Field level information: 
    com.mysql.jdbc.Field@6a400542[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=id,originalColumnName=id,mysqlType=3(FIELD_TYPE_LONG),flags= AUTO_INCREMENT PRIMARY_KEY, charsetIndex=63, charsetName=US-ASCII]
    com.mysql.jdbc.Field@6580cfdd[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=username,originalColumnName=username,mysqlType=253(FIELD_TYPE_VAR_STRING),flags=, charsetIndex=8, charsetName=WINDOWS-1252]
    com.mysql.jdbc.Field@7e0b85f9[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=password,originalColumnName=password,mysqlType=253(FIELD_TYPE_VAR_STRING),flags=, charsetIndex=33, charsetName=UTF-8]
username = libai password = 111

JDBC事務(wù)控制

 * JDBC 事務(wù)控制,Transaction 一組不可分割的操作哄啄。
 * 
 * 數(shù)據(jù)庫(kù)提供了事務(wù)控制功能雅任,支持ACID特性。
 * JDBC提供了API咨跌,方便調(diào)用數(shù)據(jù)庫(kù)的事務(wù)功能沪么。
 * 
 * 相關(guān)API:
 *  - Connection.getAutoCommit():獲得當(dāng)前事務(wù)的提交方式,默認(rèn)為true
 *  - Connection.setAutoCommit():設(shè)置事務(wù)的提交屬性锌半,參數(shù)是
 *      -true:自動(dòng)提交禽车; -false:不自動(dòng)提交(默認(rèn)自動(dòng)提交)
 *  - Connection.commit():提交事務(wù)
 *  - Connection.rollback():回滾事務(wù)

特點(diǎn):Atomic,原子性刊殉,不可分割殉摔,整體性。
     Consistent记焊,一致性逸月,數(shù)據(jù)不被破壞。
     isolate遍膜,隔離性碗硬,事務(wù)之間是獨(dú)立的,不能被干擾的瓢颅。
     Durable恩尾,持續(xù)性,數(shù)據(jù)被永久保存起來惜索。

模擬轉(zhuǎn)賬特笋,不符合條件拋出錯(cuò)誤回滾剃浇,
注意:MySQL數(shù)據(jù)庫(kù)測(cè)試表格的數(shù)據(jù)引擎要設(shè)置為InnoDB巾兆,默認(rèn)的MyISAM不支持事務(wù)處理猎物。

package day03;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Demo2 {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();
            conn.setAutoCommit(false);
            String sql1 = "update myaccount set balance=balance+? where id=?";
            String sql2 = "select balance from myaccount where id=?";
            PreparedStatement ps = conn.prepareStatement(sql1);
            
            * 一個(gè)賬戶減少 1000
            ps.setInt(1, -1000);
            ps.setInt(2, 1);
            int n1 = ps.executeUpdate();
            System.out.println(n1);
            
            * 一個(gè)賬戶增加 1000
            ps.setInt(1, 1000);
            ps.setInt(2, 2);
            int n2 = ps.executeUpdate();
            System.out.println(n2);
            
            ps.close();
            
            * 檢測(cè)余額是否大于等于0
            ps = conn.prepareStatement(sql2);
            ps.setInt(1, 1);
            ResultSet rs = ps.executeQuery();
            rs.next();
            int balance = rs.getInt("balance");
            if(balance<0) {
                throw new RuntimeException("余額不足,轉(zhuǎn)賬失敗");
            }else {
                System.out.println("轉(zhuǎn)賬成功");
            }

            * 延時(shí)20s期間角塑,在命令行中查看當(dāng)前修改的表格時(shí)蔫磨,是看不到本次交易結(jié)果的
            * 命令行中修改時(shí),會(huì)堵塞到此次修改完畢
            System.out.println("延時(shí)中");
            Thread.sleep(20000);
            
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null) {
                    conn.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            DBUtils.close(conn);
        }
    }
}

打悠粤妗:
1
1
轉(zhuǎn)賬成功
延時(shí)中



效果圖
mysql事務(wù)測(cè)試

JDBC批量SQL處理

package day03;

import java.sql.Connection;
import java.sql.Statement;
import java.util.Arrays;

public class Demo3 {
    public static void main(String[] args) {
        String sql1 = "CREATE TABLE IF NOT EXISTS `account_01` (" + 
                "  `id` int(11) NOT NULL," + 
                "  `balance` int(11) NOT NULL," + 
                "  PRIMARY KEY (`id`)" + 
                ") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
        String sql2 = "CREATE TABLE IF NOT EXISTS `account_02` (" + 
                "  `id` int(11) NOT NULL," + 
                "  `balance` int(11) NOT NULL," + 
                "  PRIMARY KEY (`id`)" + 
                ") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
        String sql3 = "CREATE TABLE IF NOT EXISTS `account_03` (" + 
                "  `id` int(11) NOT NULL," + 
                "  `balance` int(11) NOT NULL," + 
                "  PRIMARY KEY (`id`)" + 
                ") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();
            Statement ps = conn.createStatement();

             * 將SQL語(yǔ)句添加到Statement緩存中
            ps.addBatch(sql1);
            ps.addBatch(sql2);
            ps.addBatch(sql3);

             * 執(zhí)行一批SQL
            int[] n = ps.executeBatch();
            System.out.println(Arrays.toString(n));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}

打拥倘纭:
[0, 0, 0]      ---執(zhí)行成功

Statement批量插入數(shù)據(jù),注意窒朋,SQL語(yǔ)句中的并不是單引號(hào)搀罢,而是tab鍵上面的那個(gè)鍵的符號(hào),不使用(`)也是可以的侥猩。

 * 批量執(zhí)行
 * 批處理:發(fā)送到數(shù)據(jù)庫(kù)作為一個(gè)單元執(zhí)行的一組更新語(yǔ)句
 * 
 * 批處理降低了應(yīng)用程序和數(shù)據(jù)庫(kù)之間的網(wǎng)絡(luò)調(diào)用榔至,相比單個(gè)SQL語(yǔ)句的處理,批處理更為有效
 * 
 * API方法:
 * - addBatch(String sql)
 *      - Statement類的方法欺劳,可以將多條SQL語(yǔ)句添加到Statement對(duì)象的
 *        SQL語(yǔ)句列表中
 * - addBatch()
 *      - PreparedStatement類的方法唧取,可以將多條預(yù)編譯的SQL語(yǔ)句添加到
 *        PreparedStatement對(duì)象的SQL語(yǔ)句列表中
 * - executeBatch()
 *      - 把Statement對(duì)象或PreparedStatement對(duì)象語(yǔ)句列表中的所有SQL
 *       語(yǔ)句發(fā)送給數(shù)據(jù)庫(kù)進(jìn)行處理
 * - clearBatch()
 *      - 清空當(dāng)前SQL語(yǔ)句列表
 * 
 * 
 * 防止批量過大出現(xiàn)OutOfMemory錯(cuò)誤:
 * 如果PreparedStatement對(duì)象中的SQL列表包含過多的待處理SQL語(yǔ)句,可能會(huì)
 * 產(chǎn)生OutOfMemory錯(cuò)誤划提,分段處理緩沖列表
package day03;

import java.sql.Connection;
import java.sql.Statement;
import java.util.Arrays;

public class Demo3 {
    public static void main(String[] args) {
        String sql1 = "insert into myaccount (`id`,`balance`) values (1,1000)";
        String sql2 = "insert into myaccount (`id`,`balance`) values (2,2000)";
        String sql3 = "insert into myaccount (`id`,`balance`) values (3,3000)";
        Connection conn = null;
        try {
            conn = DBUtils.getConnection();
            Statement ps = conn.createStatement();

             * 將SQL語(yǔ)句添加到Statement緩存中
            ps.addBatch(sql1);
            ps.addBatch(sql2);
            ps.addBatch(sql3);

             * 執(zhí)行一批SQL
            int[] n = ps.executeBatch();
            System.out.println(Arrays.toString(n));
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DBUtils.close(conn);
        }
    }
}

打臃愕堋:
[1, 1, 1]     ---執(zhí)行成功

PreparedStatement批量插入數(shù)據(jù)

package day03;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Arrays;

public class Demo4 {
    public static void main(String[] args) {
        String sql = "insert into myaccount (id,balance) values(?,?)";
        Connection conn = DBUtils.getConnection();
        try {
            PreparedStatement ps = conn.prepareStatement(sql);
            for(int i=0; i<100; i++) {

                 * 替換參數(shù)
                ps.setInt(1, i+100);
                ps.setInt(2, i*1000);

                 * 將參數(shù)添加到ps緩沖區(qū)
                ps.addBatch();

                 * 清空緩沖區(qū),一般是在數(shù)據(jù)量特別大的時(shí)候避免內(nèi)存溢出
                if(i%8 == 0) {
                    ps.executeBatch();
                    ps.clearBatch();
                }
            }
            int[] n = ps.executeBatch();
            System.out.println(Arrays.toString(n));
            
        } catch (Exception e) {
            e.printStackTrace();    
        } finally {
            DBUtils.close(conn);
        }
    }
}

打优敉:
[1, 1, 1]   --- 每8個(gè)一批已經(jīng)被執(zhí)行過了

返回自動(dòng)主鍵

JDBC API提供了返回插入數(shù)據(jù)期間自動(dòng)生成ID的API
PreparedStatement ps = conn.prepareStatement(sql,列名列表);
ResutSet rs = ps.getGeneratedKeys();

向數(shù)據(jù)庫(kù)中插入/讀取image和長(zhǎng)文本

package test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import org.junit.Test;
import Utils.JDBCUtils;

public class TestCase2 {
     * 向數(shù)據(jù)庫(kù)中插入image淡诗,長(zhǎng)文本
     * 在數(shù)據(jù)庫(kù)中image對(duì)應(yīng)的存儲(chǔ)類型是longblob,長(zhǎng)文本對(duì)應(yīng)的存儲(chǔ)類型是longtext
    @Test
    public void test1() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        String sql = "INSERT INTO customers(name,sex,age,photo,memo) VALUES(?,?,?,?,?)";
        PreparedStatement ppst = conn.prepareStatement(sql);
        ppst.setString(1, "hello");
        ppst.setInt(2, 0);
        ppst.setInt(3, 23);
        ppst.setBinaryStream(4, new FileInputStream("E://TestCase//day20//demo.jpg"));
        ppst.setString(5, "aaaaaaaaaaaaaaaaaaaaa");
        
        ppst.executeUpdate();
        
        ppst.close();
        conn.close();
        System.out.println("over");
    }
    
     * 從數(shù)據(jù)庫(kù)中讀取image和長(zhǎng)文本
    @Test
    public void test2() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        String sql = "SELECT * FROM customers WHERE id=?";
        PreparedStatement ppst = conn.prepareStatement(sql);
        ppst.setInt(1, 1);
        
        ResultSet rs = ppst.executeQuery();
        rs.next();
        int id = rs.getInt("id");
        String name = rs.getString("name");
        int sex = rs.getInt("sex");
        int age = rs.getInt("age");
        InputStream photo = rs.getBinaryStream("photo");
        String memo = rs.getString("memo");
        
        System.out.println(id + ", " + name + ", " + age + ", " + sex + "," + memo);
        System.out.println("photo length = " + photo.available());
        FileOutputStream fos = new FileOutputStream("E://TestCase//day24//demo.jpg");
        byte[] buffer = new byte[1024];
        int len = -1;
        while((len = photo.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
        fos.close();
        
        ppst.close();
        conn.close();
        System.out.println("over");
    }
}

存儲(chǔ)過程

是在服務(wù)器中直接調(diào)用的掸犬,只需要調(diào)用時(shí)傳遞必要的參數(shù)袜漩,就可以直接執(zhí)行,速度非惩逅椋快宙攻。

前提是數(shù)據(jù)庫(kù)不再需要其他的參數(shù)來執(zhí)行數(shù)據(jù)庫(kù)的操作了

在Java中用CallableStatement來負(fù)責(zé)調(diào)用數(shù)據(jù)庫(kù)的存儲(chǔ)過程

存儲(chǔ)過程
    store procedure,存放在數(shù)據(jù)庫(kù)中的一組SQL,在服務(wù)器端執(zhí)行
    創(chuàng)建存儲(chǔ)過程
        mysql>delimiter //  --聲明結(jié)束符
    
    create procedure sp_biginsert(in num int)
    begin
        start transaction;
        declare i int default 0;
        while i < num do
            insert into customers(name,age,sex) values(concat('tom',i),i,i);
            set i = i + 1;
        end while;
        commit;
    end//
    
    mysql> show procedure status;//可以查看所有的存儲(chǔ)過程
創(chuàng)建存儲(chǔ)過程介褥,作用是返回customers表的數(shù)據(jù)量
    mysql> create procedure sp_count(out param int)
        -> begin
        -> select count(*) into param from customers;
        -> end
        -> //
        Query OK, 0 rows affected (0.11 sec)

調(diào)用存儲(chǔ)過程
    @Test
    public void test3() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        //輸出的
        String sql = "{call sp_count(?)}";
        //創(chuàng)建cst對(duì)象
        CallableStatement cst = conn.prepareCall(sql);
        //注冊(cè)輸出參數(shù)
        cst.registerOutParameter(1, Types.INTEGER);
        //執(zhí)行存儲(chǔ)過程
        cst.execute();
        //取得輸出參數(shù)
        int count = cst.getInt(1);
        System.out.println(count);
    }
打印結(jié)果:取得該表的數(shù)據(jù)量
10000

返回a+b的值

返回兩個(gè)數(shù)的合
    mysql> create procedure sp_add(in a int, in b int,out c int)
        -> begin
        -> set c:=a+b;
        -> end
        -> //
        Query OK, 0 rows affected (0.00 sec)
調(diào)用該存儲(chǔ)過程
    @Test
    public void test4() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        //輸出的
        String sql = "{call sp_add(?,?,?)}";
        //創(chuàng)建cst對(duì)象
        CallableStatement cst = conn.prepareCall(sql);
        //對(duì)于輸入?yún)?shù)需要綁定參數(shù)值
        cst.setInt(1, 1);
        cst.setInt(2, 5);
        //注冊(cè)輸出參數(shù)
        cst.registerOutParameter(3, Types.INTEGER);
        //執(zhí)行存儲(chǔ)過程
        cst.execute();
        //取得輸出參數(shù)
        int count = cst.getInt(3);
        System.out.println(count);
    }
打印結(jié)果:
5

返回a-b的值座掘,一個(gè)數(shù)可以作為輸入量,也可以作為輸出量

返回a-b的值柔滔,一個(gè)數(shù)可以作為輸入量溢陪,也可以作為輸出量
    mysql> create procedure sp_subtract(in a int, inout b int)
        -> begin
        -> set b:=a-b;
        -> end
        -> //
        Query OK, 0 rows affected (0.00 sec)

調(diào)用該存儲(chǔ)過程
    @Test
    public void test5() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        //輸出的
        String sql = "{call sp_subtract(?,?)}";
        //創(chuàng)建cst對(duì)象
        CallableStatement cst = conn.prepareCall(sql);
        //對(duì)于輸入?yún)?shù)需要綁定參數(shù)值
        cst.setInt(1, 1);
        cst.setInt(2, 5);
        //注冊(cè)輸出參數(shù)
        cst.registerOutParameter(2, Types.INTEGER);
        //執(zhí)行存儲(chǔ)過程
        cst.execute();
        //取得輸出參數(shù)
        int count = cst.getInt(2);
        System.out.println(count);
    }
打印結(jié)果:
-4

創(chuàng)建一個(gè)函數(shù),并在JDBC中調(diào)用

該函數(shù)返回a+b的值
    mysql> create function f_add(a int, b int) returns int
        -> return a+b//
    Query OK, 0 rows affected (0.00 sec)

調(diào)用該函數(shù)
    @Test
    public void test6() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        //輸出的
        String sql = "{? = call f_add(?,?)}";
        //創(chuàng)建cst對(duì)象
        CallableStatement cst = conn.prepareCall(sql);
        //對(duì)于輸入?yún)?shù)需要綁定參數(shù)值
        cst.setInt(2, 100);
        cst.setInt(3, 600);
        //注冊(cè)輸出參數(shù)
        cst.registerOutParameter(1, Types.INTEGER);
        //執(zhí)行存儲(chǔ)過程
        cst.execute();
        //取得輸出參數(shù)
        int count = cst.getInt(1);
        System.out.println(count);
    }
打印結(jié)果:
700
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末睛廊,一起剝皮案震驚了整個(gè)濱河市形真,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌超全,老刑警劉巖咆霜,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邓馒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蛾坯,警方通過查閱死者的電腦和手機(jī)光酣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脉课,“玉大人救军,你說我怎么就攤上這事√攘悖” “怎么了唱遭?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)呈驶。 經(jīng)常有香客問我胆萧,道長(zhǎng),這世上最難降的妖魔是什么俐东? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任跌穗,我火速辦了婚禮,結(jié)果婚禮上虏辫,老公的妹妹穿的比我還像新娘蚌吸。我一直安慰自己,他們只是感情好砌庄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布羹唠。 她就那樣靜靜地躺著,像睡著了一般娄昆。 火紅的嫁衣襯著肌膚如雪佩微。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天萌焰,我揣著相機(jī)與錄音哺眯,去河邊找鬼。 笑死扒俯,一個(gè)胖子當(dāng)著我的面吹牛奶卓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播撼玄,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼夺姑,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了掌猛?” 一聲冷哼從身側(cè)響起盏浙,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后废膘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辣往,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年殖卑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坊萝。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡孵稽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出十偶,到底是詐尸還是另有隱情菩鲜,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布惦积,位于F島的核電站接校,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏狮崩。R本人自食惡果不足惜蛛勉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望睦柴。 院中可真熱鬧诽凌,春花似錦、人聲如沸坦敌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)狱窘。三九已至杜顺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蘸炸,已是汗流浹背躬络。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搭儒,地道東北人洗鸵。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像仗嗦,于是被迫代替她去往敵國(guó)和親膘滨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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