Mybatis框架

什么是框架

框架(Framework)是整個或部分系統的可重用設計,表現為一組抽象構件及構件實例間交互的方法;另一種
定義認為,框架是可被應用開發者定制的應用骨架。前者是從應用方面而后者是從目的方面給出的定義。
簡而言之,框架其實就是某種應用的半成品,就是一組組件,供你選用完成你自己的系統。簡單說就是使用別
人搭好的舞臺,你來做表演。而且,框架一般是成熟的,不斷升級的軟件。

框架要解決的問題

框架要解決的最重要的一個問題是技術整合的問題,在 J2EE 的 框架中,有著各種各樣的技術,不同的
軟件企業需要從 J2EE 中選擇不同的技術,這就使得軟件企業最終的應用依賴于這些技術,技術自身的復雜性和技
術的風險性將會直接對應用造成沖擊。而應用是軟件企業的核心,是競爭力的關鍵所在,因此應該將應用自身的設
計和具體的實現技術解耦。這樣,軟件企業的研發將集中在應用的設計上,而不是具體的技術實現,技術實現是應
用的底層支撐,它不應該直接對應用產生影響。

框架一般處在低層應用平臺(如 J2EE)和高層業務邏輯之間的中間層。

軟件開發的分層重要性

框架的重要性在于它實現了部分功能,并且能夠很好的將低層應用平臺和高層業務邏輯進行了緩和。為了實現
軟件工程中的“高內聚、低耦合”。把問題劃分開來各個

MyBatis 框架概述

mybatis 是一個優秀的基于 java 的持久層框架,它內部封裝了 jdbc,使開發者只需要關注 sql 語句本身,
而不需要花費精力去處理加載驅動、創建連接、創建 statement 等繁雜的過程。

mybatis 通過 xml 或注解的方式將要執行的各種 statement 配置起來,并通過 java 對象和 statement 中
sql 的動態參數進行映射生成最終執行的 sql 語句,最后由 mybatis 框架執行 sql 并將結果映射為 java 對象并
返回。

采用 ORM 思想解決了實體和數據庫映射的問題,對 jdbc 進行了封裝,屏蔽了 jdbc api 底層訪問細節,使我
們不用與 jdbc api 打交道,就可以完成對數據庫的持久化操作。

原始dbc編程的回顧

public static void main(String[] args) {
   Connection connection = null;
   PreparedStatement preparedStatement = null;
   ResultSet resultSet = null;
   try {
   //加載數據庫驅動
   Class.forName("com.mysql.jdbc.Driver");
   //通過驅動管理類獲取數據庫鏈接
   connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "root");
   //定義 sql 語句 ?表示占位符
   String sql = "select * from user where username = ?";

   //獲取預處理 statement
   preparedStatement = connection.prepareStatement(sql);
   //設置參數,第一個參數為 sql 語句中參數的序號(從 1 開始),第二個參數為設置的參數值
   preparedStatement.setString(1, "王五");
   //向數據庫發出 sql 執行查詢,查詢出結果集
   resultSet = preparedStatement.executeQuery();
   //遍歷查詢結果集
   while(resultSet.next()){
    System.out.println(resultSet.getString("id")+"
    "+resultSet.getString("username"));
   }
   } catch (Exception e) {
   e.printStackTrace();
   }finally{
   //釋放資源
   if(resultSet!=null){
   try {
   resultSet.close();
   } catch (SQLException e) {
   e.printStackTrace();
   } }
   if(preparedStatement!=null){
   try {
   preparedStatement.close();
   } catch (SQLException e) {
   e.printStackTrace();
   } }
   if(connection!=null){
   try {
   connection.close();
   } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();

   }
   } } }

jdbc 問題分析

  • 數據庫鏈接創建、釋放頻繁造成系統資源浪費從而影響系統性能,如果使用數據庫鏈接池可解決此問題。
  • Sql 語句在代碼中硬編碼,造成代碼不易維護,實際應用 sql 變化的可能較大,sql 變動需要改變 java
    代碼。
  • 使用 preparedStatement 向占有位符號傳參數存在硬編碼,因為 sql 語句的 where 條件不一定,可能
    多也可能少,修改 sql 還要修改代碼,系統不易維護。
  • 對結果集解析存在硬編碼(查詢列名),sql 變化導致解析代碼變化,系統不易維護,如果能將數據庫記
    錄封裝成 pojo 對象解析比較方便

搭建 Mybatis 開發環境

創建 maven 工程

創建 mybatis01 的工程,工程信息如下:
Groupid:com.itheima
ArtifactId:mybatis01
Packing:jar

添加 Mybatis3.4.5 的坐標

  <dependencies>
     <dependency>
         <groupId>org.mybatis</groupId>
         <artifactId>mybatis</artifactId>
         <version>3.4.5</version>
     </dependency>
     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.10</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>5.1.6</version>
         <scope>runtime</scope>
     </dependency>
     <dependency>
         <groupId>log4j</groupId>
         <artifactId>log4j</artifactId>
         <version>1.2.12</version>
     </dependency>
  </dependencies>

編寫 User 實體類

/**
* 
* <p>Title: User</p>
* <p>Description: 用戶的實體類</p>
*/
public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    
    public Integer getId() {
    return id; }
    
    public void setId(Integer id) {
    this.id = id; }
    
    public String getUsername() {
    return username; }
    
    public void setUsername(String username) {
    this.username = username; }
    
    public Date getBirthday() {
    return birthday; }
    
    public void setBirthday(Date birthday) {
    this.birthday = birthday; }
    
    public String getSex() {
    return sex; }
    
    public void setSex(String sex) {
    this.sex = sex; }
    
    public String getAddress() {
    return address; }
    
    public void setAddress(String address) {
    this.address = address; }
    
    
}

持久層接口 IUserDao

IUserDao 接口就是我們的持久層接口(也可以寫成 UserDao 或者 UserMapper)

public interface IUserDao {
/**
* 查詢所有用戶
* @return
*/
List<User> findAll();
}

持久層接口的映射文件 IUserDao.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="com.itheima.dao.IUserDao">
    <!-- 配置查詢所有操作 --> 
    <select id="findAll" resultType="com.itheima.domain.User">
        select * from user
    </select>

</mapper>

要求:
創建位置:必須和持久層接口在相同的包中。
名稱:必須以持久層接口名稱命名文件名,擴展名是.xml

微信截圖_20210303092153.png

SqlMapConfig.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration  PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
 "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration>
<!-- 配置 mybatis 的環境 --> 
<environments default="mysql">
    <!-- 配置 mysql 的環境 --> 
    <environment id="mysql">
    <!-- 配置事務的類型 --> 
    <transactionManager type="JDBC"></transactionManager>
    <!-- 配置連接數據庫的信息:用的是數據源(連接池) --> 
    <dataSource type="POOLED"> 
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </dataSource>
    </environment>
</environments>
<!-- 告知 mybatis 映射配置的位置 --> 
<mappers> 
    <mapper resource="com/itheima/dao/IUserDao.xml"/>
</mappers>
</configuration>

編寫測試類

public class MybatisTest {

    public static void main(String[] args)throws Exception {

        //1.讀取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.創建 SqlSessionFactory 的構建者對象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3.使用構建者創建工廠對象 SqlSessionFactory
        SqlSessionFactory factory = builder.build(in);
        //4.使用 SqlSessionFactory 生產 SqlSession 對象
        SqlSession session = factory.openSession();
        //5.使用 SqlSession 創建 dao 接口的代理對象
        IUserDao userDao = session.getMapper(IUserDao.class);
        //6.使用代理對象執行查詢所有方法
        List<User> users = userDao.findAll();
        for(User user : users) {
        System.out.println(user);
        }
        //7.釋放資源
        session.close();
        in.close();
    } 
}

基于代理 Dao 實現 CRUD 操作

使用要求:
1、持久層接口和持久層接口的映射配置必須在相同的包下
2、持久層映射配置中 mapper 標簽的 namespace 屬性取值必須是持久層接口的全限定類名
3、SQL 語句的配置標簽<select>,<insert>,<delete>,<update>的 id 屬性必須和持久層接口的
方法名相同

根據 ID 查詢

在持久層接口中添加 findById 方法
/**
* 根據 id 查詢
* @param userId
* @return
*/
User findById(Integer userId);
配置映射文件
<!-- 根據 id 查詢 --> 
<select id="findById" resultType="com.itheima.domain.User" parameterType="int">
  select * from user where id = #{uid}
</select>

映射文件中的屬性

  • resultType : 用于指定結果集的類型。
  • parameterType :用于指定傳入參數的類型。

sql 語句中使用#{}字符: 它代表占位符,相當于原來 jdbc 部分所學的?,都是用于執行語句時替換實際的數據。
具體的數據是由#{}里面的內容決定的。#{}中內容的寫法:由于數據類型是基本類型,所以此處可以隨意寫并不影響,但盡量按照開發規范填寫。

編寫測試類
public class MybastisCRUDTest {

    private InputStream in ;
    private SqlSessionFactory factory;
    private SqlSession session;
    private IUserDao userDao;
    
    @Test
    public void testFindOne() {
    //6.執行操作
    User user = userDao.findById(41);
    System.out.println(user);
    }
    @Before//在測試方法執行之前執行
    public void init()throws Exception {
    //1.讀取配置文件
    in = Resources.getResourceAsStream("SqlMapConfig.xml");
    //2.創建構建者對象
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //3.創建 SqlSession 工廠對象
    factory = builder.build(in);
    //4.創建 SqlSession 對象
    session = factory.openSession();
    //5.創建 Dao 的代理對象
    userDao = session.getMapper(IUserDao.class);
    }

    @After//在測試方法執行完成之后執行
    public void destroy() throws Exception{
    session.commit();
    //7.釋放資源
    session.close();
    in.close();
    } 
}

添加操作

在持久層接口中添加新增方法
/**
* 保存用戶
* @param user
* @return 影響數據庫記錄的行數
*/
int saveUser(User user)
配置映射文件
<!-- 保存用戶--> 
<insert id="saveUser" parameterType="com.itheima.domain.User">
   insert into user(username,birthday,sex,address) 
   values(#{username},#{birthday},#{sex},#{address})
</insert>

parameterType 屬性:
代表參數的類型,因為我們要傳入的是一個類的對象,所以類型就寫類的全名稱。
sql 語句中使用#{}字符: 它代表占位符,相當于原來 jdbc 部分所學的?,都是用于執行語句時替換實際的數據。
具體的數據是由#{}里面的內容決定的。 #{}中內容的寫法:由于我們保存方法的參數是 一個 User 對象,此處要寫 User 對象中的屬性名稱。

編寫測試類
public class MybastisCRUDTest {
private InputStream in ;
private SqlSessionFactory factory;
private SqlSession session;
private IUserDao userDao;

@Test
public void testSave(){
    User user = new User();
    user.setUsername("modify User property");
    user.setAddress("北京市順義區");
    user.setSex("男");
    user.setBirthday(new Date());
    System.out.println("保存操作之前:"+user);
    //5.執行保存方法
    userDao.saveUser(user);
    System.out.println("保存操作之后:"+user);
}

打開 Mysql 數據庫發現并沒有添加任何記錄,原因是什么?
這一點和 jdbc 是一樣的,我們在實現增刪改時一定要去控制事務的提交,那么在 mybatis 中如何控制事務
提交呢?
可以使用:session.commit();來實現事務提交。加入事務提交后的代碼如下:
@After//在測試方法執行完成之后執行
public void destroy() throws Exception{
    session.commit();
    //7.釋放資源
    session.close();
    in.close();
}
}
問題擴展:如何獲取新增用戶 id 的返回值

新增用戶后,同時還要返回當前新增用戶的 id 值,因為 id 是由數據庫的自動增長來實現的,所以就相當于我們要在新增后將自動增長 auto_increment 的值返回

<insert id="saveUser" parameterType="USER">
<!-- 配置保存時獲取插入的 id --> 
<selectKey keyColumn="id" keyProperty="id" resultType="int">
select last_insert_id();
</selectKey>
insert into user(username,birthday,sex,address) 
values(#{username},#{birthday},#{sex},#{address})
</insert>

更新數據

在持久層接口中添加更新方法
/**
* 更新用戶
* @param user
* @return 影響數據庫記錄的行數
*/
int updateUser(User user);
配置映射文件
<!-- 更新用戶 --> 
<update id="updateUser" parameterType="com.itheima.domain.User">
  update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
</update>
編寫測試類
@Test
public void testUpdateUser()throws Exception{
    //1.根據 id 查詢
    User user = userDao.findById(52);
    //2.更新操作
    user.setAddress("北京市順義區");
    int res = userDao.updateUser(user);
    System.out.println(res);
}

刪除數據

在持久層接口中添加刪除方法
/**
* 根據 id 刪除用戶
* @param userId
* @return
*/
int deleteUser(Integer userId);
配置映射文件
<!-- 刪除用戶 --> 
<delete id="deleteUser" parameterType="java.lang.Integer">
  delete from user where id = #{uid}
</delete>
編寫測試類
@Test
public void testDeleteUser() throws Exception {
//6.執行操作
int res = userDao.deleteUser(52);
System.out.println(res);
}

模糊查詢

在持久層接口中添加模糊查詢方法
/**
* 根據名稱模糊查詢
* @param username
* @return
*/
List<User> findByName(String username);
配置映射文件
<!-- 根據名稱模糊查詢 --> 
<select id="findByName" resultType="com.itheima.domain.User" parameterType="String">
   select * from user where username like  '%${value}%'
</select>
我們在上面將原來的 #{}占位符,改成了${value}。注意如果用模糊查詢的這種寫法,那么${value}的寫法就是固定的,不能寫成其它名字
編寫測試類
@Test
public void testFindByName(){
 //5.執行查詢一個方法
List<User> users = userDao.findByName("王");
for(User user : users){
System.out.println(user);
} }
#{}與${}的區別
1.#{}表示一個占位符號
通過#{}可以實現 preparedStatement 向占位符中設置值,自動進行 java 類型和 jdbc 類型轉換,
#{}可以有效防止 sql 注入。 #{}可以接收簡單類型值或 pojo 屬性值。
如果 parameterType 傳輸單個簡單類型值,#{}括號中可以是 value 或其它名稱。
2. 表示拼接 sql 串通過${}可以將 parameterType 傳入的內容拼接在 sql 中且不進行 jdbc 類型轉換, ${}可以接收簡
單類型值或 pojo 屬性值,如果 parameterType 傳輸單個簡單類型值,${}括號中只能是 value。
parameterType 配置參數

基 本 類 型 和 String 我 們 可 以 直 接 寫 類 型 名 稱 , 也 可 以 使 用 包 名 . 類 名 的 方 式 , 例 如 :
java.lang.String。

實體類類型,目前我們只能使用全限定類名。

resultType 配置結果類型

resultType 屬性可以指定結果集的類型,它支持基本類型和實體類類型。

/**
* 查詢總記錄條數
* @return
*/
int findTotal()
<!-- 查詢總記錄條數 -->
<select id="findTotal" resultType="int">
  select count(*) from user;
</select>
/**
* 查詢所有用戶
* @return
*/
List<User> findAll();

此時實體類屬性和數據庫表的列名要求一致

<!-- 配置查詢所有操作 --> 
<select id="findAll" resultType="com.itheima.domain.User">
  select * from user
</select>
如果實體類屬性與表字段不能完全一致,可以通過resultMap進行映射

resultMap 標簽可以建立查詢的列名和實體類的屬性名稱不一致時建立對應關系。從而實現封裝。

type 屬性:指定實體類的全限定類名
id 屬性:給定一個唯一標識,是給查詢 select 標簽引用用的。

<resultMap type="com.itheima.domain.User" id="userMap">
    <id column="id" property="userId"/>
    <result column="username" property="userName"/>
    <result column="sex" property="userSex"/>
    <result column="address" property="userAddress"/>
    <result column="birthday" property="userBirthday"/>
</resultMap>

id 標簽:用于指定主鍵字段
result 標簽:用于指定非主鍵字段
column 屬性:用于指定數據庫列名
property 屬性:用于指定實體類屬性名稱

<!-- 配置查詢所有操作 --> 
<select id="findAll" resultMap="userMap">
  select * from user
</select>

Mybatis 與 JDBC 編程的比較

數據庫鏈接創建、釋放頻繁造成系統資源浪費從而影響系統性能,如果使用數據庫鏈接池可解決此問題。
解決:在 SqlMapConfig.xml 中配置數據鏈接池,使用連接池管理數據庫鏈接。

Sql 語句寫在代碼中造成代碼不易維護,實際應用 sql 變化的可能較大,sql 變動需要改變 java 代碼。
解決:將 Sql 語句配置在 XXXXmapper.xml 文件中與 java 代碼分離。

向 sql 語句傳參數麻煩,因為 sql 語句的 where 條件不一定,可能多也可能少,占位符需要和參數對應。
解決:Mybatis 自動將 java 對象映射至 sql 語句,通過 statement 中的 parameterType 定義輸入參數的
類型。

對結果集解析麻煩,sql 變化導致解析代碼變化,且解析前需要遍歷,如果能將數據庫記錄封裝成 pojo 對
象解析比較方便。
解決:Mybatis 自動將 sql 執行結果映射至 java 對象,通過 statement 中的 resultType 定義輸出結果的類型。

Mybatis 的動態 SQL 語句

Mybatis 的映射文件中,前面我們的 SQL 都是比較簡單的,有些時候業務邏輯復雜時,我們的 SQL 是動態變
化的,此時在前面的學習中我們的 SQL 就不能滿足要求了。

動態 SQL 之<if>標簽

我們根據實體類的不同取值,使用不同的 SQL 語句來進行查詢。比如在 id 如果不為空時可以根據 id 查詢,
如果 username 不同空時還要加入用戶名作為條件。

<select id="findByUser" resultType="user" parameterType="user">
    select * from user where 1=1
    <if test="username!=null and username != '' ">
        and username like  '%${username}%'
    </if> <if test="address != null">
        and address like   '%${address}%'
    </if>
</select>

<if>標簽的 test 屬性中寫的是對象的屬性名

動態 SQL 之<where>標簽

為了簡化上面 where 1=1 的條件拼裝,我們可以采用<where>標簽來簡化開發。

<!-- 根據用戶信息查詢 --> 
<select id="findByUser" resultType="user" parameterType="user"> 
    select * from user
    <where> 
        <if test="username!=null and username != '' ">
            and username like #{username}
        </if> 
        <if test="address != null">
            and address like #{address}
        </if>
    </where>
</select>
動態標簽之<foreach>標簽
<!-- 查詢所有用戶在 id 的集合之中 --> 
<select id="findInIds" resultType="user" parameterType="queryvo">
    <!-- select * from user where id in (1,2,3,4,5); --> 
    <include refid="defaultSql"></include>
    <where> 
        <if test="ids != null and ids.size() > 0"> 
            <foreach collection="ids" open="id in ( " close=")" item="uid" separator=",">
            #{uid}
            </foreach>
        </if>
    </where>
</select>

<foreach>標簽用于遍歷集合,它的屬性:

  • collection:代表要遍歷的集合元素,注意編寫時不要寫#{}
  • open:代表語句的開始部分
  • close:代表結束部分
  • item:代表遍歷集合的每個元素,生成的變量名
  • sperator:代表分隔符
Mybatis 中簡化編寫的 SQL 片段

Sql 中可將重復的 sql 提取出來,使用時用 include 引用即可,最終達到 sql 重用的目的。

<!-- 抽取重復的語句代碼片段 -->
<sql id="defaultSql">
  select * from user
</sql>
<!-- 配置查詢所有操作 --> 
<select id="findAll" resultType="user"> 
    <include refid="defaultSql"></include>
</select>
<!-- 根據 id 查詢 --> 
<select id="findById" resultType="UsEr" parameterType="int">
    <include refid="defaultSql"></include>
    where id = #{uid}
</select>

Mybatis 緩存

像大多數的持久化框架一樣,Mybatis 也提供了緩存策略,通過緩存策略來減少數據庫的查詢次數,從而提
高性能。
Mybatis 中緩存分為一級緩存,二級緩存

微信截圖_20210303141544.png
Mybatis 一級緩存

一級緩存是 SqlSession 級別的緩存,只要 SqlSession 沒有 flush 或 close,它就存在。

一級緩存是 SqlSession 范圍的緩存,當調用 SqlSession 的修改,添加,刪除,commit(),close()等

Mybatis 二級緩存

二級緩存是 mapper 映射級別的緩存,多個 SqlSession 去操作同一個 Mapper 映射的 sql 語句,多個
SqlSession 可以共用二級緩存,二級緩存是跨 SqlSession 的

在 SqlMapConfig.xml 文件開啟二級緩存
<settings>
<!-- 開啟二級緩存的支持 --> 
<setting name="cacheEnabled" value="true"/>
</settings>

因為 cacheEnabled 的取值默認就為 true,所以這一步可以省略不配置。為 true 代表開啟二級緩存;為
false 代表不開啟二級緩存。
配置相關的 Mapper 映射文件
<cache>標簽表示當前這個 mapper 映射將使用二級緩存,區分的標準就看 mapper 的 namespace 值。

<?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="com.itheima.dao.IUserDao">
<!-- 開啟二級緩存的支持 -->
<cache></cache>
</mapper>
配置 statement 上面的 useCache 屬性
將 UserDao.xml 映射文件中的<select>標簽中設置 useCache=”true”代表當前這個 statement 要使用
二級緩存,如果不使用二級緩存可以設置為 false。
注意:針對每次查詢都需要最新的數據 sql,要設置成 useCache=false,禁用二級緩存。

<!-- 根據 id 查詢 --> 
<select id="findById" resultType="user" parameterType="int" useCache="true">
  select * from user where id = #{uid}
</select>

當我們在使用二級緩存時,所緩存的類一定要實現 java.io.Serializable 接口,這種就可以使用序列化
方式來保存對象

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容