Mybatis中的一二級(jí)緩存

前言

Mybatis會(huì)為每次的查詢結(jié)果進(jìn)行緩存,緩存根據(jù)作用范圍劃分為一級(jí)况褪、二級(jí)緩存撕贞,基于Mybatis自帶的緩存機(jī)制,可以減少去數(shù)據(jù)庫(kù)執(zhí)行查詢的次數(shù)测垛,縮減開銷捏膨,以提升效率。本文將通過(guò)實(shí)驗(yàn)的方式食侮,來(lái)分析一級(jí)脊奋、二級(jí)緩存的作用范圍,以及緩存在何時(shí)被銷毀疙描。

配置日志

為了更好的觀察Mybatis下每條語(yǔ)句的執(zhí)行流程诚隙,首先配置為其配置日志功能,Mybatis支持多種主流的日志框架起胰,這里選擇LOG4J久又。首先在maven上下載LOG4Jjar包巫延,這里選擇的版本為

log4j-1.2.17.jar

將其加入項(xiàng)目目錄下,并設(shè)置添加為Library地消,然后創(chuàng)建一個(gè)名為log4j.properties的配置文件(注意名稱是約定好的炉峰,不可更改),添加如下配置脉执。

  • log4j.properties
# 全局日志配置

log4j.rootLogger=DEBUG, stdout
log4j.logger.org.mybatis=DEBUG
# MyBatis 日志配置
#log4j.logger.org.entity.PersonMapper=TRACE
# 控制臺(tái)輸出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

重要的是設(shè)置日志級(jí)別為DEBUG疼阔,該級(jí)別下可以輸出包括ERROR等所有級(jí)別的日志信息,輸出位置設(shè)置為標(biāo)準(zhǔn)輸出stdout半夷,即控制臺(tái)即可婆廊。
接下來(lái)為Mybatis設(shè)置所使用的日志框架, 將以下內(nèi)容添加到Mybatis的配置文件中

  • mybatis-config.xml
<configuration>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    ...
</configuration>
  • 在數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè)Person
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   | PRI | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
| age   | int(11)     | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

  • 插入如下數(shù)據(jù)
+----+------+------+
| id | name | age  |
+----+------+------+
|  1 | TOM  |   26 |
|  2 | Ben  |   41 |
  • 創(chuàng)建對(duì)應(yīng)的entity
public class Person {
    private int id;
    private String name;
    private int age;

    public Person(){};

    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }


    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

PersonMapper.xml中書寫一個(gè)簡(jiǎn)單的根據(jù)id查詢個(gè)人信息的sql

  • PersonMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE mapper
                PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="entity.PersonMapper">

    <select id="selectById" resultType="entity.Person" parameterType="int">
        SELECT *
        FROM Person
        WHERE id = #{id}
    </select>
</mapper>
  • PersonMapper.java

public interface PersonMapper {
    Person selectById(int id);
}

一切配置好就可以檢驗(yàn)下日志是否配置成功了。

  • Test.java
public class Test {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession();

        PersonMapper personMapper = session.getMapper(PersonMapper.class);
        Person person1 = personMapper.selectById(1);
        System.out.println(person1);
    }
}
  • 控制臺(tái)輸出
DEBUG [main] - Created connection 1337335626.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4fb61f4a]
DEBUG [main] - ==>  Preparing: SELECT * FROM Person WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Person{id=1, name='TOM', age=26}

日志成功跟蹤了整個(gè)流程巫橄,配置成功淘邻。

一級(jí)緩存

使用Mybatis時(shí),我們會(huì)通過(guò)sqlSessionFactory來(lái)獲得一個(gè)sqlSession實(shí)例湘换,該sqlSession實(shí)例象征著一次和Mysql Server的連接宾舅,我們?cè)谶@個(gè)sqlSession下將sql發(fā)送給Mysql Server并執(zhí)行它,Mybatis的一級(jí)緩存的作用范圍便是當(dāng)前的sqlSession下彩倚,現(xiàn)在讓我們?cè)偻粋€(gè)sqlSession下執(zhí)行兩次對(duì)id=1的記錄的查詢筹我。

  • Test.java
package entity;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Test {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession();


         PersonMapper personMapper = session.getMapper(PersonMapper.class);
         Person person1 = personMapper.selectById(1);
         System.out.println(person1);

         Person person2 = personMapper.selectById(2);
         System.out.println(person2);


    }
}

觀察日志輸出結(jié)果

DEBUG [main] - Created connection 1337335626.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4fb61f4a]
DEBUG [main] - ==>  Preparing: SELECT * FROM Person WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Person{id=1, name='TOM', age=26}
Person{id=1, name='TOM', age=26}

可以發(fā)現(xiàn),雖然執(zhí)行了兩次對(duì)id=1的查詢帆离,但是實(shí)際上只查詢了一次崎溃,因?yàn)樵诘谝淮尾樵兒螅?code>Mybatis幫我們對(duì)查詢結(jié)果進(jìn)行了緩存。
之前我們說(shuō)了一級(jí)緩存的作用范圍是同一個(gè)sqlSession下盯质,現(xiàn)在讓我們?cè)賰蓚€(gè)不同的session下執(zhí)行查詢工作。

public class Test {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession();


         PersonMapper personMapper = session.getMapper(PersonMapper.class);
         Person person1 = personMapper.selectById(1);
         System.out.println(person1);

//
        SqlSession session2 = sqlSessionFactory.openSession();
        PersonMapper personMapper2 = session2.getMapper(PersonMapper.class);

        Person person2 = personMapper2.selectById(1);
        System.out.println(person2);

    }
}

查看日志輸出結(jié)果

DEBUG [main] - Created connection 1337335626.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4fb61f4a]
DEBUG [main] - ==>  Preparing: SELECT * FROM Person WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Person{id=1, name='TOM', age=26}
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 168907708.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@a1153bc]
DEBUG [main] - ==>  Preparing: SELECT * FROM Person WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Person{id=1, name='TOM', age=26}

可以看到概而,因?yàn)椴辉谝粋€(gè)session下呼巷,所以緩存沒(méi)有派上用場(chǎng),因此查詢了兩次赎瑰。Mybatis中的一級(jí)緩存是默認(rèn)開啟的王悍,且采用了LRU算法,因此會(huì)淘汰掉最近最久未使用的查詢結(jié)果餐曼,除此之外压储,我們也可以手動(dòng)的執(zhí)行commit()語(yǔ)句來(lái)清空緩存。

  • commit()清空一級(jí)緩存

  • Test.java

public class Test {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession();


        PersonMapper personMapper = session.getMapper(PersonMapper.class);
        Person person1 = personMapper.selectById(1);
        System.out.println(person1);

        session.commit();
        Person person2 = personMapper.selectById(1);
        System.out.println(person2);

    }
}
  • 輸出日志
DEBUG [main] - Created connection 1337335626.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4fb61f4a]
DEBUG [main] - ==>  Preparing: SELECT * FROM Person WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Person{id=1, name='TOM', age=26}
DEBUG [main] - ==>  Preparing: SELECT * FROM Person WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Person{id=1, name='TOM', age=26}

可以看到源譬,當(dāng)調(diào)用session.commit()之后集惋,再執(zhí)行id=1的查詢語(yǔ)句時(shí),又去數(shù)據(jù)庫(kù)查詢了一次踩娘,說(shuō)明緩存被清空了刮刑。類似的執(zhí)行delete,update,insert語(yǔ)句時(shí)也會(huì)清空緩存,因?yàn)樗鼈儠?huì)隱式的調(diào)用commit語(yǔ)句。這所以這些操作會(huì)清空緩存的原因也很簡(jiǎn)單雷绢,因?yàn)檫@些語(yǔ)句都對(duì)數(shù)據(jù)庫(kù)表中的記錄進(jìn)行修改泛烙,如果不清空緩存,那么下一次操作就會(huì)拿到臟數(shù)據(jù)翘紊。

二級(jí)緩存

除了session范圍內(nèi)的一級(jí)緩存蔽氨,Mybatis還提供了二級(jí)緩存,與一級(jí)緩存默認(rèn)開啟不同帆疟,二級(jí)緩存需要手動(dòng)開啟鹉究,開啟的方式也很簡(jiǎn)單,只要在PersonMapper.xml 內(nèi)添加一行<cache/>標(biāo)簽即可鸯匹。

<?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE mapper
                PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="entity.PersonMapper">
    <cache/>
    <select id="selectById" resultType="entity.Person" parameterType="int">
        SELECT *
        FROM Person
        WHERE id = #{id}
    </select>
</mapper>

從這里我們可以初步猜測(cè)坊饶,二級(jí)緩存的作用范圍是在同一種mapper,也就是說(shuō)在同一個(gè)namespace下殴蓬,我們知道當(dāng)利用對(duì)PersonMapper這個(gè)接口生成動(dòng)態(tài)代理對(duì)象匿级,利用該對(duì)象進(jìn)行執(zhí)行具體的查詢操作時(shí),會(huì)傳入一個(gè)PersonMapper.class染厅。而這個(gè)PersonMapper.classnamespace="entity.PersonMapper"這個(gè) xml文件是一一對(duì)映的痘绎。

PersonMapper personMapper = session.getMapper(PersonMapper.class);

因此可以簡(jiǎn)單的說(shuō),只要是同一個(gè)PersonMapper.class生成的動(dòng)態(tài)代理對(duì)象肖粮,都會(huì)將查詢結(jié)果緩存到同一個(gè)空間中去孤页。
除了在Mapper.xml標(biāo)注使用緩存,我們還要在Mybatis的配置文件中開啟緩存功能

  • Mybatis-config.xml
<configuration>

    <settings>
        <setting name="logImpl" value="LOG4J"/>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    ...
</configuration>

在具體實(shí)驗(yàn)之前涩馆,我們首先要明白行施,一級(jí)緩存的作用范圍要小于二級(jí)緩存,因此在執(zhí)行具體的查詢時(shí)魂那,都會(huì)先去一級(jí)緩存(內(nèi)存中)進(jìn)行查找蛾号,一級(jí)緩存沒(méi)有找到的時(shí)候,才會(huì)去二級(jí)緩存查找涯雅。為此我們?cè)O(shè)計(jì)如下的測(cè)試方法

public class Test {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession();


        PersonMapper personMapper = session.getMapper(PersonMapper.class);
        Person person1 = personMapper.selectById(1);
        System.out.println(person1);

        //session.close();

        SqlSession session2 = sqlSessionFactory.openSession();

        PersonMapper personMapper2 = session2.getMapper(PersonMapper.class);
        Person person2 = personMapper2.selectById(1);
        System.out.println(person2);
    }
}

我們之前說(shuō)了只要是同一個(gè)Mapper.class生成的動(dòng)態(tài)代理對(duì)象鲜结,公用同一個(gè)緩存空間,因此利用2個(gè)不同的sqlSession生成了2個(gè)不同的動(dòng)態(tài)代理對(duì)象活逆,因此因?yàn)椴还蚕硪患?jí)緩存精刷,會(huì)去二級(jí)緩存中嘗試獲取結(jié)果,如果我們之前推論無(wú)誤的話,person2會(huì)直接從二級(jí)緩存中存取,而不會(huì)去數(shù)據(jù)庫(kù)查詢淑蔚。

  • 執(zhí)行結(jié)果
DEBUG [main] - Created connection 1388278453.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@52bf72b5]
DEBUG [main] - ==>  Preparing: SELECT * FROM Person WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Person{id=1, name='TOM', age=26}
DEBUG [main] - Cache Hit Ratio [entity.PersonMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 464887938.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1bb5a082]
DEBUG [main] - ==>  Preparing: SELECT * FROM Person WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Person{id=1, name='TOM', age=26}

根據(jù)日志可以發(fā)現(xiàn),依舊執(zhí)行了2次查詢工作误算,并沒(méi)有訪問(wèn)到二級(jí)緩存仰美,是我們的推論有問(wèn)題嗎?實(shí)際上我們要考慮一個(gè)重要的問(wèn)題儿礼,就是緩存結(jié)果的時(shí)機(jī)咖杂,在之前討論一級(jí)緩存的時(shí)候,很明顯是執(zhí)行完一次查詢蚊夫,就會(huì)把結(jié)果放進(jìn)緩存里诉字,而實(shí)際上在二級(jí)緩存里,只有一個(gè)sqlSession結(jié)束以后知纷,會(huì)把本次查詢的結(jié)果打包存進(jìn)緩存中壤圃,為什么要這么做?因?yàn)橐患?jí)緩存的結(jié)果是存在內(nèi)存里琅轧,而二級(jí)緩存實(shí)際上是將結(jié)果存在磁盤里(所以你的對(duì)象實(shí)體還需要支持序列化N樯),因此如果每次查詢完就存到磁盤里乍桂,會(huì)產(chǎn)生大量的隨機(jī) IO冲杀,開銷過(guò)大,因此會(huì)將每次查詢結(jié)果等本次sqlSession結(jié)束后再一次性放到二級(jí)緩存里睹酌。因此权谁,只要在一個(gè)sqlSession,手動(dòng)調(diào)用close()方法即可憋沿。

public class Test {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession();


        PersonMapper personMapper = session.getMapper(PersonMapper.class);
        Person person1 = personMapper.selectById(1);
        System.out.println(person1);

        session.close();

        SqlSession session2 = sqlSessionFactory.openSession();

        PersonMapper personMapper2 = session2.getMapper(PersonMapper.class);
        Person person2 = personMapper2.selectById(1);
        System.out.println(person2);
    }
}
  • 輸出結(jié)果
DEBUG [main] - Created connection 1388278453.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@52bf72b5]
DEBUG [main] - ==>  Preparing: SELECT * FROM Person WHERE id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Person{id=1, name='TOM', age=26}
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@52bf72b5]
DEBUG [main] - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@52bf72b5]
DEBUG [main] - Returned connection 1388278453 to pool.
DEBUG [main] - Cache Hit Ratio [entity.PersonMapper]: 0.5
Person{id=1, name='TOM', age=26}

可見現(xiàn)在二級(jí)緩存起作用了旺芽,解釋下 Cache Hit Ratio [entity.PersonMapper]: 0.5,即緩存命中率辐啄,第一次沒(méi)有命中采章,第二次命中了,因此1/2=0.5.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壶辜,一起剝皮案震驚了整個(gè)濱河市悯舟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌士复,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翩活,死亡現(xiàn)場(chǎng)離奇詭異阱洪,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)菠镇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門冗荸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人利耍,你說(shuō)我怎么就攤上這事蚌本】猓” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵程癌,是天一觀的道長(zhǎng)舷嗡。 經(jīng)常有香客問(wèn)我,道長(zhǎng)嵌莉,這世上最難降的妖魔是什么进萄? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮锐峭,結(jié)果婚禮上中鼠,老公的妹妹穿的比我還像新娘。我一直安慰自己沿癞,他們只是感情好援雇,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著椎扬,像睡著了一般惫搏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盗舰,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天晶府,我揣著相機(jī)與錄音,去河邊找鬼钻趋。 笑死川陆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蛮位。 我是一名探鬼主播较沪,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼失仁!你這毒婦竟也來(lái)了尸曼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤萄焦,失蹤者是張志新(化名)和其女友劉穎控轿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拂封,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茬射,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冒签。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片在抛。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖萧恕,靈堂內(nèi)的尸體忽然破棺而出刚梭,到底是詐尸還是另有隱情肠阱,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布朴读,位于F島的核電站屹徘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏磨德。R本人自食惡果不足惜缘回,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望典挑。 院中可真熱鬧酥宴,春花似錦、人聲如沸您觉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琳水。三九已至肆糕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間在孝,已是汗流浹背诚啃。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留私沮,地道東北人始赎。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像仔燕,于是被迫代替她去往敵國(guó)和親造垛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

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