新手入門hibernate框架

Hibernate

創建 hibernate 工程示例:

創建工程,引入 jar 包

創建配置文件 hibernate.cfg.xml, 配置數據庫連接

編寫實體類(entity), 標明注解, 然后配置在 hibernate.cfg.xml 中

創建?SessionFactory, 獲取?Session, 通過操作實體類操作數據庫。

Session

對象的三種狀態:

Transient, 瞬時狀態,指的是對象已經被初始化,但沒有跟 hibernate 的 session 建立過聯系,即數據庫里沒有數據對應。

Persistent, 持久化狀態,指的是對象在數據中有對應數據,對象有 id 值。它可能是通過 save 或 load 等方式得到的,并且在 session 緩存中有定義。

Detached, 脫管狀態,曾經被持久,在數據庫中有數據對應。但是,在 session 緩存里沒有記錄。也許是 session 關閉了,也許是清空了。

狀態之間可以進行轉換,下面是大致的轉換流程:

get/load/query()

get/load 會優先在 session 緩存里尋找對象,如果找不到,再去查詢數據庫

query 會直接查詢數據庫

get 不懶,會立刻查詢。如果沒有找到,那么返回 null

load 延遲加載,立刻返回一個代理對象。如果沒有找到,那么拋出異常

LazyInitializationException !!!

flush/refresh()

flush 將 session 緩存里的數據同步到數據庫,觸發相應的 sql 語句。

以下情況,會觸發 flush 操作:

調用 commit 的時候,會觸發 session.flush() 操作。

執行 session.createQuery() 查詢的時候,也會觸發 flush 操作。

手動執行 flush 操作。

refresh 是將數據庫里的信息,同步到 session 緩存。

clear/evict()

從 session 緩存中清理數據

save/persist()

都是用來將瞬時對象變為持久化對象,即將數據插入數據庫,對應 insert 語句。

save 是 hibernate 原生的語法,persist 是 jpa 的語法。

在執行的時候,不會立刻插入數據,只有執行了 flush 操作,才真正觸發數據庫操作。

save/persist 方法會立刻為實體類對象生成主鍵。

他們的區別是, 如果在保存之前,重新手動賦予了主鍵:

save 會忽視你的賦值

persist 會拋異常

update/merge()

他們主要用來完成實體類對象的修改,對應的是 update 語句。

若更新一個持久化對象,可以不顯式調用 update, 因為 flush 操作會觸發 update

可以將一個脫管對象轉換為持久化對象

merge 是 jpa 中的語法

doWork

可以將 jdbc 的 connection 對象暴露出來,用于插入一些 jdbc 操作語法。

Identifier

-- JPA 默認

@GeneratedValue(strategy = GenerationType.AUTO/IDENTITY/SEQUENCE/TABLE)

-- JPA 定制序列/Table

@GeneratedValue(generator = "xxx")

@SequenceGenerator(name = "xxx", sequenceName = "seq_xxx", associateSize = 1)

@TableGenerator(name = "xxx", table = "tb_xxx")

-- Hibernate 格式的 generator:

@GeneratedValue(generator = "yyy")

@GenericGenerator(name = "yyy", strategy = "native")

@GenericGenerator(name = "yyy", strategy = "uuid2")

@GenericGenerator(name = "yyy", strategy = "table")

Association

1-N

一對多的關系,在數據庫的角度,需要使用外鍵維護這種關系。

一般情況下,在多的一邊的表上,建立一個外鍵映射到另一個表。

比如,有兩個表 author, book 一般而言,book 的定義類似是這樣的:

create table book {

? ? bookid int primary key,

? ? name varchar2(20) not null,

? ? price float,

? ? publish_date date default sysdate,

? ? -- 下面字段用來維護跟作者的關系

? ? -- 它是一個外鍵約束

? ? authorid references author

}

book/author 分別對應實體類 Book/Author,我們可以在其中任意一個實體類中,設置他們的關系。

如果只是在其中一個中設置關系,那么叫“單邊關系”、“單向關聯”,否則是“雙向關聯”。

其中最常用的是 多對一的單向關聯 和 *多對一的雙向關聯*。

多對一的單向:

public class Author {

? ? @Id @GeneratedValue private long id;

? ? private String name;

}

public class Book {

? ? @Id @GeneratedValue private long id;

? ? private String name;

? ? private FLoat price;

? ? // 只是在多的一段設置關系。這是非常常用的一種方式。

? ? // 用 @JoinColumn 定制外鍵字段的名字

? ? @ManyToOne @JoinColumn

? ? private Author author;

}

多對一的雙向關系:

// 多的一端,即主端,需要負責維護關系

public class Book {

? ? @Id @GeneratedValue private long id;

? ? private String name;

? ? private FLoat price;

? ? // 只是在多的一端設置關系。這是非常常用的一種方式。

? ? // 用 @JoinColumn 定制外鍵字段的名字

? ? @ManyToOne @JoinColumn

? ? private Author author;

}

// 一的一端,即從端,需要當甩手掌柜

public class Author {

? ? @Id @GeneratedValue private long id;

? ? private String name;

? ? // 不要讓雙方都去維護關系,不然會有沖突或重復。

? ? // 一般情況下,需要讓多的一端維護關系即可。這里用 mappedBy 表名,自己當甩手掌柜。

? ? @OneToMany(mappedBy = "author")

? ? private Set<Books> books = new HashSet<>();

}

在數據插入的時候,要先保存一的一端,再保存多的一端,否則,會有冗余的 SQL 語句。

M-N

多對多的關系,需要使用中間表維護雙方關系。對應的注解為 @ManyToMany

必須為雙方制定從屬關系,也就是將維護關系的責任交給其中一個實體類(mappedBy),從而避免重復或沖突。

可以使用 @JoinTable 對中間表進行定制

例子:

@Entity

public class Emp {

? ? @ManyToMany? // 負責關系的維護

? ? @JoinTable(...)

? ? private Set<Project> projects = new HashSet<>();

}

@Entity

public class Project {

? ? @ManyToMany(mappedBy = "projects")? // 甩手掌柜

? ? private Set<Emp> emps = new HashSet<>();

}

1-1

兩種方式:

在其中一個表上創建一個列,保存另一個表的主鍵。即外鍵關聯。

兩個表,有關聯的數據,使用相同的主鍵。即主鍵關聯。

外鍵關聯:

@Entity

public class Person {

? ? @Id @GeneratedValue? // 主鍵自動生成

? ? private long id;


? ? @OneToOne @JoinColumn? // 負責維護外鍵

? ? private IdCard idcard;

}

@Entity

public class IdCard {

? ? @Id @GeneratedValue // 主鍵自動生成

? ? private long id;


? ? @OneToOne(mappedBy="idcard")? // 甩手掌柜

? ? private Person person;

}

主鍵關聯:

@Entity

public class Person {

? ? @Id? ? ? // 主鍵*不要*自動生成!!

? ? private long id;


? ? @OneToOne // 負責維護外鍵,將外鍵映射到主鍵。即將另一張表的外鍵映射到本表的主鍵。

? ? @MapsId @JoinColumn(name = "id")

? ? private IdCard idcard;

}

@Entity

public class IdCard {

? ? @Id @GeneratedValue // 主鍵自動生成

? ? private long id;


? ? @OneToOne(mappedBy="idcard")? // 甩手掌柜

? ? private Person person;

}

Embed

這不屬于關聯關系,只是一種包含:

@Entity

class Person {

? ? @Embedded

? ? private Name name;

}

@Embeddable

class Name {

? ? String firstName;

? ? String lastName;

}

Inheritance

SINGLE_TABLE

將所有的東西塞進 一張表 中,即所有的子類跟父類使用一張表, 在這張表中使用“區別列”(DiscriminatorColumn)來區分各個類。

這是默認的繼承策略。

@Entity

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)

@DiscriminatorColumn(name = "xxx") // 可以定制分割列的名字

public class Animal {}

@Entity

@DiscriminatorValue("狗") // 可以定制

public class Dog extend Animal {}

它并不符合范式,但也有自己的優點:

使用了區別的列

只使用了一張表,所以查詢速度快

缺點:子類的獨有列,不能添加唯一/非空約束

缺點:太多冗余字段

JOINED

是一種完全“符合范式”的設計:

將所有共有的屬性提取到父表中

僅將子類特有的屬性保存到子表中

父表跟子表通過外鍵的方式建立關系

如果查詢子表的詳細數據,通過關聯查詢關聯相關表即可

@Entity

@Inheritance(strategy = InheritanceType.JOINED)

public class Animal { }

@PrimaryKeyJoinColumn(name = "xxxxid")? // 可以定制關聯主鍵

public class Dog extend Animal { }

總結:

優點:沒有任何冗余

缺點:查詢的效率低,因為需要關聯各張表

TABLE_PER_CLASS(union)

每個類對應一張表,大家互相隔離,各自為政!

@Entity

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

public class Animal { }

@Entity

public class Dog extend Animal { }

總結:

優點:獨立,自由,查詢快

如果只查詢子類,那么不需要任何關聯;但如果查詢父類的話,需要使用 Union 關聯各表

缺點:存在冗余字段

缺點:如果要更新父類中的字段,每個子表都需要去更新

MappedSuperclass

如果父類不是 Entity,只是為子類提供公共屬性,那么,將其注解為 @MappedSuperclass 即可。

@MappedSuperclass

abstract public class Person {

? ? @Id private long id;

? ? @Column private String name;

}

@Entity

public class Girl extend Person {

? ? private String wechat;

}

@Entity

public class Boy extend Person {

? ? private String address;

}

**

級聯(Cascade)

比如說,一個部門有很多員工,它們是多對一的關系。如果我們要刪除1號部門:

Dept d = session.load(Dept.class, 1L);

session.delete(d);

我們會刪除失敗并得到一個異常,因為部門被員工數據引用,所以要刪除部門前,需要先將引用到部門的所有員工刪掉。

如果我們不想手動刪除部門內部員工,那么可以采取 *級聯操作*,即對 Dept 實體類中的 emps 屬性這樣設置:

@OneToMany(mappedBy="dept", cascade=CascadeType.REMOVE)

private List<Emp> emps = new ArrayList<>();

那么,再去執行刪除操作的時候,部門、連帶它所有的員工,都會被刪除。一步到位,快速絕倫。

除了刪除操作,級聯的類型還有:

CascadeType.PERSIST

CascadeType.MERGE

CascadeType.REFRESH

CascadeType.ALL (快捷方式,代指所有)

雖然 cascade 會讓我們的代碼更簡介,使用更方便。但是,在工業環境中,*不建議使用 cascade 設置*。

刪除數據的方式

第一種方法:

// 優點:快速簡潔

// 缺點:不能關聯刪除

Product product = new Product();

product.setId(44L);

session.delete(product);

第二種方法:

// 優點,能關聯刪除

// 缺點,不直接

Product product = session.load(Product.class, id); // load, not get

session.delete(product);

第三種方法:

// 優點,更靈活

// 缺點,跟第一種方式一樣,不能刪除關聯

int result = session

? ? .createQuery("delete Product where id = :id")

? ? .setParameter("id", 44L)

? ? .executeUpdate();

查詢

get/load

根據主鍵進行查詢。這是最基本,最高效的一種查詢手段。

Query

//// 基本語法

String hql = "from xxx where yyy";

Query query = session.createQuery(hql);

query.setParameter("aaa", "bbb");

query.uniqueResult();

// 可以用鏈式語法簡化語句

session.createQuery("from xxx where yyy").setParameter("aaa", "bbb").uniqueResult();

//// select 語句 和 返回值

from Emp e where e.name = 'x';? ? ? ? ? // 默認不需要寫 select, 那么會將結果封裝到 Emp 對象中

select e from Emp e where e.name = 'x';? // 上面的語句,跟此句是一致的

select name from Emp;? ? ? ? ? ? ? ? ? ? // 返回值:Object

select name, salary from Emp;? ? ? ? ? ? // 返回值:Object[]

select new list(name) from Emp;? ? ? ? ? // 返回值:ArrayList

select new map(name, salary) from Emp;? // 返回值:HashMap

select new map(name as name, salary as sal) from Emp; // 定制 key 值

select new Boy(name, salary) from Emp;? // 返回值:Boy 對象

//// 得到返回結果

session.createQuery("from Book", Book.class).uniqueResult();

session.createQuery("from Book", Book.class).list();

session.createQuery("from Book", Book.class).iterate().next();

// 過濾操作

session.createFilter(customer.getOrders(), "where price > 5000").list();

//// 聚合函數及其他運算符的使用

// 返回值:Object[]

select max(salary), avg(salary), sum(salary) from Emp;

// group by

select max(salary), avg(salary), sum(salary) from Emp e group by e.department;

// 將結果封裝到 map 中

select new map(max(salary) as maxsal, avg(salary) as avgsal, sum(salary) as sumsal) from Emp e group by e.department;

// 運算符和函數

select sum(salary + nvl(commission, 0)) as res from Emp;

//// join

// Query 不能使用 JOIN 抓取策略。Query 默認使用 select 語句進行關聯數據的加載。

// 如果想強制使用 join 語句,需要通過 hql 語句指定:

/// 1. 隱式設置

from Emp e where e.department.location = 'NEW YORK';

/// 2. 顯式調用

// fetch 決定最后結果的形式:

//? - 有 fetch: [Emp, Emp, ...]

//? - 無 fetch: [[Emp, Dept], [Emp, Dept]]

from Emp e join e.dept where e.name = 'xxx';

from Emp e left join e.dept where e.name = 'xxx';

from Emp e left join fetch e.dept where e.name = 'xxx';

//// 分頁、總行數

long count = session.createQuery("select count(*) from Emp", Long.class).uniqueResult();

long count = session.createQuery("select count(*) from Emp", Long.class).iterate().next();

// oracle: rownum/row_number()

// sqlserver: top/row_number()

// mysql/sqlite: limit x offset y

// hibernate 通過下面語句屏蔽了底層細節:

/// 從 80 行開始,取 5 行記錄

session.createQuery("from xxx").setFirstResult(80).setMaxResults(5).list();

//// delete & update

delete Emp where name = :oldName;

update Emp set name = :newName where id = :id;

// 級聯操作的設置,對 Query 也是無效的,比如,想刪除一個部門,需要先刪除員工,再刪部門:

delete Emp e where e.department.deptno = '#DN';

delete Dept where deptno = '#DN';

Criteria

Criteria,標準、規范,它是 Criterion 的復數形式。

優勢:

面向對象

不用拼接sql,方便擴展

統一性,跨數據庫

Criteria 接口: 表示特定類的一個查詢

Criterion 接口: 表示一個限定條件

示例:

// Session 是 Criteria 的工廠

// Criterion 的主要實現由 Example、Junction 和 SimpleExpression

// Criterion 一般通過 Restrictions 提供的工廠方法獲得

List<Emp> emps = session.createCriteria(Emp.class) // 創建

? ? .add( Restrictions.like("name", "K%") )? ? ? ? // 模糊

? ? .add( Restrictions.gt( "salary", 2000F ) )? ? // 大于

? ? .addOrder( Order.desc("salary") )? ? ? ? ? ? ? // 排序-1

? ? .addOrder( Order.desc("commission") )? ? ? ? ? // 排序-2

? ? .list();

// 約束可以按邏輯分組

List<Emp> emps = sess.createCriteria(Emp.class)

? ? .add( Restrictions.like("name", "K%") )

? ? .add( Restrictions.or( Restrictions.ge( "salary", 3000F ),

? ? ? ? ? ? ? ? ? ? ? ? ? Restrictions.isNotNull("commission") ) )

? ? .list();

// Property~Example 是添加約束的另兩種方法

List<Emp> emps = session.createCriteria(Emp.class)

? ? .add(Property.forName("name").eq("KING")) // Property

? ? .add(Example.create(king))? ? // 將 king 上的數據封裝成條件

? ? .list();

//// 關聯查詢

List<Emp> emps = session.createCriteria(Emp.class)

? ? .createCriteria("depts")? ? ? // vs. createAlias

? ? .add( Restrictions.eq("location", "NEW YORK") )

? ? .list();

//// Projections 提供投影查詢,并能分組聚合

// 投影條件

ProjectionList projectionList = Projections.projectionList()

? ? .add( Projections.property("dept") )

? ? .add( Projections.rowCount() )

? ? .add( Projections.max("salary") )

? ? .add( Projections.sum("salary", "sum" ) )

? ? .add( Projections.groupProperty("dept") );

// 查詢結果

List<Object[]> rs = session.createCriteria(Emp.class)

? ? .setProjection( projectionList )

? ? .addOrder( Order.asc("sum") )

? ? .list();

NativeSQL

基本語法,默認的返回的結果為 Object[]:

session.createNativeQuery("select ename, sal from emp").list();

session.createNativeQuery("select * from emp").list();

session.createNativeQuery("select * from emp e, dept d where e.deptno=d.deptno and d.loc=:loc")

? ? .setParameter("loc", "NEW YORK")

? ? .list();

可以通過 addScalar() 設置返回類型,并限定結果:

// 下面的查詢,得到的結果為 Object[], 包含兩個元素:0:id / 1:name

session.createNativeQuery("select * from emp where id=9999")

? ? .addScalar("empno", StandardBasicType.INTEGER)

? ? .addScalar("ename", StandardBasicType.STRING)

? ? .list();

也可以將結果封裝到 Entity(實體類) 中:

// simplest

session.createNativeQuery("select * from emp where sal > 2000")

? ? .addEntity(Emp.class).list();

// with alias

session.createNativeQuery("select e.* from emp e where sal > 2000")

? ? .addEntity("e", Emp.class)

? ? .list();

// multiple

session.createNativeQuery("select e.*, d.* from emp e join dept d using (deptno) where e.sal > 2000")

? ? .addEntity("e", Emp.class)

? ? .addEntity("d", Dept.class)

? ? .list();

將結果封裝到普通對象(非實體類)。注意,必須要使用 addScalar() 設置字段:

List<Person> persons = session.createSQLQuery("select * from emp")

? .addScalar("ename", StandardBasicType.INTEGER)

? .addScalar("salary", StandardBasicType.FLOAT)

? .setResultTransformer(Transforms.aliasToBean(Person.class))

? .list();

NameQuery

Query Strategy

一個實體類對象,里面有各個屬性,這些屬性的值可能不是在同一張表中。

為了效率,需要有一定加載策略,主要兩個方面:

when,屬性數據的加載時機,是否在加載這個實體類的時候就立刻加載。

how,通過什么樣的語句加載,select/join/其他。

比如,有一個實體類,叫 Girl:

@Entity

public class Girl {

? ? // 基本數據,保存在 girl 表中的數據:

? ? //? select id, name from girl;

? ? // 這種數據的默認加載機制是:

? ? //? 1. when: 立刻加載(EAGER)

? ? //? 2. how:? SELECT 語句

? ? @Id private long id;

? ? private String name;

? ? // 關聯數據,單結果,保存在 boy 表中的:

? ? //? select * from boy where id='我的老父親,您的編號';

? ? // 這種方式的默認加載機制是:

? ? //? 1. when: 立刻加載(EAGER)

? ? //? 2. how:? LEFT JOIN 連接

? ? @ManyToOne

? ? private Boy father;

? ? // 關聯數據,結果集,保存在 bag 表中的

? ? //? select * from bag where big_owner='女孩的編號';

? ? // 這種屬性數據的默認加載機制是:

? ? //? 1. when: 延遲加載(LAZY)

? ? //? 2. how:? SELECT 語句

? ? @OneToMany(mappedBy = "girl")

? ? private Set<Bag> bags = new HashSet();

}

如果我們調用 session.load(Girl.class, 1L), 會加載編號為 1 的女孩的數據。

她的數據分為三種:

基本數據,包含在 girl 表中的,比如 =id/name=。

關聯數據/XtoOne,比如 father 屬性。

關聯數據/XtoMany,比如 bags 屬性。

可以通過 fetch 屬性/@Fetch 注解 定制加載策略,分別對應 when/how, 例:

@ManyToOne(fetch = FetchType.EAGER)// 定義加載的時機(when)

@Fetch(FetchMode.SELECT)? ? ? ? ? // 定義加載語句的樣式(how)

private Boy boyfriend;

如果 when 為 EAGER=,默認的 how 為 =FetchMode.JOIN

如果 when 為 EAGER=,可以定制使 how 為 =FetchMode.SELECT/SUBSELECT

如果 how 為 JOIN, 那么 when 只能是 EAGER

如果設置了 hibernate.default_batch_fetch_size 或在實體類/集合上標注了 @BatchSize, 會對 LAZY 屬性加載采取批量優化。

@Fetch(FetchMode.SUBSELECT) 可以優化 HQL 返回的列表的關聯數據查詢語句

*JOIN 策略對 Query 查詢無效*,如需關聯查詢,在語句中顯式調用 join 語句!

N+1 問題

比如,如果:

打印出編號大于10的部門中的所有員工姓名。

那么,語句大致如此:

String hql = "from Dept where depto > :dn";

List<Dept> depts = session.createQuery(hql, Dept.class)

? ? .setParameter("dn", 10)

? ? .list();

for(Dept dept: depts) {

? ? for(Emp emp : dept.getEmps()) {

? ? ? ? System.out.printf("部門: %s, 姓名: %s\n",

? ? ? ? ? ? ? ? ? ? ? ? ? dept.getName(),

? ? ? ? ? ? ? ? ? ? ? ? ? emp.getName());

? ? }

}

因為 @OneToMany 默認是 Lazy + SELECT 策略,所以,每個部門的員工只有使用的時候才去查詢。

這就導致了上面的語句發送很多條 select 語句(N+1),嚴重影響效率。

*這就是 N+1 問題*。

解決方案有主要有下面幾種:

在 hql 語句中,使用 join 語句進行關聯查詢。

將 Dept#emps 的策略設置為 SUBSELECT 方式。

采取批量抓取的優化方式(BatchSize),即在 Dept#emps 上面加上注解: =@BatchSize(size=n)=。

使用二級緩存。

緩存(Cache)

緩存分為三種:事務范圍;應用范圍;集群范圍。

二級緩存是應用范圍的緩存機制。適合放入二級緩存的數據:

很少修改,不會修改,或不允許被更改的數據(常量數據)

不是很重要,允許偶爾出錯的數據

而一些重要的數據或者修改頻繁的數據,是不適合放到緩存里的。

配置使用二級緩存過程:

加入 JAR 包支持:

"org.hibernate:hibernate-ehcache:5.2.11.Final"


配置 /ehcache.xml [可選]

在 hibernate.cfg.xml 中啟用:

<prop key="hibernate.cache.use_second_level_cache">true</prop>

<prop key="hibernate.cache.use_query_cache">true</prop>

<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</prop>


配置要被緩存的類或集合

@Cachable

@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)


使用示例

session.createQuery("from Employee where id=7782").setCacheable(true).list();

session.createQuery("from Employee where id=7782").setCacheable(true).list();

session.createQuery("from Employee where id=7782").setCacheable(true).list();


鎖(Lock)

Hibernate 中,設置鎖定有下面三種方式:

session.load(Male.class, 1L, LockMode.WRITE)

session.lock(m, LockModeType.WRITE);

session.createQuery(hql).setLockMode(LockModeType.PESSIMISTIC_WRITE);

Hibernate 中鎖的類型,分為兩種:

悲觀鎖。使用數據庫底層的 for update 語句。數據會被鎖定,直到事務結束。

樂觀鎖。使用實體類中的額外字段( @Version )。它不會真正在數據上加鎖,而是用版本號區別記錄的不同。

-- 它會在初次讀取數據時將 version 一起讀出,得到【版本號】,比如 10

-- 等到提交數據的時候,發送下面語句:

update xxx set version = 10 + 1, ... where id = 2 and version = 10;

-- 如果數據被別人修改過,那么 version 已經不是 10,所以上面語句不會更新到任何數據。

-- 同樣,hibernate 會拋出下面異常:

---- javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1

-- 從而防止了數據的修改沖突。


悲觀鎖更適用于修改頻率大,讀取不多的數據。樂觀鎖適用于修改非常少,但讀取特別多的數據。悲觀鎖需要耗費更多資源。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有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,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容