[我讀'不懂'源碼]:001因事務(wù)導(dǎo)致hibernate延遲加載出現(xiàn)no session異常

[TOC]

因事務(wù)導(dǎo)致hibernate延遲加載出現(xiàn)no session異常

文章來源: 臨窗旋墨的博客

本人對hibernate 的使用不是特別的熟悉,這里只是記錄一次幫助同事解決異常排查的過程.

項(xiàng)目中的spring版本為4.1.6

貼出的源碼的spring版本為5.1.9

1 項(xiàng)目技術(shù)框架

spring + springmvc + hibernate + freemarker

2 異常的產(chǎn)生現(xiàn)象

controller中直接調(diào)用serviceA中的方法A 頁面可正常渲染;

controller調(diào)用serviceB中的方法B, 方法B中調(diào)用serviceA中的方法A,渲染頁面的時(shí)候報(bào)異常:no session

3 問題追蹤

  1. 根據(jù)現(xiàn)象,最直觀的原因是session已經(jīng)關(guān)閉, 然后獲取懶加載對象的屬性的時(shí)候,沒有獲取到session;
  2. 直接調(diào)用無錯(cuò), 但是經(jīng)過另外一個(gè)service調(diào)用后就no session, 說明兩個(gè)session可能并不是一個(gè)session
3.1 確認(rèn)是否是一個(gè)session的調(diào)試:結(jié)果不是一個(gè)session;

本項(xiàng)目開啟了OpenSessionInViewFilter過濾器,

  1. 斷點(diǎn)跟蹤OpenSessionInViewFilter中的open的Session, 記錄sesson的hashcode-->code1;
  2. 進(jìn)入serviceB, 通過獲取sessionFactory.getCurrentSession(), 記錄session的hashcode, 等于code1, 表示此處使用的session是filter中打開的session;
  3. 從serviceB進(jìn)入serviceA,獲取當(dāng)前session,獲得的hashcode值與code1不一致;
3.2 為什么不是一個(gè)session: 因?yàn)椴辉谝粋€(gè)事物中
  1. 本項(xiàng)目的事務(wù)在配置文件中由切面配置;
  2. 由于命名方式的問題,
    1. 入口方法B不要求在事物中運(yùn)行( propagation="SUPPORTS")
    2. 方法A需要在一個(gè)事物中運(yùn)行((propagation="REQUIRED"))
    3. 導(dǎo)致進(jìn)入方法B后不需要事務(wù),但是B調(diào)用的A方法在事務(wù)中運(yùn)行

關(guān)于事務(wù)的傳播性,小伙伴可自行復(fù)習(xí);

3.3 解決問題:修改方法名,保證在一個(gè)事務(wù)中

修改方法名后, 保證兩個(gè)方法在一個(gè)事務(wù)中,再次驗(yàn)證, 兩者session一致, 延遲加載的屬性可正常加載, 在OpenSessionInViewFilter的finally代碼塊中亦可正常關(guān)閉;

4 再問:openSession和getCurrentSession的區(qū)別

  • 采用getCurrentSession()創(chuàng)建的session在commit或rollback時(shí)會自動(dòng)關(guān)閉州弟,
  • 采用openSession()創(chuàng)建的session必須手動(dòng)關(guān)閉
  • 采用getCurrentSession()創(chuàng)建的session會綁定到當(dāng)前線程中,
  • 采用openSession() 創(chuàng)建的session則不會.所以以上的session是一直都是一個(gè)session.

本問題中因?yàn)榉椒ˋ中session (getCurrentSession())在事務(wù)中運(yùn)行, 所以在commit之后被關(guān)閉掉了,造成延遲加載事務(wù)失敗;

5 三問: 為什么開啟新事物中的session和當(dāng)前線程中的session不一致

5.1 spring如何管理hibernate的session

查看配置文件中關(guān)于sessionFactory的配置為LocalSessionFactoryBean

LocalSessionFactoryBean` implements FactoryBean<SessionFactory>.......

SessionFactory.getCurrentSession源碼追蹤
SessionFactoryImpl#getCurrentSession
public Session getCurrentSession() throws HibernateException {
    if ( currentSessionContext == null ) {
        throw new HibernateException( "No CurrentSessionContext configured!" );
    }
    return currentSessionContext.currentSession();
}
SpringSessionContext#currentSession
    public Session currentSession() throws HibernateException {
        /*
        *以sessionFactory為key去當(dāng)前線程中獲取session(此處不再展開源碼), 可能是在 OpenSessionInViewFilter 中存入線程的, 參見 OpenSessionInViewFilter 的openSessIon代碼
        */
        Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
        if (value instanceof Session) {
            return (Session) value;
        }
        else if (value instanceof SessionHolder) {
            // hibernate事務(wù)管理器
            SessionHolder sessionHolder = (SessionHolder) value;
            Session session = sessionHolder.getSession();
            if (!sessionHolder.isSynchronizedWithTransaction() &&
                    TransactionSynchronizationManager.isSynchronizationActive()) {
                TransactionSynchronizationManager.registerSynchronization(
                        new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
                sessionHolder.setSynchronizedWithTransaction(true);
                // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
                // with FlushMode.MANUAL, which needs to allow flushing within the transaction.
                FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
                if (flushMode.equals(FlushMode.MANUAL) &&
                        !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                    session.setFlushMode(FlushMode.AUTO);
                    sessionHolder.setPreviousFlushMode(flushMode);
                }
            }
            return session;
        }
        else if (value instanceof EntityManagerHolder) {
            // JpaTransactionManager
            return ((EntityManagerHolder) value).getEntityManager().unwrap(Session.class);
        }

        if (this.transactionManager != null && this.jtaSessionContext != null) {
            try {
                if (this.transactionManager.getStatus() == Status.STATUS_ACTIVE) {
                    Session session = this.jtaSessionContext.currentSession();
                    if (TransactionSynchronizationManager.isSynchronizationActive()) {
                        TransactionSynchronizationManager.registerSynchronization(
                                new SpringFlushSynchronization(session));
                    }
                    return session;
                }
            }
            catch (SystemException ex) {
                throw new HibernateException("JTA TransactionManager found but status check failed", ex);
            }
        }

        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            Session session = this.sessionFactory.openSession();
            if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                session.setFlushMode(FlushMode.MANUAL);
            }
            SessionHolder sessionHolder = new SessionHolder(session);
            TransactionSynchronizationManager.registerSynchronization(
                    new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true));
            TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
            sessionHolder.setSynchronizedWithTransaction(true);
            return session;
        }
        else {
            throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");
        }
    }
對比OpenSessionInViewFilter#doFilterInternal ,可以發(fā)現(xiàn)兩者獲取hibernate 的session的邏輯相似;
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        SessionFactory sessionFactory = lookupSessionFactory(request);
        boolean participate = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        String key = getAlreadyFilteredAttributeName();

        if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
            // Do not modify the Session: just set the participate flag.
            participate = true;
        }
        else {
            boolean isFirstRequest = !isAsyncDispatch(request);
            if (isFirstRequest || !applySessionBindingInterceptor(asyncManager, key)) {
                logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
                Session session = openSession(sessionFactory);
                SessionHolder sessionHolder = new SessionHolder(session);
                TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);

                AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder);
                asyncManager.registerCallableInterceptor(key, interceptor);
                asyncManager.registerDeferredResultInterceptor(key, interceptor);
            }
        }

        try {
            filterChain.doFilter(request, response);
        }

        finally {
            if (!participate) {
                SessionHolder sessionHolder =
                        (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
                if (!isAsyncStarted(request)) {
                    logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
                    SessionFactoryUtils.closeSession(sessionHolder.getSession());
                }
            }
        }
spring是如何管理hibernate的事務(wù)的?HibernateTransactionManager

本項(xiàng)目配置的hibernate事務(wù)管理器為HibernateTransactionManager

HibernateTransactionManager extends AbstractPlatformTransactionManager......

其中獲取事務(wù)的源碼參見:AbstractPlatformTransactionManager#getTransaction
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
        Object transaction = doGetTransaction();

        // Cache debug flag to avoid repeated checks.
        boolean debugEnabled = logger.isDebugEnabled();

        if (definition == null) {
            // Use defaults if no transaction definition given.
            definition = new DefaultTransactionDefinition();
        }

        if (isExistingTransaction(transaction)) {
            // Existing transaction found -> check propagation behavior to find out how to behave.
            return handleExistingTransaction(definition, transaction, debugEnabled);
        }

        // Check definition settings for new transaction.
        if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
        }

        // No existing transaction found -> check propagation behavior to find out how to proceed.
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException(
                    "No existing transaction found for transaction marked with propagation 'mandatory'");
        }
        else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            SuspendedResourcesHolder suspendedResources = suspend(null);
            if (debugEnabled) {
                logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
            }
            try {
                boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
                DefaultTransactionStatus status = newTransactionStatus(
                        definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                doBegin(transaction, definition);
                prepareSynchronization(status, definition);
                return status;
            }
            catch (RuntimeException | Error ex) {
                resume(null, suspendedResources);
                throw ex;
            }
        }
        else {
            // Create "empty" transaction: no actual transaction, but potentially synchronization.
            if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
                logger.warn("Custom isolation level specified but no actual transaction initiated; " +
                        "isolation level will effectively be ignored: " + definition);
            }
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
        }
    }
其中dobegin進(jìn)入HibernateTransactionManager#doBegin
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;

        if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            throw new IllegalTransactionStateException(
                    "Pre-bound JDBC Connection found! HibernateTransactionManager does not support " +
                    "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
                    "It is recommended to use a single HibernateTransactionManager for all transactions " +
                    "on a single DataSource, no matter whether Hibernate or JDBC access.");
        }

        Session session = null;

        try {
            /*** 判斷是否 open一個(gè)newSession 見下文說明
            txObject.hasSessionHolder() 一般應(yīng)返回true,因?yàn)樵趂ilter中新建了
            txObject.getSessionHolder().isSynchronizedWithTransaction() 何時(shí)為true呢?
            在try代碼塊的末尾會設(shè)置其為true: txObject.getSessionHolder().setSynchronizedWithTransaction(true);
            有理由相信 可能是第1+n次進(jìn)入此dobegin方法時(shí)候,如果是txObject中的SessionHolder屬性為同一個(gè),則這個(gè)屬性為true.
            那么什么時(shí)候第1+n次進(jìn)入dobegin,卻攜帶了相同的txObject呢?
            參見:AbstractPlatformTransactionManager#
            DefaultTransactionStatus status = newTransactionStatus(
                        definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                doBegin(transaction, definition);
            
            *************************************/
            if (!txObject.hasSessionHolder() || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
                Interceptor entityInterceptor = getEntityInterceptor();
                Session newSession = (entityInterceptor != null ?
                        obtainSessionFactory().withOptions().interceptor(entityInterceptor).openSession() :
                        obtainSessionFactory().openSession());
                if (logger.isDebugEnabled()) {
                    logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
                }
                txObject.setSession(newSession);
            }

            session = txObject.getSessionHolder().getSession();

            boolean holdabilityNeeded = this.allowResultAccessAfterCompletion && !txObject.isNewSession();
            boolean isolationLevelNeeded = (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT);
            if (holdabilityNeeded || isolationLevelNeeded || definition.isReadOnly()) {
                if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
                    // We're allowed to change the transaction settings of the JDBC Connection.
                    if (logger.isDebugEnabled()) {
                        logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]");
                    }
                    Connection con = ((SessionImplementor) session).connection();
                    Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
                    txObject.setPreviousIsolationLevel(previousIsolationLevel);
                    if (this.allowResultAccessAfterCompletion && !txObject.isNewSession()) {
                        int currentHoldability = con.getHoldability();
                        if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
                            txObject.setPreviousHoldability(currentHoldability);
                            con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
                        }
                    }
                }
                else {
                    // Not allowed to change the transaction settings of the JDBC Connection.
                    if (isolationLevelNeeded) {
                        // We should set a specific isolation level but are not allowed to...
                        throw new InvalidIsolationLevelException(
                                "HibernateTransactionManager is not allowed to support custom isolation levels: " +
                                "make sure that its 'prepareConnection' flag is on (the default) and that the " +
                                "Hibernate connection release mode is set to 'on_close' (the default for JDBC).");
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]");
                    }
                }
            }

            if (definition.isReadOnly() && txObject.isNewSession()) {
                // Just set to MANUAL in case of a new Session for this transaction.
                session.setFlushMode(FlushMode.MANUAL);
                // As of 5.1, we're also setting Hibernate's read-only entity mode by default.
                session.setDefaultReadOnly(true);
            }

            if (!definition.isReadOnly() && !txObject.isNewSession()) {
                // We need AUTO or COMMIT for a non-read-only transaction.
                FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
                if (FlushMode.MANUAL.equals(flushMode)) {
                    session.setFlushMode(FlushMode.AUTO);
                    txObject.getSessionHolder().setPreviousFlushMode(flushMode);
                }
            }

            Transaction hibTx;

            // Register transaction timeout.
            int timeout = determineTimeout(definition);
            if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                // Use Hibernate's own transaction timeout mechanism on Hibernate 3.1+
                // Applies to all statements, also to inserts, updates and deletes!
                hibTx = session.getTransaction();
                hibTx.setTimeout(timeout);
                hibTx.begin();
            }
            else {
                // Open a plain Hibernate transaction without specified timeout.
                hibTx = session.beginTransaction();
            }

            // Add the Hibernate transaction to the session holder.
            txObject.getSessionHolder().setTransaction(hibTx);

            // Register the Hibernate Session's JDBC Connection for the DataSource, if set.
            if (getDataSource() != null) {
                SessionImplementor sessionImpl = (SessionImplementor) session;
                // The following needs to use a lambda expression instead of a method reference
                // for compatibility with Hibernate ORM <5.2 where connection() is defined on
                // SessionImplementor itself instead of on SharedSessionContractImplementor...
                ConnectionHolder conHolder = new ConnectionHolder(() -> sessionImpl.connection());
                if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                    conHolder.setTimeoutInSeconds(timeout);
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Exposing Hibernate transaction as JDBC [" + conHolder.getConnectionHandle() + "]");
                }
                TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
                txObject.setConnectionHolder(conHolder);
            }

            // Bind the session holder to the thread.
            if (txObject.isNewSessionHolder()) {
                TransactionSynchronizationManager.bindResource(obtainSessionFactory(), txObject.getSessionHolder());
            }
            txObject.getSessionHolder().setSynchronizedWithTransaction(true);
        }

        catch (Throwable ex) {
            if (txObject.isNewSession()) {
                try {
                    if (session != null && session.getTransaction().getStatus() == TransactionStatus.ACTIVE) {
                        session.getTransaction().rollback();
                    }
                }
                catch (Throwable ex2) {
                    logger.debug("Could not rollback Session after failed transaction begin", ex);
                }
                finally {
                    SessionFactoryUtils.closeSession(session);
                    txObject.setSessionHolder(null);
                }
            }
            throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
        }
    }

如上 :若 txObject.hasSessionHolder() || txObject.getSessionHolder().isSynchronizedWithTransaction() 返回ture則會新建session

  1. 其中txObject.hasSessionHolder() 一般應(yīng)返回true,因?yàn)槎嘣贠penSessionInViewFilter中新建了

  2. 那txObject.getSessionHolder().isSynchronizedWithTransaction() 何時(shí)為true呢?

  • 在try代碼塊的末尾會設(shè)置其為true: txObject.getSessionHolder().setSynchronizedWithTransaction(true);

  • txObject(HibernateTransactionObject)對象來自doGetTransaction()方法

    • txObject的sessionFactory屬性來自:(SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    • 可知在一個(gè)線程中確實(shí)是一個(gè)對象

HibernateTransactionManager#doGetTransaction
    protected Object doGetTransaction() {
        HibernateTransactionObject txObject = new HibernateTransactionObject();
        txObject.setSavepointAllowed(isNestedTransactionAllowed());
        //獲取 sessionFactory, 來自當(dāng)前HibernateTransactionManager實(shí)例的sessionFactory屬性
        SessionFactory sessionFactory = obtainSessionFactory();
        //獲取當(dāng)前線程綁定的sessionHolder,來自TransactionSynchronizationManager 的ThreadLocal<Map<Object, Object>> resources
        SessionHolder sessionHolder, =
                (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
        if (sessionHolder != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction");
            }
            txObject.setSessionHolder(sessionHolder);
        }
        else if (this.hibernateManagedSession) {
            try {
                Session session = sessionFactory.getCurrentSession();
                if (logger.isDebugEnabled()) {
                    logger.debug("Found Hibernate-managed Session [" + session + "] for Spring-managed transaction");
                }
                txObject.setExistingSession(session);
            }
            catch (HibernateException ex) {
                throw new DataAccessResourceFailureException(
                        "Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
            }
        }

        if (getDataSource() != null) {
            ConnectionHolder conHolder = (ConnectionHolder)
                    TransactionSynchronizationManager.getResource(getDataSource());
            txObject.setConnectionHolder(conHolder);
        }

        return txObject;
    }

追蹤代碼而知, 在新建事務(wù)的時(shí)候,若當(dāng)前線程之前已經(jīng)新建了事務(wù),且進(jìn)入了doBegin方法,則hibernate的事務(wù)對象HibernateTransactionObject中持有的SessionHolder對象的synchronizedWithTransaction屬性會被設(shè)置為true;在這種情況下,新事務(wù)下的session為新打開的session,造成和先前的session不一致的情況.

6 留下的疑問:

項(xiàng)目中的serviceA的A方法并沒有開啟事務(wù)(雖然進(jìn)入事務(wù)切面, 但是傳播級別是SUPPORTS)

根據(jù)源碼只會新建一個(gè)空的事務(wù)(Create "empty" transaction: no actual transaction, but potentially synchronization.),

而不會進(jìn)入doBegin,也就不會設(shè)置SessionHolder對象的synchronizedWithTransaction屬性為true,

那么serviceB的B方法開啟新事務(wù)的時(shí)候, 理應(yīng)不會打開新的session才對......................

由于項(xiàng)目在內(nèi)網(wǎng)環(huán)境,無法debug進(jìn)入源碼, 暫時(shí)未追蹤運(yùn)行時(shí)進(jìn)入事務(wù)源碼的代碼,且留個(gè)疑問, 有暇在外網(wǎng)搭建個(gè)環(huán)境的時(shí)候,再做補(bǔ)充吧.

或者有哪些大俠直接告訴我答案也是好的.

文章來源: 臨窗旋墨的博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末斯辰,一起剝皮案震驚了整個(gè)濱河市赦役,隨后出現(xiàn)的幾起案子艘绍,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件籽前,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)枝哄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門肄梨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挠锥,你說我怎么就攤上這事众羡。” “怎么了蓖租?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵粱侣,是天一觀的道長。 經(jīng)常有香客問我蓖宦,道長齐婴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任稠茂,我火速辦了婚禮柠偶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘睬关。我一直安慰自己诱担,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布共螺。 她就那樣靜靜地躺著该肴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪藐不。 梳的紋絲不亂的頭發(fā)上匀哄,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機(jī)與錄音雏蛮,去河邊找鬼涎嚼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛挑秉,可吹牛的內(nèi)容都是我干的法梯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼犀概,長吁一口氣:“原來是場噩夢啊……” “哼立哑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起姻灶,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤铛绰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后产喉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捂掰,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡敢会,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了这嚣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸥昏。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖姐帚,靈堂內(nèi)的尸體忽然破棺而出吏垮,到底是詐尸還是另有隱情,我是刑警寧澤罐旗,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布惫皱,位于F島的核電站,受9級特大地震影響尤莺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜生棍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一颤霎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涂滴,春花似錦友酱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搁料,卻和暖如春或详,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背郭计。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工霸琴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昭伸。 一個(gè)月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓梧乘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親庐杨。 傳聞我的和親對象是個(gè)殘疾皇子选调,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355