Hibernate 進階

本文包括:

1、Hibernate的持久化類

2、Hibernate 持久化對象的三個狀態(難點)

3、Hibernate 的一級緩存

4、Hibernate 中的事務與并發

5、Hibernate 的查詢方式(HQL:Hibernate Query Language)

1、Hibernate的持久化類

  • 什么是持久化類?

    持久化類:就是一個 Java 類(咱們編寫的 JavaBean),這個 Java 類與表建立了映射關系就可以成為是持久化類。

    簡單記:持久化類 = JavaBean + xxx.hbm.xml

  • 持久化類的編寫規則:

    1. 提供一個無參數 public訪問控制符的構造器 —— 底層需要進行反射。

    2. 提供一個標識屬性,映射數據表主鍵字段 —— 唯一標識 OID,數據庫中通過主鍵,Java 對象通過地址確定對象,持久化類通過唯一標識 OID 確定記錄。

    SQL 語句:cust_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客戶編號(主鍵)'

    JavaBean 代碼:private Long cust_id;

    結論:JavaBean 中的 cust_id 即為唯一標識 OID。

    1. 所有屬性提供 public 訪問控制符的 set 和 get 方法。

    2. 標識屬性應盡量使用基本數據類型的包裝類型(默認值為 null)。

  • 自然主鍵和代理主鍵的區別:

    • 自然主鍵:該主鍵是對象本身的一個屬性。例如:創建一個人員表,每個人都有一個身份證號(唯一的)。使用身份證號作為表的主鍵,稱為自然主鍵。(開發中不會使用這種方式

    • 代理主鍵:該主鍵不是對象本身的一個屬性。例如:創建一個人員表,為每個人員單獨創建一個字段,用這個字段作為主鍵,稱為代理主鍵。(開發中推薦使用這種方式

    簡單記:創建表時,新增一個毫無關系的字段,用該字段作為主鍵。

  • 主鍵的生成策略:

    1. increment:適用于 short,int,long 作為主鍵,沒有使用數據庫的自動增長機制。

      • Hibernate 中提供的一種增長機制,具體步驟如下:

        • 先進行查詢:select max(id) from user;

        • 再進行插入:獲得最大值+1作為新的記錄的主鍵。

      • 問題:不能在集群環境下或者有并發訪問的情況下使用。

      分析:在查詢時,有可能有兩個用戶幾乎同時得到相同的 id,再插入時就有可能主鍵沖突!

    2. identity:適用于 short,int,long 作為主鍵。但是必須使用在有自動增長機制的數據庫中,并且該數據庫采用的是數據庫底層的自動增長機制。

      • 底層使用的是數據庫的自動增長(auto_increment),像 Oracle 數據庫沒有自動增長機制,而MySql、DB2 等數據庫有自動增長的機制。
    3. sequence:適用于 short,int,long 作為主鍵,底層使用的是序列的增長方式。

      • Oracle 數據庫底層沒有自動增長,若想自動增長需要使用序列。
    4. uuid:適用于 char,varchar 類型的作為主鍵。

      • 使用隨機的字符串作為主鍵.
    5. native:本地策略。根據底層的數據庫不同,自動選擇適用于該種數據庫的生成策略(short,int,long)。

      • 如果底層使用的 MySQL 數據庫:相當于 identity.

      • 如果底層使用 Oracle 數據庫:相當于 sequence.

    6. assigned:主鍵的生成不用 Hibernate 管理了,必須手動設置主鍵。

    重點掌握:uuid(字符串)、native(數字)

2、Hibernate 持久化對象的三個狀態(難點)

  • 持久化對象的狀態

    1. Hibernate 的持久化類(前文已寫)

    2. Hibernate 的持久化類的狀態

      • Hibernate為了管理持久化類:將持久化類分成了三個狀態
        1. 瞬時態:Transient Object

          • 沒有持久化標識 OID, 沒有被納入到 Session 對象的管理(即沒有關系)
        2. 持久態:Persistent Object

          • 有持久化標識 OID,已經被納入到 Session 對象的管理.
        3. 托管態:Detached Object

          • 有持久化標識 OID,沒有被納入到 Session 對象的管理.
  • Hibernate 持久化對象的狀態的轉換

    1. 瞬時態 -- 沒有持久化標識 OID, 沒有被納入到 Session 對象的管理

      • 獲得瞬時態的對象

          User user = new User();
        

        創建了持久化類的對象,該對象還沒有 OID,也和 Session 對象無關,所以是瞬時態。

      • 瞬時態對象轉換持久態

          session.save(user); 或者 session.saveOrUpdate(user);
        

        user對象進入緩存,且自動生成了 OID,故為持久態。

      • 瞬時態對象轉換成托管態

          user.setId(1);
        

        手動設置了 OID,但沒有和 Session 對象發生關系,故為托管態。

    2. 持久態 -- 有持久化標識OID,已經被納入到Session對象的管理

      • 獲得持久態的對象

          get()/load();
        
      • 持久態轉換成瞬時態對象

          delete();  --- 比較有爭議的,進入特殊的狀態(刪除態:Hibernate中不建議使用的)
        
      • 持久態對象轉成脫管態對象

          session的close()/evict()/clear();
        

        Session 對象被銷毀,所以持久化類的對象沒有被 session 管理,所以為托管態。

    3. 脫管態 -- 有持久化標識OID,沒有被納入到Session對象的管理

      • 獲得托管態對象:不建議直接獲得脫管態的對象.

          User user = new User();
          user.setId(1);
        
      • 脫管態對象轉換成持久態對象

          update();/saveOrUpdate()/lock();
        
      • 脫管態對象轉換成瞬時態對象

          user.setId(null);
        
    4. 注意:持久態對象有自動更新數據庫的能力!!!

      • 測試代碼:

          /**
           * 持久態的對象有自動更新數據庫的能力
           * session的一級緩存!!
           */
          @Test
          public void run1(){
              Session session = HibernateUtils.getSession();
              Transaction tr = session.beginTransaction();
              
              // 獲取到持久態的對象
              User user = session.get(User.class,1);
              // user是持久態,有自動更新數據庫的能力
              System.out.println(user.getName());
              
              // 重新設置新的名稱
              user.setName("隔離老王");
              
              // 正常編寫代碼
              // session.update(user);
              
              tr.commit();
              session.close();
          }
        
      • 執行后,控制臺輸出:

          Hibernate: 
              select
                  user0_.id as id1_1_0_,
                  user0_.version as version2_1_0_,
                  user0_.name as name3_1_0_,
                  user0_.age as age4_1_0_ 
              from
                  t_user user0_ 
              where
                  user0_.id=?
          小風
          Hibernate: 
              update
                  t_user 
              set
                  version=?,
                  name=?,
                  age=? 
              where
                  id=? 
                  and version=?
        
      • 從中可以發現,在測試代碼中我們把 session.update(user); 注釋掉了,但是在控制臺中發現仍有 update 語句,這就是持久態有自動更新數據庫的能力,具體原因是 Session 對象的一級緩存(見下節)。

  1. 三個狀態之間的轉換圖解:

3、Hibernate 的一級緩存

  • Session 對象的一級緩存

    1. 什么是緩存?

      • 其實就是一塊內存空間,將數據源(數據庫或者文件)中的數據存放到緩存中。再次獲取的時候,直接從緩存中獲取,可以提升程序的性能!
    2. Hibernate 框架提供了兩種緩存

      • 一級緩存 -- 自帶的不可卸載的。一級緩存的生命周期與 session 一致,一級緩存稱為 session 級別的緩存.
      • 二級緩存 -- 默認沒有開啟,需要手動配置才可以使用的。二級緩存可以在多個 session 中共享數據,二級緩存稱為是 sessionFactory 級別的緩存.
    3. Session 對象的緩存概述

      • Session 接口中,有一系列的 java 的集合,這些 java 集合構成了 Session 級別的緩存(一級緩存)。將對象存入到一級緩存中,session 沒有結束生命周期,那么對象在 session 中存放著。
      • 內存中包含 Session 實例 --> Session 的緩存(一些集合) --> 集合中包含的是緩存對象!
    4. 證明一級緩存的存在,編寫查詢的代碼即可證明

      • 在同一個 Session 對象中兩次查詢,可以證明使用了緩存。
      • 測試代碼:

          @Test
          public void run3(){
              Session session = HibernateUtils.getSession();
              Transaction tr = session.beginTransaction();
              
              // 最簡單的證明,查詢兩次
              User user1 = session.get(User.class, 1);
              System.out.println(user1.getName());
              
              User user2 = session.get(User.class, 1);
              System.out.println(user2.getName());
              
              tr.commit();
              session.close();
          }
        
      • 控制臺只輸出一次如下信息:

          Hibernate: 
              insert 
              into
                  t_user
                  (version, name, age) 
              values
                  (?, ?, ?)
        
      • 進一步分析,若采用斷點的方式 debug,發現在執行 User user2 = session.get(User.class, 1); 時,控制臺不輸出任何信息,所以證明了一級緩存的存在。

    5. Hibernate 框架是如何做到數據發生變化時進行同步操作的呢?

      • 實驗步驟: 使用 get 方法查詢 User 對象,然后設置 User 對象的一個屬性,注意:沒有做 update 操作。最后發現,數據庫中的記錄也改變了。
      • 原因:利用快照機制來完成的(SnapShot),且該特性正好和持久態擁有自動更新數據庫能力相符合。
      • 快照機制:


  • 控制 Session 的一級緩存(了解)

    • 學習Session接口中與一級緩存相關的方法
      • Session.clear() -- 清空緩存。

        • 測試代碼:

            /**
             * Session.clear()  -- 清空緩存。
             */
            @Test
            public void run5(){
                Session session = HibernateUtils.getSession();
                Transaction tr = session.beginTransaction();
                
                // 最簡單的證明,查詢兩次
                User user1 = session.get(User.class, 1);
                System.out.println(user1.getName());
                
                // 清空緩存
                session.clear();
                
                User user2 = session.get(User.class, 1);
                System.out.println(user2.getName());
                
                tr.commit();
                session.close();
            }
          
        • 控制臺輸出:

            Hibernate: 
                select
                    user0_.id as id1_1_0_,
                    user0_.version as version2_1_0_,
                    user0_.name as name3_1_0_,
                    user0_.age as age4_1_0_ 
                from
                    t_user user0_ 
                where
                    user0_.id=?
            隔離老王
            Hibernate: 
                select
                    user0_.id as id1_1_0_,
                    user0_.version as version2_1_0_,
                    user0_.name as name3_1_0_,
                    user0_.age as age4_1_0_ 
                from
                    t_user user0_ 
                where
                    user0_.id=?
            隔離老王
          
        • 由此可見,clear方法可以清除緩存。

      • Session.evict(Object entity) -- 從一級緩存中清除指定的實體對象。

        • 若把上面代碼中的 session.clear(); 改為 session.evict(user1);,則控制臺輸出仍如上。
      • Session.flush() -- 刷出緩存

        • 在一般的快照機制中,是在事務提交時(tr.commit();),比較緩存與快照的不同,然后 Hibernate 框架自動執行 update SQL 語句,再更新數據庫。

        • 而如果調用 flush 方法,則在執行該方法時就比較緩存與快照的不同,然后 Hibernate 框架自動執行 update SQL 語句,最后在事務提交時更新數據庫。

        上面兩點的區別本人經過斷點調試一一驗證,以保證其正確性。

4、Hibernate 中的事務與并發

  • 事務相關的概念

    1. 什么是事務

      • 事務就是邏輯上的一組操作,組成事務的各個執行單元,操作要么全都成功,要么全都失敗.
      • 轉賬的例子:冠希給美美轉錢,扣錢,加錢。兩個操作組成了一個事情!
    2. 事務的特性

      • 原子性 -- 事務不可分割.
      • 一致性 -- 事務執行的前后數據的完整性保持一致.
      • 隔離性 -- 一個事務執行的過程中,不應該受到其他的事務的干擾.
      • 持久性 -- 事務一旦提交,數據就永久保持到數據庫中.
    3. 如果不考慮隔離性:引發一些讀的問題

      • 臟讀 -- 一個事務讀到了另一個事務未提交的數據(數據庫隔離中最重要的問題)
      • 不可重復讀 -- 一個事務讀到了另一個事務已經提交的 update 數據,導致多次查詢結果不一致.
      • 虛讀 -- 一個事務讀到了另一個事務已經提交的 insert 數據,導致多次查詢結構不一致.
    4. 通過設置數據庫的隔離級別來解決上述讀的問題

      • 未提交讀:以上的讀的問題都有可能發生.
      • 已提交讀:避免臟讀,但是不可重復讀,虛讀都有可能發生.
      • 可重復讀:避免臟讀,不可重復讀.但是虛讀是有可能發生.
      • 串行化:以上讀的情況都可以避免.
    5. 如果想在Hibernate的框架中來設置隔離級別,需要在 hibernate.cfg.xml 的配置文件中通過標簽來配置

      • 通過:hibernate.connection.isolation = 4 來配置
      • 取值:
        • 1—Read uncommitted isolation
        • 2—Read committed isolation
        • 4—Repeatable read isolation
        • 8—Serializable isolation
  • 丟失更新的問題

    1. 如果不考慮隔離性,也會產生寫入數據的問題,這一類的問題叫丟失更新的問題。

    2. 例如:兩個事務同時對某一條記錄做修改,就會引發丟失更新的問題。

      • A 事務和 B 事務同時獲取到一條數據,同時再做修改
      • 如果 A 事務修改完成后,提交了事務
      • B 事務修改完成后,不管是提交還是回滾,如果不做處理,都會對數據產生影響


    3. 解決方案有兩種

      • 悲觀鎖

        • 采用的是數據庫提供的一種鎖機制,如果采用做了這種機制,在SQL語句的后面添加 for update 子句

          • 當A事務在操作該條記錄時,會把該條記錄鎖起來,其他事務是不能操作這條記錄的。

          • 只有當A事務提交后,鎖釋放了,其他事務才能操作該條記錄

      • 樂觀鎖

        • 使用的不是數據庫鎖機制,而是采用版本號的機制來解決的。給表結構添加一個字段 version=0,默認值是0

          • 當 A 事務在操作完該條記錄,提交事務時,會先檢查版本號,如果發生版本號的值相同時,才可以提交事務。同時會更新版本號 version=1.

          • 當 B 事務操作完該條記錄時,提交事務時,會先檢查版本號,如果發現版本不同時,程序會出現錯誤。

    4. 使用 Hibernate 框架解決丟失更新的問題

      • 悲觀鎖(效率較低,不常見)

        • 使用 session.get(Customer.class, 1,LockMode.UPGRADE); 方法
      • 樂觀鎖

        • 在對應的 JavaBean 中添加一個屬性,名稱可以是任意的。例如:private Integer version; 提供 get 和 set 方法

        • 在映射的配置文件中,提供<version name="version"/>標簽即可。

    附上本人以前學習的筆記,從 JDBC 角度分析事務、隔離級別、丟失更新問題:http://www.lxweimin.com/p/aacde54542b5

  • 綁定本地的 Session

    1. 在上文所附的文章里,講解了 JavaWEB 的事務,需要在業務層使用 Connection 來開啟事務。

      • 一種是通過參數的方式,一層一層的傳遞下去
      • 另一種是把 Connection 綁定到 ThreadLocal 對象中,因為 ThreadLocal 對于內通過 map 存儲了 key = 當前線程, value= Connection對象

      ThreadLocal 兩個重要方法

      • get
        public T get() 返回此線程局部變量的當前線程副本中的值。如果變量沒有用于當前線程的值,則先將其初始化為調用 initialValue() 方法返回的值。
        返回:
        此線程局部變量的當前線程的值
      • set
        public void set(T value) 將此線程局部變量的當前線程副本中的值設置為指定值。大部分子類不需要重寫此方法,它們只依靠 initialValue() 方法來設置線程局部變量的值。
        參數:
        value - 存儲在此線程局部變量的當前線程副本中的值。
    2. 在 Hibernate 框架中,使用 session 對象開啟事務,而 session 對象存在于業務層中,所以需要把 session 對象傳遞到持久層,在持久層使用 session 對象操作數數據對象,框架提供了 ThreadLocal 的方式:

      • 首先需要在 hibernate.cfg.xml 的配置文件中提供配置

          <property name="hibernate.current_session_context_class">thread</property>
        
      • 然后重寫 HibernateUtil 的工具類,使用 SessionFactory 的 getCurrentSession( )方法,獲取當前的 Session 對象。并且該 Session 對象不用手動關閉,線程結束了,會自動關閉。

        • HibernateUtil 工具類:

            package com.itheima.utils;
            
            import org.hibernate.Session;
            import org.hibernate.SessionFactory;
            import org.hibernate.cfg.Configuration;
            
            /**
             * Hibernate框架的工具類
             * @author Administrator
             */
            public class HibernateUtils {
                
                // ctrl + shift + x
                private static final Configuration CONFIG;
                private static final SessionFactory FACTORY;
                
                // 編寫靜態代碼塊
                static{
                    // 加載XML的配置文件
                    CONFIG = new Configuration().configure();
                    // 構造工廠
                    FACTORY = CONFIG.buildSessionFactory();
                }
                
                /**
                 * 從工廠中獲取Session對象
                 * @return
                 */
                public static Session getSession(){
                    return FACTORY.openSession();
                }
                
                /**
                 * // 從ThreadLocal類中獲取到session的對象
                 * @return
                 */
                public static Session getCurrentSession(){
                    return FACTORY.getCurrentSession();
                }
                
            }
          
        • UserService 業務層:

            package com.itheima.service;
            
            import org.hibernate.Session;
            import org.hibernate.Transaction;
            
            import com.itheima.dao.UserDao;
            import com.itheima.domain.User;
            import com.itheima.utils.HibernateUtils;
            
            public class UserService {
                
                public void save(User u1,User u2){
                    UserDao dao = new UserDao();
                    // 獲取session
                    Session session = HibernateUtils.getCurrentSession();
                    // 開啟事務
                    Transaction tr = session.beginTransaction();
                    try {
                        dao.save1(u1);
                        int a = 10/0;
                        dao.save2(u2);
                        // 提交事務
                        tr.commit();
                    } catch (Exception e) {
                        e.printStackTrace();
                        // 出現問題:回滾事務
                        tr.rollback();
                    }finally{
                        // 自己釋放資源,現在,session不用關閉,線程結束了,自動關閉的!!
                    }
                }
            }
          
        • UserDao 持久層:

            package com.itheima.dao;
            
            import org.hibernate.Session;
            
            import com.itheima.domain.User;
            import com.itheima.utils.HibernateUtils;
            
            public class UserDao {
                
                public void save1(User u1){
                    Session session = HibernateUtils.getCurrentSession();
                    session.save(u1);
                    //不用寫 session.close; 
                }
                
                public void save2(User u2){
                    Session session = HibernateUtils.getCurrentSession();
                    session.save(u2);
                }
            
            }
          
      • 注意:想使用 getCurrentSession() 方法,必須要先配置才能使用。

5、Hibernate 的查詢方式(HQL:Hibernate Query Language)

  • Query 查詢接口

    • 具體的查詢代碼如下:

        // 1.查詢所有記錄
        /*Query query = session.createQuery("from Customer");
        List<Customer> list = query.list();
        System.out.println(list);*/
        
        // 2.條件查詢(? 從0開始)
        /*Query query = session.createQuery("from Customer where name = ?");
        query.setString(0, "李健");
        List<Customer> list = query.list();
        System.out.println(list);*/
        
        // 3.條件查詢(設置別名)
        /*Query query = session.createQuery("from Customer where name = :aaa and age = :bbb");
        query.setString("aaa", "李健");
        query.setInteger("bbb", 38);
        List<Customer> list = query.list();
        System.out.println(list);*/
      
        // 4.模糊查詢(注意 % 要寫在 setString 里面)
        /*Query query = session.createQuery("from User where name like ?");
        query.setString(0, "%老%");
        List<Customer> list = query.list();
        System.out.println(list);*/
      

      在 JDBC 中,記錄從1開始!
      在 HQL 中,記錄從0開始!

  • Criteria 查詢接口(做條件查詢非常合適)

    • 完全面向對象,代碼中基本不會體現 SQL 語言的特點

    • Criterion 是 Hibernate 提供的條件查詢的對象,如果想傳入條件,可以使用工具類 Restrictions ,在其內部有許多靜態方法可以用來描述條件

    • 具體的查詢代碼如下

        // 1.查詢所有記錄
        /*Criteria criteria = session.createCriteria(Customer.class);
        List<Customer> list = criteria.list();
        System.out.println(list);*/
        
        // 2.條件查詢(查詢 name 字段為李健的記錄)
        /*Criteria criteria = session.createCriteria(Customer.class);
        criteria.add(Restrictions.eq("name", "李健"));
        List<Customer> list = criteria.list();
        System.out.println(list);*/
        
        // 3.條件查詢(查詢 name 字段為李健、age 字段為38的記錄)
        /*Criteria criteria = session.createCriteria(Customer.class);
        criteria.add(Restrictions.eq("name", "李健"));
        criteria.add(Restrictions.eq("age", 38));
        List<Customer> list = criteria.list();
        System.out.println(list);*/
      
        // Restrictions 提供了許多靜態方法來描述條件
        criteria.add(Restrictions.gt("age", 18)); // gt:大于
        criteria.add(Restrictions.like("name", "%小%")); // like:模糊查詢
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容