Hibernate入门1-基本用法

Hibernate 快速入门1 - 基本用法

Hibernate简介

ORM:对象关系映射(ORM, Object Relational Mapping)。用于实现面向对象编程语言里不同类型系统的数据之间的转换。

Hibernate: Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行。

一句话:让程序员以面向对象的方式访问数据库。

注意:本文假设你对JDBC和Java反射有基本的了解,熟悉基本MySQL操作。

0 下载

// hibernate我使用的是hibernate-release-5.2.16.Final

hibernate下载:http://hibernate.org/orm/releases/

mysql驱动:https://dev.mysql.com/downloads/connector/j/

common-logging: http://commons.apache.org/proper/commons-logging/

解压hibernate,将/path-to-hibernate/lib/required、mysql驱动所在目录和common-logging 所在目录添加到classpath。

在Intellij Idea中,可以在Edit -> Project Structure -> Libraries中配置。

1.1 简单例子

先考虑下,传统访问数据库的方式是:编写SQL,获取数据,赋值给对象。Hibernate要做的就是这一步。

先把数据库配置好:

@ mysql -u root -p
@ CREATE USER hibernate IDENTIFIED BY 'hibernate123';
@ CREATE DATABASE hibernatedb;
@ USE hibernatedb;
@ GRANT ALL ON hibernatedb.* TO hibernate;

上面在MySQL中为本文建立了一个用户: hibernate ,密码是: hibernate123 ,数据库是: hibernatedb

首先来看一个例子:

// src/com/example/test/Student.java
package com.example.test;

import javax.persistence.*;

@Entity
@Table(name = "student_info")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

     @Column(name = "name")
    private String name;

    private int age;
    
    @Transient
    private boolean olderThan18;

    @Override
    public String toString() {
        return "id: " + id + ", name: " + name + ", age: " + age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer 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;
    }
}

// src/com/example/test/HibernateTest.java
package com.example.test;

// user: hibernate, pass: hibernate123, db:hibernatedb

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;

public class HibernateTest {
    public static Integer addStudent() {
        Configuration conf = new Configuration().configure();
        StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
        try (SessionFactory sessionFactory = conf.buildSessionFactory(registry);
             Session session = sessionFactory.openSession()) {
            Transaction transaction = session.beginTransaction();

            Student student = new Student();

            student.setAge(18);
            student.setName("robert");

            Integer id = (Integer) session.save(student);

            transaction.commit();

            return id;
        }
    }

    public static void searchStudent(Integer id) {
        if (id != null) {
            Configuration conf = new Configuration().configure();
            StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
            try (SessionFactory sessionFactory = conf.buildSessionFactory(registry);
                 Session session = sessionFactory.openSession()) {
                Transaction transaction = session.beginTransaction();

                Student student = session.get(Student.class, id);

                if (student != null) {
                    System.out.println(student);
                }

                transaction.commit();
            }
        }
    }

    public static void main(String[] args) {
        Integer id = addStudent();
        searchStudent(id);
    }
}

src/hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!--
  ~ Hibernate, Relational Persistence for Idiomatic Java
  ~
  ~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
  ~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
  -->
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <!--后面根据tomcat,修改成根据JDNI来访问。参考java ee 企业应用实战 p393-->
        <property name="connection.url">jdbc:mysql://localhost:3306/hibernatedb</property>
        <property name="connection.username">hibernate</property>
        <property name="connection.password">hibernate123</property>
        <property name="hibernate123"/>
        <!-- maximum number of connections -->
        <property name="hibernate.c3p0.max_size">20</property>
        <property name="hibernate.c3p0.min_size">1</property>
        <property name="hibernate.c3p0.max_statements">30</property>
        <property name="hibernate.c3p0.idle_test_period">3000</property>
        <property name="hibernate.c3p0.acquire_increment">2</property>
        <property name="hibernate.c3p0.valid">true</property>

        <!-- SQL dialect -->
        <!--caution. it's not MySQLInnoDBDialect, but MySQL5InnoDBDialect !!-->
        <property name="dialect">org.hibernate.dialect.MySQL55Dialect</property>
        <property name="hbm2ddl.auto">update</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <property name="hibernate.format_sql">true</property>

        <mapping class="com.example.test.Student"/>
    </session-factory>

</hibernate-configuration>

下面运行程序,可以看到console有部分输出为:

Hibernate: 
    select
        student0_.id as id1_0_0_,
        student0_.age as age2_0_0_,
        student0_.name as name3_0_0_ 
    from
        student_info student0_ 
    where
        student0_.id=?
id: 1, name: robert, age: 18

前面是Hibernate生成的SQL语句。id: 1, name: robert, age: 18就是我们新建到数据库中数据。

mysql> show tables;
+-----------------------+
| Tables_in_hibernatedb |
+-----------------------+
| student_info          |
+-----------------------+
7 rows in set (0.01 sec)

mysql> select * from student_info;
+----+-----+--------+
| id | age | name   |
+----+-----+--------+
|  1 |  18 | robert |
+----+-----+--------+
1 row in set (0.00 sec)

这表明数据库中已经有一个名为student_info的表,表中有我们插入的元组。

1.2 基本使用

下面来讲解一下hibernate的基本使用。

1.2.1 类的注解

拿前面例子来说明。

@Entity: 标识这个类想要被映射到数据库中。

@Table: 指定表的名字。如果省略@Table,表的名字则是这个类的类名(the unqualified class name)。这里表的名字是student_info。如果上面省略@Table,表名是Student,并不是com.example.test.Student。

@Id:标识这个字段用作主键(primary key)。可以把多个字段都用@Id标识,对应属性的组合构成了主键。多个@Id不是JPA(Java Persistence API)所支持的。JPA自己查。这里就只用一个字段。

@GeneratedValue: 表示主键生成的策略。比如自增还是生成一个UUID(这里是Integer不能用UUID)。strategy = GenerationType.IDENTITY 生成策略是由数据库自动生成。例子中是自增。其他策略(比如GenerationType.AUTO等)请参考文档。

@Column: 设置属性在数据库表中的名字。如果省略,就是属性全部小写的名字。

@Transient: 表示该属性不应该被实例化。

最重要的:Student是一个bean。它为所有需要让外部访问的成员变量都提供了setter和getter。除了static和@transient标记的属性外,其他属性所有不管有没有annotation,都会被持久化。

请查看官方文档,看这些注解还可以设置哪些属性。

也就是如下:

@Entity
public class Student {
    private String hometown;    // will be persistent too.
};

1.2.2 配置文件

除了要在源代码中用注解指出哪些class需要被映射外,我们还要配置文件。Hibernate的配置文件是hibernate.cfg.xml。基本的数据库配置就像上面举例的一样,数据库的用户名,密码,db名字。hibernate.c3p0.这些属性是数据库连接*的一些基本信息,看名字基本能看懂。不懂的查阅文档。

为了让hibernate能根据不同的数据库进行优化,我们还要设置数据库的种类,我用的是MySQL5,所以设置方言为MySQL5. <property name="dialect>org.hibernate.dialect.MySQL55Dialect</property> 其他的数据库怎么设置请查阅文档。

<property name="hbm2ddl.auto">update</property>是让hibernate根据需要更新数据库(包含了创建)。hbm2ddl.atuo还可以有其他值,请查阅温度。注意,这个值最好仅在开发测试的时候使用,正式环境中请勿使用这个property。

配置最下面<mapping/>指定,哪些类需要被映射。这个容易被忽略

1.2.3 SessionFactory

有了被注解的类,有了配置文件,我们还要进行一系列的操作。

  1. 加载配置
  2. 启动
  3. 调用SessionFactory得到一个Session
  4. 开始事务
  5. 程序逻辑处理
  6. 结束事务
  7. 关闭Session和SessionFactory

分别如下:

Configuration conf = new Configuration().configure();   // 默认加载src/hibernate.cfg.xml
StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();    // 启动   
try (SessionFactory sessionFactory = conf.buildSessionFactory(registry);
     Session session = sessionFactory.openSession()) {  // 打开得到session
    Transaction transaction = session.beginTransaction();   // 开始事务

    Student student = session.get(Student.class, id);   // 逻辑处理

    if (student != null) {
        System.out.println(student);
    }

    transaction.commit();   // 结束事务
}   // 关闭Session和SessionFactory

注意:要正确关闭Session和SessionFactory,不然程序退出不了。

我们对数据库的CRUD操作都是先打开一个事务,然后通过Session来进行。即使只查询,不修改数据库,事务也是必不可少的。具体原因请看这里https://stackoverflow.com/questions/13539213/why-do-i-need-transaction-in-hibernate-for-read-only-operation

1.2.4 对象的状态

对象在Hibernate中有如下几个状态:

  • transient: never persistent, not associated with any Session
  • persistent: associated with a unique Session
  • detached: previously persistent, not associated with any Session

暂时分别翻译为:瞬态,持久态,脱管态。

瞬态的意思是,这个对象的被new以后,从来没有被持久化到数据库中。

持久态的意思是,这个对象和一个session是相关联的。比如刚从数据库中取出来:调用session.get()

脱离态的意思是,这个对象被持久化过,但现在没有和一个session相关联。比如对持久态的对象调用:obj.evict,或者session.close()。

如下:

hibernate_states
hibernate_states

1.2.4 基本函数

public interface Session {
Serializable save(Object object);
void saveOrUpdate(Object object);
void persist(Object object);
<T> T load(Class<T> theClass, Serializable id);
<T> T get(Class<T> entityType, Serializable id);
void update(Object object);
void delete(Object object);
void evict(Object object);
// ... other methods
}

保存:save、saveOrUpdate和persist返回的是生成的主键(the generated identifier)。它们主要是把瞬态的对象持久化。其中save会返回生成的主键。在我这个版本的hibernate,对于处于持久态的对象,不管对象是否有更新,再次调用save后,似乎数据库都是一样的。

查询: load和get。区别是如果数据库中,没有id对应的记录,那么load会抛出异常,get会返回null。

更新:update只能作用于处于持久态和脱离态的对象上。对于脱离态的对象,这个操作还会把对象变为持久态。

删除:delete。很简单,只能删除处于持久态的对象。

evict: 把对象从持久态转化为脱离态。

还有一些其他函数,请参考官方文档。

1.3 映射集合属性

如果一个属性有多个地址,也就是Student的属性为一个集合的时候。怎么办呢?

1.3.1 映射 list, set, array

首先,为了避免数据冗余,这些bean属性的集合,肯定要保存在另外一个表中。比如(id, name, addressList),如果addressList让然和student保存在一个表中,就会产生冗余: (1, "robert", "beijing"), (1, "robert", "shanghai")。 后面MySQL的输出也验证了这个想法。

映射list(数组和list一样):

@Entity
@Table(name = "student_info")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "name")
    private String name;

    private int age;
    
    @ElementCollection(targetClass = String.class)  // element class type
    // name = "addresses": name of table contain elements of list is address.
    // joinColumns = @JoinColumn(..): FOREIGN KEY (student_id) REFERENCES Student (id). 
    // In fact, we can rename Student.id by using @Column(name = "student_id"). 
    // And in such case we can omit joinColumns here. 
    @CollectionTable(name = "addresses", joinColumns = @JoinColumn(name = "student_id", referencedColumnName = "id"))
    @Column(name = "address", nullable = false) // name of column representing elements of the set
    @OrderColumn(name = "addr_id")
    private List<String> addressList;

    // getters and setters
}

HibernateTest.java.addStudent

session.beginTransaction();

Student student = new Student();
student.setName("robert");
student.setAge(18);
List<String> addrs = new ArrayList<>(2);
addrs.add("bj");
addrs.add("sh");
student.setAddressList(addrs);

session.save(student);

session.getTransaction.commit();

数据库的输出:

mysql> desc addresses;
+------------+--------------+------+-----+---------+-------+
| Field      | Type         | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| Student_id | int(11)      | NO   | PRI | NULL    |       |
| address    | varchar(255) | NO   |     | NULL    |       |
| addr_id    | int(11)      | NO   | PRI | NULL    |       |
+------------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> select * from addresses;
+------------+---------+---------+
| Student_id | address | addr_id |
+------------+---------+---------+
|          7 | bj      |       0 |
|          7 | sh      |       1 |
+------------+---------+---------+
2 rows in set (0.00 sec)

mysql> show create table addresses;
CREATE TABLE `addresses` (
  `student_id` int(11) NOT NULL,
  `address` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `addr_id` int(11) NOT NULL,
  PRIMARY KEY (`student_id`,`addr_id`),
  CONSTRAINT `FK37pobof33ovjb9oxnt205houy` FOREIGN KEY (`student_id`) REFERENCES `student_info` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

映射集合用@ElementCollection注解。

还有个@CollectionTable注解,name字段指定指定集合对应的表的名字,joinColumns是一个数组类型,它的元素类型为@JoinColumn.

@JoinColumn:专门用来指定外键,它的name属性指定它所属于的表对应的名字,referencedColumnName指定被引用表的属性名。举个例子: @JoinColumn(name = "student_id", referencedColumnName = "id")表示在@JoinColumn所在的表建立一个属性student_id, 同时建立一个外键约束: FOREIGN KEY (student_id) REFERENCES student_info (id);

@OrderColumn(name = "addr_id")表示addr_id这个列用来保存元素的顺序。

可以在MySQL输出中看到的确建立了对应的属性和外键约束。 addresses.student_id <-> student_info.id。需要注意的是,addresses上的外键约束,默认是拒绝更改。意思是,如果删除student_info中id=7的行,这个操作会被拒绝。如下:

mysql> delete from student_info where id = 7;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`hibernatedb`.`addresses`, CONSTRAINT `FK37pobof33ovjb9oxnt205houy` FOREIGN KEY (`student_id`) REFERENCES `student_info` (`id`))

如果我们通过hibernate删除id = 7 的Student,那么他对应的address确可以被删除。

Integer id = 7;
Transaction transaction = session.beginTransaction();

Student student = session.get(Student.class, id);

if (student != null) {
    System.out.println("deleting " + student);
    session.delete(student);
}

transaction.commit();

数据库的输出:

mysql> select * from addresses;
Empty set (0.00 sec)

这是为什么呢?仔细查看控制台,我们发现两条sql语句:

deleting id: 7, name: robert, age: 18, addrs: [bj, sh]
Hibernate: 
    delete 
    from
        addresses 
    where
        student_id=?
Hibernate: 
    delete 
    from
        student_info 
    where
        id=?

这下非常明显了。先删除了addresses表中 id = 7的行,再删除student_info中id = 7的行。

映射set的话,和上面类似,只是不能再用@OrderColumn。

当我们有疑惑的时候,仔细查看hibernate控制台的输出是一个好方法。

1.3.2 映射map

public class Student {
    // other code
    @ElementCollection(targetClass = Float.class)
    @CollectionTable(name = "subject_scores", joinColumns = @JoinColumn(name = "student_id", referencedColumnName = "id"))
    @MapKeyClass(String.class)
    @MapKeyColumn(name = "subject")
    @Column(name = "score")
    private Map<String, Float> scores; // K: name of subject. V: score for the subject
}

和list映射的区别是,多了MapKeyClass和MapKeyColumn,分别代表map的key所属的类和key在表中的属性名。@ElementCollection(targetClass = String.class)表示value所属的类,@CollectionTable(name = "subject_scores")这个name表示的value在表中的名字。

transaction.beginTransaction();
Student student = new Student();

// ... 
Map<String, Float> subjects = new HashMap<>(2);
subjects.put("english", 90.0f);
subjects.put("math", 95.5f);
student.setScores(subjects);

sesssion.save(student);

transaction.commit();

数据库输出如下:

mysql> select * from subject_scores;
+------------+-------+---------+
| student_id | score | subject |
+------------+-------+---------+
|          9 |    90 | english |
|          9 |  95.5 | math    |
+------------+-------+---------+
2 rows in set (0.00 sec)

mysql> desc subject_scores;
+------------+--------------+------+-----+---------+-------+
| Field      | Type         | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| student_id | int(11)      | NO   | PRI | NULL    |       |
| score      | float        | YES  |     | NULL    |       |
| subject    | varchar(255) | NO   | PRI | NULL    |       |
+------------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

1.3.3 集合的性能

考虑这样一个情景:我们获取通过hibernate获取一个对象,该对象有个集合属性。集合包含了几万条数据。但在我们当前的业务当中,并不会访问该集合属性。如果hibernate在获取对象的时候,也从集合属性对应的表中取出所有元组并添加到集合里面,那hibernate的这种操作无疑是非常浪费内存的。幸运的是,hibernate的针对集合属性,默认采取延迟加载策略,只有在我们访问该集合属性的时候,才会从数据库中加载相关数据。如果我要改变默认的策略,我们可以为集合属性指定FetchType如下:

//...
@ElementCollection(targetClass = String.class, fetch = FetchType.EAGER) 
private List<String> list;

延迟加载对应的属性是: FetchType.LAZY

1.4 集合元素是自定义的bean(不是int, String等)

这种情况下,我们把作为元素的自定义的bean用@Embeddable修饰,把该bean称作包含类的组合属性。其他的不变:

@Embeddable
public class BodyInfo {
    private int height;
    private int weight;
}

public class Student {
    private BodyInfo bodyInfo;
    // ...
}

// ...
BodyInfo bodyInfo = new BodyInfo();
bodyInfo.setHeight(178);
bodyInfo.setWeight(70);

student.setBodyInfo(bodyInfo);
Integer id = (Integer) session.save(student);

transaction.commit();

sql输出:

mysql> select * from student_info;
+----+-----+--------+--------+--------+
| id | age | name   | height | weight |
+----+-----+--------+--------+--------+
|  9 |  18 | robert |      0 |      0 |
| 10 |  18 | robert |    178 |     70 |
+----+-----+--------+--------+--------+
6 rows in set (0.00 sec)

注意和1.1中的student_info输出对比,是不是多了2个列:height,weight。这两列也恰巧是自定义bean BodyInfo的两个属性。

这说明了hibernate自动把BodyInfo中的属性(必须是非集合)添加到了student_info表中。查看控制台:

Hibernate: 
    
    alter table hibernatedb.student_info 
       add column height integer not null
Hibernate: 
    
    alter table hibernatedb.student_info 
       add column weight integer not null
Hibernate: 
    insert 
    into
        student_info
        (age, height, weight, name) 
    values
        (?, ?, ?, ?)

hibernate为student_info表增加了两个属性!!很智能吧。但需要注意的是,这种对数据库表结构的操作最好不要在生产环境中发生(可以删除配xml置文件中的hbm2ddl.auto的属性)。

如果BodyInfo中有集合属性怎么办?我们可以猜想,BodyInfo中的那些非集合属性仍然会添加到student_info表中,那些集合属性,会像前面一样,用单独的表来存储。这里就不验证了。

1.5 使用xml来配置映射

我们可以用xml来配置属性关系,从而不用注解。

比如如下:
src/com/example/test/Student.java

public class Student {
    private Integer id;
    private String name;
    
    // getter and setters
}

src/com/example/test/Student.hbm.xml

<?xml version='1.0' encoding='utf-8'?>
<!--
  ~ Hibernate, Relational Persistence for Idiomatic Java
  ~
  ~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
  ~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
  -->
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.example.test">
    <class name="Student" table="student_info">
        <id name="id" type="java.lang.Integer" column="id"/>
        <property name="name" type="java.lang.String" column="name"/>
    </class>

</hibernate-mapping>

src/hibernate.cfg.xml

<!-- some other settings -->
<session-factory>
    <!--<mapping class="com.example.test.Student"/>-->
    <mapping resource="com/example/test/Student.hbm.xml"/>
</session-factory>

现在用注解的比较多。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342