Mybatis源碼剖析 -- 執(zhí)行SQL過程(傳統(tǒng)方式)

一羊异、簡(jiǎn)單介紹下 SqlSession

  • SqlSession 是?個(gè)接口,它有兩個(gè)實(shí)現(xiàn)類:DefaultSqlSession(默認(rèn))和
    SqlSessionManager(棄用捉撮,不做介紹)
  • SqlSession 是 MyBatis 中用于和數(shù)據(jù)庫(kù)交互的頂層類怕品,通常將它與 ThreadLocal 綁定,?個(gè)會(huì)話使用?個(gè) SqlSession巾遭,并且在使用完畢后需要 close
  • SqlSession 中的兩個(gè)最重要的參數(shù)肉康,configuration 與初始化時(shí)的相同闯估,Executor 為執(zhí)行器
    public class DefaultSqlSession implements SqlSession {
        private final Configuration configuration;
        private final Executor executor;
    }
    
  • Executor
    Executor 也是?個(gè)接口,他有三個(gè)常用的實(shí)現(xiàn)類:
    1. BatchExecutor(重用語(yǔ)句并執(zhí)行批量更新)
    2. ReuseExecutor(重用預(yù)處理語(yǔ)句 prepared statements)
    3. SimpleExecutor(普通的執(zhí)行器吼和,默認(rèn))

二涨薪、生產(chǎn) DefaultSqlSession 實(shí)例對(duì)象,設(shè)置了事務(wù)不自動(dòng)提交纹安,完成了 executor 對(duì)象的創(chuàng)建

  1. 入口
    // 生產(chǎn)了DefaultSqlsession實(shí)例對(duì)象   設(shè)置了事務(wù)不自動(dòng)提交  完成了 executor 對(duì)象的創(chuàng)建
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
  2. openSession()中傳遞三個(gè)參數(shù)尤辱,第一個(gè)為默認(rèn)的執(zhí)行器 SimpleExecutor,第二個(gè)表示事務(wù)隔離級(jí)別厢岂,最后一個(gè)參數(shù)表示是否自動(dòng)提交事務(wù)光督,所以一會(huì)生產(chǎn)出來的 SqlSession,它需要手動(dòng)提交事務(wù)
    @Override
    public SqlSession openSession() {
        //getDefaultExecutorType()傳遞的是SimpleExecutor
        // 第二個(gè)參數(shù)表示 當(dāng)前數(shù)據(jù)庫(kù)事務(wù)的隔離級(jí)別
        // 最后一個(gè)參數(shù)表示是否自動(dòng)提交事務(wù)
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }
    
    public ExecutorType getDefaultExecutorType() {
        return defaultExecutorType;
    }
    
    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
    
  3. openSessionFromDataSource() 方法
    //進(jìn)入openSessionFromDataSource塔粒。
    //ExecutorType 為Executor的類型结借,TransactionIsolationLevel為事務(wù)隔離級(jí)別,autoCommit是否開啟事務(wù)
    //openSession的多個(gè)重載方法可以指定獲得的SeqSession的Executor類型和事務(wù)的處理
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            // 獲得 Environment 對(duì)象
            final Environment environment = configuration.getEnvironment();
            // 創(chuàng)建 Transaction 對(duì)象
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 創(chuàng)建 Executor 對(duì)象
            final Executor executor = configuration.newExecutor(tx, execType);
            // 創(chuàng)建 DefaultSqlSession 對(duì)象
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            // 如果發(fā)生異常卒茬,則關(guān)閉 Transaction 對(duì)象
            closeTransaction(tx); // may have fetched a connection so lets call close()
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }
    
  4. 開始使用 SqlSession船老,根據(jù) statementId 來從 configuration 中的 map 集合中獲取到了指定的 MappedStatement 對(duì)象,并將查詢?nèi)蝿?wù)委派給了 executor 執(zhí)行器
    //進(jìn)入selectList方法圃酵,多個(gè)重載方法
    @Override
    public <E> List<E> selectList(String statement) {
        return this.selectList(statement, null);
    }
    
    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }
    
    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            // 獲得 MappedStatement 對(duì)象
            MappedStatement ms = configuration.getMappedStatement(statement);
            // 執(zhí)行查詢
            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();
        }
    }
    

三柳畔、Executor

  1. 先點(diǎn)進(jìn)executor.query()方法,發(fā)現(xiàn)真正的實(shí)現(xiàn)類是 BaseExecutor郭赐,他是 SimpleExecutor 的父類
    //此方法在SimpleExecutor的父類BaseExecutor中實(shí)現(xiàn)
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //根據(jù)傳入的參數(shù)動(dòng)態(tài)獲得SQL語(yǔ)句薪韩,最后返回用BoundSql對(duì)象表示
        BoundSql boundSql = ms.getBoundSql(parameter);
        //為本次查詢創(chuàng)建緩存的Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        // 查詢
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    
  2. 繼續(xù)點(diǎn)擊
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        // 已經(jīng)關(guān)閉,則拋出 ExecutorException 異常
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        // 清空本地緩存捌锭,如果 queryStack 為零俘陷,并且要求清空本地緩存。
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            // queryStack + 1
            queryStack++;
            // 從一級(jí)緩存中观谦,獲取查詢結(jié)果
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            // 獲取到拉盾,則進(jìn)行處理
            if (list != null) {
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            // 獲得不到,則從數(shù)據(jù)庫(kù)中查詢
            } else {
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            // queryStack - 1
            queryStack--;
        }
        if (queryStack == 0) {
            // 執(zhí)行延遲加載
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            // 清空 deferredLoads
            deferredLoads.clear();
            // 如果緩存級(jí)別是 LocalCacheScope.STATEMENT 豁状,則進(jìn)行清理
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482
                clearLocalCache();
            }
        }
        return list;
    }
    
  3. 查詢數(shù)據(jù)庫(kù)的方法
    // 從數(shù)據(jù)庫(kù)中讀取操作
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        // 在緩存中捉偏,添加占位對(duì)象。此處的占位符泻红,和延遲加載有關(guān)告私,可見 `DeferredLoad#canLoad()` 方法
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // 執(zhí)行讀操作
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // 從緩存中,移除占位對(duì)象
            localCache.removeObject(key);
        }
        // 添加到緩存中
        localCache.putObject(key, list);
        // 暫時(shí)忽略承桥,存儲(chǔ)過程相關(guān)
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }
    
  4. 將 JDBC 查詢代碼再次委派給 StatementHandler 去執(zhí)行
    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // 傳入?yún)?shù)創(chuàng)建StatementHanlder對(duì)象來執(zhí)行查詢
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 創(chuàng)建jdbc中的statement對(duì)象
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 執(zhí)行 StatementHandler  驻粟,進(jìn)行讀操作
            return handler.query(stmt, resultHandler);
        } finally {
            // 關(guān)閉 StatementHandler 對(duì)象
            closeStatement(stmt);
        }
    }
    
    // 初始化 StatementHandler 對(duì)象
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 獲得 Connection 對(duì)象
        Connection connection = getConnection(statementLog);
        // 創(chuàng)建 Statement 或 PrepareStatement 對(duì)象
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 設(shè)置 SQL 上的參數(shù),例如 PrepareStatement 對(duì)象上的占位符
        handler.parameterize(stmt);
        return stmt;
    }
    

四、StatementHandler

  • StatementHandler 對(duì)象主要完成兩個(gè)工作
    1. 對(duì)于 JDBC 的 PreparedStatement 類型的對(duì)象蜀撑,創(chuàng)建的過程中挤巡,我們使用的是SQL語(yǔ)句字符串會(huì)包含若干個(gè) "?" 占位符,我們其后再對(duì)占位符進(jìn)行設(shè)值酷麦。StatementHandler 通過parameterize(statement)方法對(duì) S tatement 進(jìn)行設(shè)值
    2. StatementHandler 通過 List query(Statement statement, ResultHandler resultHandler)方法來完成執(zhí)行 Statement矿卑,和將 Statement 對(duì)象返回的 resultSet 封裝成 List
  • 進(jìn)入handler.parameterize(stmt);
    @Override
    public void parameterize(Statement statement) throws SQLException {
        //使用ParameterHandler對(duì)象來完成對(duì)Statement的設(shè)值
        parameterHandler.setParameters((PreparedStatement) statement);
    }
    
    @Override
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        // 遍歷 ParameterMapping 數(shù)組
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                // 獲得 ParameterMapping 對(duì)象
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    // 獲得值
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    // 獲得 typeHandler、jdbcType 屬性
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    // 設(shè)置 ? 占位符的參數(shù)
                    try {
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (TypeException | SQLException e) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    }
                }
            }
        }
    }
    
  • 回到 SimpleExecutor 的doQuery()方法中沃饶,繼續(xù)進(jìn)入handler.query(stmt, resultHandler);
    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // 傳入?yún)?shù)創(chuàng)建StatementHanlder對(duì)象來執(zhí)行查詢
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // 創(chuàng)建jdbc中的statement對(duì)象
            stmt = prepareStatement(handler, ms.getStatementLog());
            // 執(zhí)行 StatementHandler  母廷,進(jìn)行讀操作
            return handler.query(stmt, resultHandler);
        } finally {
            // 關(guān)閉 StatementHandler 對(duì)象
            closeStatement(stmt);
        }
    }
    
    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        // 執(zhí)行查詢
        ps.execute();
        // 處理返回結(jié)果
        return resultSetHandler.handleResultSets(ps);
    }
    
  • 處理返回結(jié)果集
    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    
        // 多 ResultSet 的結(jié)果集合,每個(gè) ResultSet 對(duì)應(yīng)一個(gè) Object 對(duì)象糊肤。而實(shí)際上琴昆,每個(gè) Object 是 List<Object> 對(duì)象。
        // 在不考慮存儲(chǔ)過程的多 ResultSet 的情況馆揉,普通的查詢业舍,實(shí)際就一個(gè) ResultSet ,也就是說升酣,multipleResults 最多就一個(gè)元素舷暮。
        final List<Object> multipleResults = new ArrayList<>();
    
        int resultSetCount = 0;
        // 獲得首個(gè) ResultSet 對(duì)象,并封裝成 ResultSetWrapper 對(duì)象
        ResultSetWrapper rsw = getFirstResultSet(stmt);
    
        // 獲得 ResultMap 數(shù)組
        // 在不考慮存儲(chǔ)過程的多 ResultSet 的情況噩茄,普通的查詢下面,實(shí)際就一個(gè) ResultSet ,也就是說绩聘,resultMaps 就一個(gè)元素沥割。
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount); // 校驗(yàn)
        while (rsw != null && resultMapCount > resultSetCount) {
            // 獲得 ResultMap 對(duì)象
            ResultMap resultMap = resultMaps.get(resultSetCount);
            // 處理 ResultSet ,將結(jié)果添加到 multipleResults 中
            handleResultSet(rsw, resultMap, multipleResults, null);
            // 獲得下一個(gè) ResultSet 對(duì)象君纫,并封裝成 ResultSetWrapper 對(duì)象
            rsw = getNextResultSet(stmt);
            // 清理
            cleanUpAfterHandlingResultSet();
            // resultSetCount ++
            resultSetCount++;
        }
    
        // 因?yàn)?`mappedStatement.resultSets` 只在存儲(chǔ)過程中使用,本系列暫時(shí)不考慮芹彬,忽略即可
        String[] resultSets = mappedStatement.getResultSets();
        if (resultSets != null) {
            while (rsw != null && resultSetCount < resultSets.length) {
                ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
                if (parentMapping != null) {
                    String nestedResultMapId = parentMapping.getNestedResultMapId();
                    ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                    handleResultSet(rsw, resultMap, null, parentMapping);
                }
                rsw = getNextResultSet(stmt);
                cleanUpAfterHandlingResultSet();
                resultSetCount++;
            }
        }
    
        // 如果是 multipleResults 單元素蓄髓,則取首元素返回
        return collapseSingleResultList(multipleResults);
    }
    
    // 處理 ResultSet ,將結(jié)果添加到 multipleResults 中
    private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
        try {
            // 暫時(shí)忽略舒帮,因?yàn)橹挥写鎯?chǔ)過程的情況会喝,調(diào)用該方法,parentMapping 為非空
            if (parentMapping != null) {
                handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
            } else {
                // 如果沒有自定義的 resultHandler 玩郊,則創(chuàng)建默認(rèn)的 DefaultResultHandler 對(duì)象
                if (resultHandler == null) {
                    // 創(chuàng)建 DefaultResultHandler 對(duì)象
                    DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
                    // 處理 ResultSet 返回的每一行 Row
                    handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                    // 添加 defaultResultHandler 的處理的結(jié)果肢执,到 multipleResults 中
                    multipleResults.add(defaultResultHandler.getResultList());
                } else {
                    // 處理 ResultSet 返回的每一行 Row
                    handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
                }
            }
        } finally {
            // issue #228 (close resultsets)
            // 關(guān)閉 ResultSet 對(duì)象
            closeResultSet(rsw.getResultSet());
        }
    }
    
    // 處理 ResultSet 返回的每一行 Row
    public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
        // 處理嵌套映射的情況
        if (resultMap.hasNestedResultMaps()) {
            // 校驗(yàn)不要使用 RowBounds
            ensureNoRowBounds();
            // 校驗(yàn)不要使用自定義的 resultHandler
            checkResultHandler();
            // 處理嵌套映射的結(jié)果
            handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        // 處理簡(jiǎn)單映射的情況
        } else {
            // 處理簡(jiǎn)單映射的結(jié)果
            handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        }
    }
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市译红,隨后出現(xiàn)的幾起案子预茄,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耻陕,死亡現(xiàn)場(chǎng)離奇詭異拙徽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)诗宣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門膘怕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人召庞,你說我怎么就攤上這事岛心。” “怎么了篮灼?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵忘古,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我穿稳,道長(zhǎng)存皂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任逢艘,我火速辦了婚禮旦袋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘它改。我一直安慰自己疤孕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布央拖。 她就那樣靜靜地躺著祭阀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鲜戒。 梳的紋絲不亂的頭發(fā)上专控,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音遏餐,去河邊找鬼伦腐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛失都,可吹牛的內(nèi)容都是我干的柏蘑。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼粹庞,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼咳焚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起庞溜,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤革半,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體督惰,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡不傅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赏胚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片访娶。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖觉阅,靈堂內(nèi)的尸體忽然破棺而出崖疤,到底是詐尸還是另有隱情,我是刑警寧澤典勇,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布劫哼,位于F島的核電站,受9級(jí)特大地震影響割笙,放射性物質(zhì)發(fā)生泄漏权烧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一伤溉、第九天 我趴在偏房一處隱蔽的房頂上張望般码。 院中可真熱鬧,春花似錦乱顾、人聲如沸板祝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)券时。三九已至,卻和暖如春伏伯,著一層夾襖步出監(jiān)牢的瞬間橘洞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工说搅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炸枣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓蜓堕,卻偏偏與公主長(zhǎng)得像抛虏,于是被迫代替她去往敵國(guó)和親博其。 傳聞我的和親對(duì)象是個(gè)殘疾皇子套才,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

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