mybatis 深入剖析

java中提到持久層框架,相信沒(méi)有人不知道m(xù)ybatis的存在蜈项,相對(duì)于JDBC她多了一份干練(jdbc工作量大),相對(duì)于Hibernate她又多了一份靈動(dòng)(HQL雖然方便,但臺(tái)呆板)锋谐,面對(duì)如此尤物,今天我們就一起走就她的世界截酷。

一涮拗、mybatis的簡(jiǎn)單實(shí)現(xiàn)

→準(zhǔn)備工作

? ? ? ? 1.創(chuàng)建實(shí)體類和表映射

? ? ? ? 2.導(dǎo)入maven依賴

→編寫接口和mapper為文件

注意:在idea中,直接把資源文件放在src文件夾下迂苛,如果不進(jìn)行設(shè)置三热,是不能被找到的,所以一定要在pom.xml(第一張圖)中加入標(biāo)記的代碼三幻,指定加載位置就漾。

→編寫配置文件

注意:配置文件要放在resources目錄下

→測(cè)試

二、mybatis的實(shí)現(xiàn)原理分析

→原理分析之一:MyBatis的主要成員

Configuration :MyBatis所有的配置信息都保存在Configuration對(duì)象之中念搬,配置文件中的大部分配置都會(huì)存儲(chǔ)到該類中

SqlSession :作為MyBatis工作的主要頂層API从藤,表示和數(shù)據(jù)庫(kù)交互時(shí)的會(huì)話,完成必要數(shù)據(jù)庫(kù)增刪改查功能

Executor: MyBatis執(zhí)行器锁蠕,是MyBatis 調(diào)度的核心夷野,負(fù)責(zé)SQL語(yǔ)句的生成和查詢緩存的維護(hù)

StatementHandler :封裝了JDBC Statement操作,負(fù)責(zé)對(duì)JDBC statement 的操作荣倾,如設(shè)置參數(shù)等

ParameterHandler :負(fù)責(zé)對(duì)用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所對(duì)應(yīng)的數(shù)據(jù)類型

ResultSetHandler : 負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集對(duì)象轉(zhuǎn)換成List類型的集合

TypeHandler :負(fù)責(zé)java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型(也可以說(shuō)是數(shù)據(jù)表列類型)之間的映射和轉(zhuǎn)換

MappedStatement :MappedStatement維護(hù)一條<select|update|delete|insert>節(jié)點(diǎn)的封裝

SqlSource:負(fù)責(zé)根據(jù)用戶傳遞的parameterObject悯搔,動(dòng)態(tài)地生成SQL語(yǔ)句,將信息封裝到BoundSql對(duì)象中舌仍,并返回

BoundSql :表示動(dòng)態(tài)生成的SQL語(yǔ)句以及相應(yīng)的參數(shù)信息

→原理分析之二:Mybatis的執(zhí)行流程

→原理分析之三:初始化(配置文件讀取和解析)

1. 準(zhǔn)備工作

? 編寫測(cè)試代碼(具體請(qǐng)參考《Mybatis入門示例》),設(shè)置斷點(diǎn),以Debug模式運(yùn)行,具體代碼如下:

Java代碼?

String resource =?"mybatis.cfg.xml";??Reader reader = Resources.getResourceAsReader(resource);??SqlSessionFactory ssf =?new?SqlSessionFactoryBuilder().build(reader);??SqlSession session = ssf.openSession();

2.源碼分析

我們此次就對(duì)上面的代碼進(jìn)行跟蹤和分析妒貌,let's go。

首先我們按照順序先看看第一行和第二行代碼铸豁,看看它主要完成什么事情:

Java代碼?

String resource =?"mybatis.cfg.xml";??Reader reader = Resources.getResourceAsReader(resource);

讀取Mybaits的主配置配置文件灌曙,并返回該文件的輸入流,我們知道Mybatis所有的SQL語(yǔ)句都寫在XML配置文件里面,所以第一步就需要讀取這些XML配置文件节芥,這個(gè)不難理解在刺,關(guān)鍵是讀取文件后怎么存放逆害。

我們接著看第三行代碼(如下),該代碼主要是讀取配置文件流并將這些配置信息存放到Configuration類中蚣驼。

Java代碼?

SqlSessionFactory ssf =?new?SqlSessionFactoryBuilder().build(reader);

SqlSessionFactoryBuilder的build的方法如下:

Java代碼?

public?SqlSessionFactory build(Reader reader) {??return?build(reader,?null,?null);??}

其實(shí)是調(diào)用該類的另一個(gè)build方法來(lái)執(zhí)行的,具體代碼如下:

Java代碼?

public?SqlSessionFactory build(Reader reader, String environment, Properties properties) {??try?{??XMLConfigBuilder parser =?new?XMLConfigBuilder(reader, environment, properties);??return?build(parser.parse());??}?catch?(Exception e) {??throw?ExceptionFactory.wrapException("Error building SqlSession.", e);??}?finally?{??ErrorContext.instance().reset();??try?{??reader.close();??}?catch?(IOException e) {??// Intentionally ignore. Prefer previous error.??}??}??}

我們重點(diǎn)看一下里面兩行:

Java代碼?

//創(chuàng)建一個(gè)配置文件流的解析對(duì)象XMLConfigBuilder,其實(shí)這里是將環(huán)境和配置文件流賦予解析類??XMLConfigBuilder parser =?new?XMLConfigBuilder(reader, environment, properties);??// 解析類對(duì)配置文件進(jìn)行解析并將解析的內(nèi)容存放到Configuration對(duì)象中,并返回SqlSessionFactory??return?build(parser.parse());

這里的XMLConfigBuilder初始化其實(shí)調(diào)用的代碼如下:

Java代碼?

private?XMLConfigBuilder(XPathParser parser, String environment, Properties props) {??super(new?Configuration());??ErrorContext.instance().resource("SQL Mapper Configuration");??this.configuration.setVariables(props);??this.parsed =?false;??this.environment = environment;??this.parser = parser;??}

XMLConfigBuilder的parse方法執(zhí)行代碼如下:?

Java代碼?

public?Configuration parse() {??if?(parsed) {??throw?new?BuilderException("Each MapperConfigParser can only be used once.");??}??parsed =?true;??parseConfiguration(parser.evalNode("/configuration"));??return?configuration;??}

解析的內(nèi)容主要是在parseConfiguration方法中,它主要完成的工作是讀取配置文件的各個(gè)節(jié)點(diǎn),然后將這些數(shù)據(jù)映射到內(nèi)存配置對(duì)象Configuration中,我們看一下parseConfiguration方法內(nèi)容:

Java代碼?

private?void?parseConfiguration(XNode root) {??try?{??typeAliasesElement(root.evalNode("typeAliases"));??pluginElement(root.evalNode("plugins"));??objectFactoryElement(root.evalNode("objectFactory"));??objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));??propertiesElement(root.evalNode("properties"));??settingsElement(root.evalNode("settings"));??environmentsElement(root.evalNode("environments"));??typeHandlerElement(root.evalNode("typeHandlers"));??mapperElement(root.evalNode("mappers"));??}?catch?(Exception e) {??throw?new?BuilderException("Error parsing SQL Mapper Configuration. Cause: "?+ e, e);??}??}

最后的build方法其實(shí)是傳入配置對(duì)象進(jìn)去,創(chuàng)建DefaultSqlSessionFactory實(shí)例出來(lái). DefaultSqlSessionFactory是SqlSessionFactory的默認(rèn)實(shí)現(xiàn).

Java代碼?

public?SqlSessionFactory build(Configuration config) {??return?new?DefaultSqlSessionFactory(config);??}


最后我們看一下第四行代碼:

Java代碼?

SqlSession session = ssf.openSession();

通過(guò)調(diào)用DefaultSqlSessionFactory的openSession方法返回一個(gè)SqlSession實(shí)例,我們看一下具體是怎么得到一個(gè)SqlSession實(shí)例的魄幕。首先調(diào)用openSessionFromDataSource方法。?

Java代碼?

public?SqlSession openSession() {??return?openSessionFromDataSource(configuration.getDefaultExecutorType(),?null,?false);??}

下面我們看一下openSessionFromDataSource方法的邏輯:?

Java代碼?

private?SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,?boolean?autoCommit) {??Connection connection =?null;??try?{??//獲取配置信息里面的環(huán)境信息颖杏,這些環(huán)境信息都是包括使用哪種數(shù)據(jù)庫(kù)纯陨,連接數(shù)據(jù)庫(kù)的信息,事務(wù)??final?Environment environment = configuration.getEnvironment();??//根據(jù)環(huán)境信息關(guān)于數(shù)據(jù)庫(kù)的配置獲取數(shù)據(jù)源??final?DataSource dataSource = getDataSourceFromEnvironment(environment);??//根據(jù)環(huán)境信息關(guān)于事務(wù)的配置獲取事務(wù)工廠??TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);??connection = dataSource.getConnection();??if?(level !=?null) {??//設(shè)置連接的事務(wù)隔離級(jí)別??connection.setTransactionIsolation(level.getLevel());??}??//對(duì)connection進(jìn)行包裝留储,使連接具備日志功能翼抠,這里用的是代理。??connection = wrapConnection(connection);??//從事務(wù)工廠獲取一個(gè)事務(wù)實(shí)例??Transaction tx = transactionFactory.newTransaction(connection, autoCommit);??//從配置信息中獲取一個(gè)執(zhí)行器實(shí)例??Executor executor = configuration.newExecutor(tx, execType);??//返回SqlSession的一個(gè)默認(rèn)實(shí)例??return?new?DefaultSqlSession(configuration, executor, autoCommit);??}?catch?(Exception e) {??closeConnection(connection);??throw?ExceptionFactory.wrapException("Error opening session. Cause: "?+ e, e);??}?finally?{??ErrorContext.instance().reset();??}??}

傳入?yún)?shù)說(shuō)明:

(1)ExecutorType:執(zhí)行類型获讳,ExecutorType主要有三種類型:SIMPLE, REUSE, BATCH阴颖,默認(rèn)是SIMPLE,都在枚舉類ExecutorType里面赔嚎。

(2)TransactionIsolationLevel:事務(wù)隔離級(jí)別膘盖,都在枚舉類TransactionIsolationLevel中定義胧弛。

(3)autoCommit:是否自動(dòng)提交尤误,主要是事務(wù)提交的設(shè)置。

DefaultSqlSession是SqlSession的實(shí)現(xiàn)類结缚,該類主要提供操作數(shù)據(jù)庫(kù)的方法給開(kāi)發(fā)人員使用损晤。

這里總結(jié)一下上面的過(guò)程,總共由三個(gè)步驟:

步驟一:讀取Ibatis的主配置文件红竭,并將文件讀成文件流形式(InputStream)尤勋。

步驟二:從主配置文件流中讀取文件的各個(gè)節(jié)點(diǎn)信息并存放到Configuration對(duì)象中。讀取mappers節(jié)點(diǎn)的引用文件茵宪,并將這些文件的各個(gè)節(jié)點(diǎn)信息存放到Configuration對(duì)象最冰。

步驟三:根據(jù)Configuration對(duì)象的信息獲取數(shù)據(jù)庫(kù)連接,并設(shè)置連接的事務(wù)隔離級(jí)別等信息稀火,將經(jīng)過(guò)包裝數(shù)據(jù)庫(kù)連接對(duì)象SqlSession接口返回暖哨,DefaultSqlSession是SqlSession的實(shí)現(xiàn)類,所以這里返回的是DefaultSqlSession凰狞,SqlSession接口里面就是對(duì)外提供的各種數(shù)據(jù)庫(kù)操作篇裁。

原理分析之四:一次SQL查詢的源碼分析

上回我們講到Mybatis加載相關(guān)的配置文件進(jìn)行初始化,這回我們講一下一次SQL查詢?cè)趺催M(jìn)行的赡若。

準(zhǔn)備工作

Mybatis完成一次SQL查詢需要使用的代碼如下:

Java代碼? ?

String resource =?"mybatis.cfg.xml";??Reader reader = Resources.getResourceAsReader(resource);??SqlSessionFactory ssf =?new?SqlSessionFactoryBuilder().build(reader);?? SqlSession session = ssf.openSession();??try?{??UserInfo user = (UserInfo) session.selectOne("User.selectUser",?"1");??System.out.println(user);??}?catch?(Exception e) {??e.printStackTrace();??}?finally?{??session.close();??}

本次我們需要進(jìn)行深入跟蹤分析的是:

Java代碼?

SqlSession session = ssf.openSession();??UserInfo user = (UserInfo) session.selectOne("User.selectUser",?"1");

源碼分析

第一步:打開(kāi)一個(gè)會(huì)話达布,我們看看里面具體做了什么事情。

Java代碼?

SqlSession session = ssf.openSession();

DefaultSqlSessionFactory的 openSession()方法內(nèi)容如下:

Java代碼?

public?SqlSession openSession() {??return?openSessionFromDataSource(configuration.getDefaultExecutorType(),?null,?false);??}

跟進(jìn)去逾冬,我們看一下openSessionFromDataSource方法到底做了啥:

Java代碼?

private?SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,?boolean?autoCommit) {??Connection connection =?null;??try?{??final?Environment environment = configuration.getEnvironment();??final?DataSource dataSource = getDataSourceFromEnvironment(environment);??TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);??connection = dataSource.getConnection();??if?(level !=?null) {??connection.setTransactionIsolation(level.getLevel());??}?connection = wrapConnection(connection);??Transaction tx = transactionFactory.newTransaction(connection, autoCommit);??Executor executor = configuration.newExecutor(tx, execType);??return?new?DefaultSqlSession(configuration, executor, autoCommit);??}?catch(Exception e) {??closeConnection(connection);??throw?ExceptionFactory.wrapException("Error opening session. Cause: "?+ e, e);??}?finally?{??ErrorContext.instance().reset();?}??}

這里我們分析一下這里所涉及的步驟:

(1)獲取前面我們加載配置文件的環(huán)境信息黍聂,并且獲取環(huán)境信息中配置的數(shù)據(jù)源。

(2)通過(guò)數(shù)據(jù)源獲取一個(gè)連接,對(duì)連接進(jìn)行包裝代理(通過(guò)JDK的代理來(lái)實(shí)現(xiàn)日志功能)分冈。

(3)設(shè)置連接的事務(wù)信息(是否自動(dòng)提交圾另、事務(wù)級(jí)別),從配置環(huán)境中獲取事務(wù)工廠雕沉,事務(wù)工廠獲取一個(gè)新的事務(wù)集乔。

(4)傳入事務(wù)對(duì)象獲取一個(gè)新的執(zhí)行器,并傳入執(zhí)行器坡椒、配置信息等獲取一個(gè)執(zhí)行會(huì)話對(duì)象扰路。

從上面的代碼我們可以得出,一次配置加載只能有且對(duì)應(yīng)一個(gè)數(shù)據(jù)源倔叼。對(duì)于上述步驟汗唱,我們不難理解,我們重點(diǎn)看看新建執(zhí)行器和DefaultSqlSession丈攒。

首先哩罪,我們看看newExecutor到底做了什么?

Java代碼?

public?Executor newExecutor(Transaction transaction, ExecutorType executorType) {?executorType = executorType ==?null?? defaultExecutorType : executorType;??executorType = executorType ==?null?? ExecutorType.SIMPLE : executorType;??Executor executor;??if?(ExecutorType.BATCH == executorType) {??executor =?new?BatchExecutor(this, transaction);??}?else?if?(ExecutorType.REUSE == executorType) {??executor =?new?ReuseExecutor(this, transaction);??}?else?{??executor =?new?SimpleExecutor(this, transaction);??}??if?(cacheEnabled) {??executor =?new?CachingExecutor(executor);??}??executor = (Executor) interceptorChain.pluginAll(executor);??return?executor;??}

上面代碼的執(zhí)行步驟如下:

(1)判斷執(zhí)行器類型巡验,如果配置文件中沒(méi)有配置執(zhí)行器類型际插,則采用默認(rèn)執(zhí)行類型ExecutorType.SIMPLE。

(2)根據(jù)執(zhí)行器類型返回不同類型的執(zhí)行器(執(zhí)行器有三種显设,分別是 BatchExecutor框弛、SimpleExecutor和CachingExecutor,后面我們?cè)僭敿?xì)看看)捕捂。

(3)跟執(zhí)行器綁定攔截器插件(這里也是使用代理來(lái)實(shí)現(xiàn))瑟枫。

DefaultSqlSession到底是干什么的呢?

DefaultSqlSession實(shí)現(xiàn)了SqlSession接口指攒,里面有各種各樣的SQL執(zhí)行方法慷妙,主要用于SQL操作的對(duì)外接口,它會(huì)的調(diào)用執(zhí)行器來(lái)執(zhí)行實(shí)際的SQL語(yǔ)句允悦。

接下來(lái)我們看看SQL查詢是怎么進(jìn)行的

Java代碼?

UserInfo user = (UserInfo) session.selectOne("User.selectUser",?"1");

實(shí)際調(diào)用的是DefaultSqlSession類的selectOne方法膝擂,該方法代碼如下:

Java代碼?

public?Object selectOne(String statement, Object parameter) {??// Popular vote was to return null on 0 results and throw exception on too many.??List list = selectList(statement, parameter);??if?(list.size() ==?1) {??return?list.get(0);??}?else?if?(list.size() >?1) {??throw?new?TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: "?+ list.size());??}?else?{??return?null;??}??}

我們?cè)倏纯磗electList方法(實(shí)際上是調(diào)用該類的另一個(gè)selectList方法來(lái)實(shí)現(xiàn)的):

Java代碼?

public?List selectList(String statement, Object parameter) {??return?selectList(statement, parameter, RowBounds.DEFAULT);??}??public?List selectList(String statement, Object parameter, RowBounds rowBounds) {??try?{??MappedStatement ms = configuration.getMappedStatement(statement);??return?executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);??}?catch?(Exception e) {??throw?ExceptionFactory.wrapException("Error querying database. Cause: "?+ e, e);??}?finally?{??ErrorContext.instance().reset();??}??}

第二個(gè)selectList的執(zhí)行步驟如下:

(1)根據(jù)SQL的ID到配置信息中找對(duì)應(yīng)的MappedStatement,在之前配置被加載初始化的時(shí)候我們看到了系統(tǒng)會(huì)把配置文件中的SQL塊解析并放到一個(gè)MappedStatement里面澡屡,并將MappedStatement對(duì)象放到一個(gè)Map里面進(jìn)行存放猿挚,Map的key值是該SQL塊的ID。

(2)調(diào)用執(zhí)行器的query方法驶鹉,傳入MappedStatement對(duì)象绩蜻、SQL參數(shù)對(duì)象、范圍對(duì)象(此處為空)和結(jié)果處理方式室埋。

好了办绝,目前只剩下一個(gè)疑問(wèn)伊约,那就是執(zhí)行器到底怎么執(zhí)行SQL的呢?

上面我們知道了孕蝉,默認(rèn)情況下是采用SimpleExecutor執(zhí)行的屡律,我們看看這個(gè)類的doQuery方法:

Java代碼?

public?List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)?throws?SQLException {??Statement stmt =?null;??try?{??Configuration configuration = ms.getConfiguration();??StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler);??stmt = prepareStatement(handler);??return?handler.query(stmt, resultHandler);??}?finally?{??closeStatement(stmt);??}??}

doQuery方法的內(nèi)部執(zhí)行步驟:

(1) 獲取配置信息對(duì)象。

(2)通過(guò)配置對(duì)象獲取一個(gè)新的StatementHandler降淮,該類主要用來(lái)處理一次SQL操作超埋。

(3)預(yù)處理StatementHandler對(duì)象,得到Statement對(duì)象佳鳖。

(4)傳入Statement和結(jié)果處理對(duì)象霍殴,通過(guò)StatementHandler的query方法來(lái)執(zhí)行SQL,并對(duì)執(zhí)行結(jié)果進(jìn)行處理系吩。

我們看一下newStatementHandler到底做了什么来庭?

Java代碼?

public?StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {??StatementHandler statementHandler =?new?RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler);??statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);??return?statementHandler;??}

上面代碼的執(zhí)行步驟:

(1)根據(jù)相關(guān)的參數(shù)獲取對(duì)應(yīng)的StatementHandler對(duì)象。

(2)為StatementHandler對(duì)象綁定攔截器插件穿挨。

RoutingStatementHandler類的構(gòu)造方法RoutingStatementHandler如下:

Java代碼?

public?RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {??switch?(ms.getStatementType()) {??case?STATEMENT:??delegate =?new?SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler);??break;??case?PREPARED:??delegate =?new?PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler);??break;??case?CALLABLE:??delegate =?new?CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler);??break;??default:??throw?new?ExecutorException("Unknown statement type: "?+ ms.getStatementType());??}??}

根據(jù) MappedStatement對(duì)象的StatementType來(lái)創(chuàng)建不同的StatementHandler月弛,這個(gè)跟前面執(zhí)行器的方式類似。StatementType有STATEMENT科盛、PREPARED和CALLABLE三種類型帽衙,跟JDBC里面的Statement類型一一對(duì)應(yīng)。

我們看一下prepareStatement方法具體內(nèi)容:

Java代碼?

private?Statement prepareStatement(StatementHandler handler)?throws?SQLException {?Statement stmt;??Connection connection = transaction.getConnection();??//從連接中獲取Statement對(duì)象??stmt = handler.prepare(connection);??//處理預(yù)編譯的傳入?yún)?shù)??handler.parameterize(stmt);??return?stmt;??}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末土涝,一起剝皮案震驚了整個(gè)濱河市佛寿,隨后出現(xiàn)的幾起案子幌墓,更是在濱河造成了極大的恐慌但壮,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件常侣,死亡現(xiàn)場(chǎng)離奇詭異蜡饵,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)胳施,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門溯祸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人舞肆,你說(shuō)我怎么就攤上這事焦辅。” “怎么了椿胯?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵筷登,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我哩盲,道長(zhǎng)前方,這世上最難降的妖魔是什么狈醉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮惠险,結(jié)果婚禮上苗傅,老公的妹妹穿的比我還像新娘。我一直安慰自己班巩,他們只是感情好渣慕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著抱慌,像睡著了一般摇庙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上遥缕,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天卫袒,我揣著相機(jī)與錄音,去河邊找鬼单匣。 笑死夕凝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的户秤。 我是一名探鬼主播码秉,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鸡号!你這毒婦竟也來(lái)了转砖?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鲸伴,失蹤者是張志新(化名)和其女友劉穎府蔗,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體汞窗,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姓赤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仲吏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片不铆。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖裹唆,靈堂內(nèi)的尸體忽然破棺而出誓斥,到底是詐尸還是另有隱情,我是刑警寧澤许帐,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布劳坑,位于F島的核電站,受9級(jí)特大地震影響舞吭,放射性物質(zhì)發(fā)生泄漏泡垃。R本人自食惡果不足惜析珊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蔑穴。 院中可真熱鬧忠寻,春花似錦、人聲如沸存和。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捐腿。三九已至纵朋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茄袖,已是汗流浹背操软。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宪祥,地道東北人聂薪。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蝗羊,于是被迫代替她去往敵國(guó)和親藏澳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355