GreenDao使用

简介

GreenDao是一个简化数据库开发的ORM(Object Ralativational Mapping)来(对象关系映射)源框架,就是通过操作对象间接操作数据库方式。

GreenDao的使用说明
  • gradle 配置中参数说明

    1. schemaVersion:指定数据库schema版本号,迁移等操作会用到。
    2. targetGenDir:生成数据库文件的目录。
    3. daoPackage:dao的包名,包名默认是entity所在的包。
  • 实体类中注解说明

    1. @Entity:表明这个实体类会在数据库中生成一个与之相对应的表,其中
      nameInDb:可以自定义表名,表明该实体对应数据库中的那张表,默认为实体类名。
      indexes:定义索引,这里可跨越多个列。
      createInDb:如果是有多个实体都关联这个表,可以把多余的实体里面设置为false避免重复创建(默认是true)。
      schema:一个项目中有多个schema时,表明要让这个dao属于哪个schema。
      active:是否应该生成更新/删除/刷新方法。如果Entity定义了 @ToOne 或 @ToMany关系,那么独立于该值是有效的。意为是否支持实体类之间update,refresh,delete等操作。

    2. @Id:对应数据表中的主键,是一条数据的唯一标识。如果实体没有声明主键,默认创建Long类型主键"_id"自增。使用Long类型主键时可以通过@Id(autoincrement = true)设置为自增。

    3. @Property(nameInDb = "USER_NAME" ):可以自定义字段名,注意外键不能使用该属性。表明这个属性对应数据表中的 USER_NAME 字段。

    4. @NotNull:该属性值不能为空。

    5. @Transient:该属性不会被存入数据库中。

    6. @Unique:表明该属性在数据库中只能有唯一值。

    7. @Index:创建一个索引。通过name设置索引别名,也可以通过unique给索引添加约束。

    8. @Convert:指定一个PropertyConverter用于支持自定义类型(没用过)。

    9. @ToOne:定义自己与一个实体对象的关系。

    10. @ToMany:定义自己与多个实体对象的关系(可不与@ToOne联合使用)。@ToMany的属性referencedJoinProperty,类似于外键约束。

    11. @JoinProperty:对于更复杂的关系,可以使用这个注解标明目标属性的源属性,起关联作用。

    12. @JoinEntity:如果你在做多对多的关系,有其他的表或实体参与,可以给目标属性添加这个额外的注解。

    13. @OrderBy:指定{@ToMany}关系的相关集合的排序,(propertyA, propertyB)默认为按主键ASC排序。

    14. @Generated:这个是build后greendao自动生成的,这个注解理解为防止重复,每一块代码生成后会加个hash作为标记。

表关联(一对一,一对多)举例

现有两张表:作者表和文章表,文章表中有一列authorId对应作者表的id。希望可以通过文章的id查询作者信息。

一对一:

@Entity(nameInDb = "CsYdd",createInDb = true,indexes = {@Index(value = "id",unique = true)}) //value值必须是主键
public class Article {

  @Id(autoincrement = true) 
  public long id; //id是主键

  @SerializedName("is_FORCEEEE") //json解析换名
  @Property(nameInDb = "is_FORCEEEE2") //db中换名
  public boolean isFirst;

  private long authId; //该authId就是Author的主键id,注意类型一定要相同,这里都为long类型
  @ToOne(joinProperty = "authId")  
  private Author bean;
}

一对多:

@Entity(nameInDb = "CsYEE",createInDb = true,indexes = {@Index(value = "id",unique = true)})
public class Author {

  @Id
  public long id;

  @ToMany(referencedJoinProperty = "authId") //authId是Article中与Author关联的参数名
  private List<Article> list;

}

代码调用:
    ArticleDao dao = daoSession.getArticleDao();
    Article load = dao.load(id);
    Author bean = load.getBean();


    AuthorDao dao = daoSession.getArticleDao();
    Author load = dao.load(id);
    List<Article> list = load.getList();

多对多:(例如:老师与学生关系)

@Entity public class Teacher {

  @Id(autoincrement = true) private Long id;
  private String name;
  // 对多,@JoinEntity注解:entity 中间表;sourceProperty 实体属性;targetProperty 外链实体属性
  @ToMany
  @JoinEntity(entity = JoinStudentToTeacher.class, sourceProperty = "tid", targetProperty = "sid")
  private List<Student> students;
}

@Entity public class Student {

  @Id private Long id;
  private String name;

  //对多,@JoinEntity注解:entity 中间表;sourceProperty 实体属性;targetProperty 外链实体属性
  @ToMany
  @JoinEntity(entity = JoinStudentToTeacher.class, sourceProperty = "sid", targetProperty = "tid")
  private List<Teacher> teachers;
}

//借助的中间关系网
@Entity public class JoinStudentToTeacher {
  @Id(autoincrement = true) private Long id;
  //和teacher关联的id
  private Long tid;
  //和student关联的id
  private Long sid;
}

greenDao方式测试

  private void cs_toMany_toMany() {
    TeacherDao TeacherDao = MyApplication.getDaoSession().getTeacherDao();
    StudentDao studentDao = MyApplication.getDaoSession().getStudentDao();
    JoinStudentToTeacherDao spDao = MyApplication.getDaoSession().getJoinStudentToTeacherDao();

    Teacher p1 = new Teacher();
    p1.setId(1L);
    p1.setName("teacherOne");
    Teacher p2 = new Teacher();
    p2.setId(2L);
    p2.setName("teacherTwo");
    Teacher p3 = new Teacher();
    p3.setId(3L);
    p3.setName("teacherThree");

    Student stu1 = new Student();
    stu1.setId(1L);
    stu1.setName("stu1");
    Student stu2 = new Student();
    stu2.setId(2L);
    stu2.setName("stu2");
    Student stu3 = new Student();
    stu3.setId(3L);
    stu3.setName("stu3");
    
    // 模拟 多对多关系,建立关系网
    //p1有stu1 stu2 stu3 ,那么反过来stu123都有p1
    JoinStudentToTeacher sp1 = new JoinStudentToTeacher();
    sp1.setTid(1L);
    sp1.setSid(1l);
    JoinStudentToTeacher sp2 = new JoinStudentToTeacher();
    sp2.setTid(1L);
    sp2.setSid(2L);
    JoinStudentToTeacher sp3 = new JoinStudentToTeacher();
    sp3.setTid(1L);
    sp3.setSid(3L);

    //p2有stu1 stu2 stu3 ,那么反过来stu123都有p2
    JoinStudentToTeacher sp4 = new JoinStudentToTeacher();
    sp4.setTid(2L);
    sp4.setSid(1L);
    JoinStudentToTeacher sp5 = new JoinStudentToTeacher();
    sp5.setTid(2L);
    sp5.setSid(2L);
    JoinStudentToTeacher sp6 = new JoinStudentToTeacher();
    sp6.setTid(2L);
    sp6.setSid(3L);

    TeacherDao.insertOrReplace(p1); //也可用insertOrReplaceInTx()方式直接插入List
    TeacherDao.insertOrReplace(p2);
    TeacherDao.insertOrReplace(p3);

    studentDao.insertOrReplace(stu1);
    studentDao.insertOrReplace(stu2);
    studentDao.insertOrReplace(stu3);

    spDao.insertOrReplace(sp1);
    spDao.insertOrReplace(sp2);
    spDao.insertOrReplace(sp3);
    spDao.insertOrReplace(sp4);
    spDao.insertOrReplace(sp5);
    spDao.insertOrReplace(sp6);

    List<Teacher> Teachers = TeacherDao.queryBuilder().build().list();

    for (Teacher Teacher : Teachers) {
      Log.d("GreenDao","Teacher:"+Teacher.toString());
    }
  }

容易忽略的:
1.如果是list或者数组类型的属性XX,只有getXX方法,没有setXX方法,但可用GreenDao的insertOrReplaceInTx()方法设置。
2.如果你要打印 @ToOne对应的bean类时,要先调用getXXX方法,而不是直接打印对象(因为没有赋值,而是在getXX的时候才赋值的)
3.以上三种方法,删除时候都不会关联删除。

GreenDao的其他方法

1 inser() 插入一条数据
2 insertInTx() 批量插入数据,比如List
3 insertOrReplace() 插入数据,传入的对象主键如果存在于数据库中,有则更新,否则插入
4 insertOrReplaceInTx() 批量插入数据,同上
5 save()比insertOrReplace()更有效率,但其会先判断对象是否有Key值,有则采用update方式更新(若在数据库中无任何数据,则此数据不会保存到数据库),无则采用insert方式插入。
6 insertWithoutSettingPk() 不需要设置主键属性,因为仅对主键为Long类型自增有效,String类型主键会Error。
7 deleteAll() 删除相应表的全部数据(清空相应表)。
8 deleteByKey() 删除给定PK的实体。
9 deleteByKeyInTx(K... keys) 使用事务删除数据库中给定键的所有实体。批量删除。
10 delete(T entity) entity的id属性必须有值,否则报错。就是删除与entity主键值相同的数据,其他数据值与表中数据值不一致时,会删除。
11 deleteInTx(T... entities) 使用事务删除数据库中给定的实体。批量删除。每个entity的id属性必须有值,否则报错。
12 update(T entity) entity的id属性必须有值,否则报错。若entity的id值表中不存在时,不会报错。
13 updateInTx(T... entities) 使用事务更新数据库中给定的实体。批量更新。
14 load(K key) 加载给定PK的实体。传入:key or null。返回:该实体 或 null。
15 loadByRowId(long rowId) 加载给定RowId的实体。传入:key or null。返回:该实体 或 null。
16 loadAll() 从数据库加载所有可用的实体。
17 unique() 返回非空的结果,否则抛出异常。
18 uniqueOrThrow() 返回非空的结果,否则抛出异常。
19 queryBuilder()用法示例如下:

    mCustomerBeanDao.queryBuilder()
        .limit(3) //取前3条数据
        .offset(2) //偏移量(必须与limit()配合使用)
        .distinct() //去重
        .orderDesc(CustomerBeanDao.Properties.Adcode) //降序---descending
        .orderAsc(CustomerBeanDao.Properties.District) //升序---ascending
        .orderCustom(CustomerBeanDao.Properties.Adcode,"") //偏好排序---custom---定制
        .orderRaw("") //使用原始sql排序
        .preferLocalizedStringOrder() //使用本地化排序
        .stringOrderCollation("COLLATE NOCASE") //orderAsc和orderAsc自定义排序
        .where(CustomerBeanDao.Properties.CustName.like("dfsfs%")) //查询条件,参数间关系为 and
        .whereOr(CustomerBeanDao.Properties.AreaName.like("dsfsf%") //查询条件,参数间关系为 or
            ,CustomerBeanDao.Properties.Address.eq("")) //n个查询条件
        .build()
        .list();

查询条件有:

    between(Object value1, Object value2): BETWEEN … AND …
    eq(Object value): equal (‘=’)
    notEq(Object value): not equal (‘<>’)
    gt(Object value): than (‘>’)  ---- greater than
    lt(Object value): less than (‘<’) ---less than
    ge(Object value): greater or equal (‘>=’)
    le(Object value): less or equal (‘<=’)
    like(String value): LIKE
    isNotNull(): IS NOT NULL
    isNull(): IS NULL
    in(Object… inValues): IN (…, …, …)
    notIn(Object… notInValues): NOT IN (…, …, …)
    in(Collection< ?> inValues): IN (…, …, …)
    notIn(Collection< ?> notInValues): NOT IN (…, …, …)

queryBuilder.or()用法,借用一个网上的例子说明:
获取1970年10月或之后出生的名为“Tom”的用户信息(对应的sql:where name = "Tom" and ((year = 1970 and month >=10) or (year >1970)))

QueryBuilder<User> qb = userDao.queryBuilder();
//要特别注意qb.or(,qb.and())中嵌套的层级结构
qb.where(Properties.FirstName.eq("Tom"),qb.or(Properties.YearOfBirth.gt(1970),qb.and(Properties.YearOfBirth.eq(1970),Properties.MonthOfBirth.ge(10)))); 
List<User> youngJoes = qb.list();

20 or\and(WhereCondition... condMore) 在where或whereOr中使用,condMore参数间关系为or\and
21 list() 执行查询并将返回一个包含所有entity的list为结果,直接加载在内存中(存在缓存问题)。
22 listLazy() 当真正用到数据时(访问entity的属性时),才会查询数据库。
23 listLazyUncached() 也实现了懒加载技术,但是返回结果list没有保存在内存中,没法复用,每次都会向数据库查询真正的数据。
24 listIterator() 执行查询并将结果作为列表迭代器返回;确保关闭它以关闭底层游标。一旦迭代器完全迭代,游标就会关闭。
25 queryRawCreate() 基于给定的原始SQL创建一个可重复的{查询}对象,可以在这里传递任何WHERE子句和参数。
26 queryRaw() 一个原始样式查询,可以在这里传递任何WHERE子句和参数。
27 queryRawCreateListArgs() 基于给定的原始SQL创建一个可重复的{查询}对象,您可以在这里传递任何WHERE子句和参数。
28 多线程查询:
29 rx():返回在IO线程发射事件的 Observables
30 rxPlain():返回没有设置无线程调度的 Observables
31 detach() 将一个实体从会话中拆分。后续查询结果不会返回此对象。
32 detachAll() 所有的 同上
33 refresh() 通过从数据库重新加载重置该实体的所有本地更改属性。
34 daoSession.clear() 清除daoSession的缓存
35 dao.detachAll() 清除指定dao类的缓存
36 QueryBuilder.LOG_SQL = true; //打印SQL标志
37 QueryBuilder.LOG_VALUES = true; //打印参数标志

执行原始SQL查询举例:(目的:根据一个表的数据查另一个表中数据)
方法一,被拼接在where之后添加查询条件:

Query<User> query = userDao.queryBuilder().where(new StringCondition("_ID in " +"(SELECT USER_ID FROM USER_MESSAGE WHERE READ_FLAG = 0)")).build();

方法二,被拼接在查询语句的SELECT和实体之后:

Query<User> query = userDao.queryRawCreate(", Table2 G WHERE G.NAME=? AND T.GROUP_ID=G._ID", "admin"); //注意:实体User默认别名为 T,Table2前边的","是表间的分隔符

方法三,Database方式:

Cursor cursor = daoSession().getDatabase().rawQuery(sql,selectionArgs);

数据库升级

Android数据库升级涉及到的问题:
1. 若不升级,是不能增加新表,也不能修改或增加旧表字段的,除非卸载APP重装(即删除数据库并重建,但丢数据),否则会crash;
2. 升级版本号,触发onUpgrade()方法,可手动进行新旧表的增、删、改等操作;
3. 实现数据库升级方案:a.采用增加新表,同时修改或增减旧表字段;b.旧表改名为临时表,新建表,导入数据,如上方式;

public final class MigrationHelper {
 
  public static void migrate(SQLiteDatabase sqliteDatabase, Class<? extends AbstractDao<?, ?>>... daoClasses) {
    StandardDatabase db = new StandardDatabase(sqliteDatabase);
    generateNewTablesIfNotExists(db, daoClasses);
    generateTempTables(db, daoClasses);
    dropAllTables(db, true, daoClasses);
    createAllTables(db, false, daoClasses);
    restoreData(db, daoClasses);
  }
 
  public static void migrate(StandardDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
    generateNewTablesIfNotExists(db, daoClasses);
    generateTempTables(db, daoClasses);
    dropAllTables(db, true, daoClasses);
    createAllTables(db, false, daoClasses);
    restoreData(db, daoClasses);
  }
 
  private static void generateNewTablesIfNotExists(StandardDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
    reflectMethod(db, "createTable", true, daoClasses);
  }
 
  private static void generateTempTables(StandardDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
    for (int i = 0; i < daoClasses.length; i++) {
      DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
      String tableName = daoConfig.tablename;
      String tempTableName = daoConfig.tablename.concat("_TEMP");
      StringBuilder insertTableStringBuilder = new StringBuilder();
      insertTableStringBuilder.append("CREATE TEMP TABLE ").append(tempTableName);
      insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");
      db.execSQL(insertTableStringBuilder.toString());
    }
  }
 
  private static void dropAllTables(StandardDatabase db, boolean ifExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
    reflectMethod(db, "dropTable", ifExists, daoClasses);
  }
 
  private static void createAllTables(StandardDatabase db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
    reflectMethod(db, "createTable", ifNotExists, daoClasses);
  }
 
  /**
   * dao class already define the sql exec method, so just invoke it
   */
  private static void reflectMethod(StandardDatabase db, String methodName, boolean isExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
    if (daoClasses.length < 1) {
      return;
    }
    try {
      for (Class cls : daoClasses) {
        Method method = cls.getDeclaredMethod(methodName, Database.class, boolean.class);
        method.invoke(null, db, isExists);
      }
    } catch (NoSuchMethodException e) {
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    }
  }
 
  private static void restoreData(StandardDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
    for (int i = 0; i < daoClasses.length; i++) {
      DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
      String tableName = daoConfig.tablename;
      String tempTableName = daoConfig.tablename.concat("_TEMP");
      // get all columns from tempTable, take careful to use the columns list
      List<String> columns = getColumns(db, tempTableName);
      ArrayList<String> properties = new ArrayList<>(columns.size());
      for (int j = 0; j < daoConfig.properties.length; j++) {
        String columnName = daoConfig.properties[j].columnName;
        if (columns.contains(columnName)) {
          properties.add(columnName);
        }
      }
      if (properties.size() > 0) {
        final String columnSQL = TextUtils.join(",", properties);
 
        StringBuilder insertTableStringBuilder = new StringBuilder();
        insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
        insertTableStringBuilder.append(columnSQL);
        insertTableStringBuilder.append(") SELECT ");
        insertTableStringBuilder.append(columnSQL);
        insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
        db.execSQL(insertTableStringBuilder.toString());
      }
      StringBuilder dropTableStringBuilder = new StringBuilder();
      dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
      db.execSQL(dropTableStringBuilder.toString());
    }
  }
 
  private static List<String> getColumns(StandardDatabase db, String tableName) {
    List<String> columns = null;
    Cursor cursor = null;
    try {
      cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 0", null);
      if (null != cursor && cursor.getColumnCount() > 0) {
        columns = Arrays.asList(cursor.getColumnNames());
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (cursor != null)
        cursor.close();
      if (null == columns)
        columns = new ArrayList<>();
    }
    return columns;
  }
}

然后在初始化时用到的 UpgradeHelper 类的onUpgrade方法中添加:

public class UpgradeHelper extends DaoMaster.OpenHelper {
  public UpgradeHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
    super(context, name, factory);
  }
 
  @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    MigrationHelper.migrate(db, PortraitPhotoDao.class, FeatureDao.class, PhotoJoinUserDao.class,
        UserPhotoDao.class);
  }
}

升级方案参考于:https://blog.csdn.net/zhanlv/article/details/82425709

SQL中常用命令

对表结构操作命令:

  • 修改表名:
    rename table 旧表名 to 新表名

  • 修改表选项:
    alter table 表名 表选项 [=] 新值

  • 新增字段:
    alter table 表名 add [column] 新字段名 列类型[列属性] [位置first/after 字段名](默认加到表的最后面)字段位置:字段想要存放的位置,first:在某某之间(最前面),第一个字段;after 字段名:放在某个具体的字段后(默认的)

  • 删除字段
    alter table 表名 drop 字段名

  • 修改字段名
    alter table 表名 change 旧字段名 新字段名 字段类型[列属性] [位置属性] (修改名字需要修改字段类型)

  • 修改字段类型(属性):
    alter table 表名 modify 字段名 新类型 [新属性] [新位置]

  • 删除表结构
    drop table 表名[,表名2....]; 可同时删除多个表

  • 小结:对表结构操作时,都需要用特定字段如alter,并且需要特指table及名称,但下边对数据操作,则不需要特指table。

对表中数据操作命令:

  • 增:insert into 表名(字段列表,当插入所有字段时可省略) values(对应字段列表)
  • 删:delete from 表名 [where条件] 如果没有where条件,意味着系统会自动删除该表所有数据(慎用)
  • 改:update 表名 set 字段名 = 新值 [where条件];
  • 查:select 字段列表 from 表名 from 表名 where 字段名=值 and 字段名=值 order by name DESC, age ASC //字段列表使用‘ , ’隔开

需要注意:

写的顺序:select ... from... where.... group by... having... order by..
执行顺序:from... where...group by... having.... select ... order by...

  • group by数据分组举例:

方法一:
SELECT * FROM (select a.* from new_table a inner join new_table b on a.name = b.name group by name order by time desc) c GROUP BY c.kefuid

方法二:
SELECT * FROM (select * from new_table group by id order by time desc) c GROUP BY c.kefuid

  • having条件语句:
    having与where有类似作用,当WHERE 关键字无法与合计函数一起使用时使用:
    例如:我们希望查找订单总金额少于 2000 的客户:

SELECT Customer,SUM(OrderPrice) FROM Orders GROUP BY Customer HAVING SUM(OrderPrice)<2000

然而,现在我们希望查找客户 "Bush" 或 "Adams" 拥有超过 1500 的订单总金额,则需要这样:

SELECT Customer,SUM(OrderPrice) FROM Orders WHERE Customer='Bush' OR Customer='Adams'
GROUP BY Customer HAVING SUM(OrderPrice)>1500

  • SUM () 函数返回数值列的总数:

SELECT COUNT(column_name) FROM table_name //函数返回指定列的值的数目值(NULL 不计入)
SELECT COUNT(*) FROM table_name //函数返回表中的记录数目值
SELECT COUNT(DISTINCT column_name) FROM table_name //函数返回指定列的不同值的数目值

  • AVG()取平局值:如我们希望找到 OrderPrice 值高于 OrderPrice 平均值的客户

SELECT Customer FROM Orders WHERE OrderPrice>(SELECT AVG(OrderPrice) FROM Orders)

  • inner join

nner join(等值联接) 从两表中抽取出联结字段相等的行数据,整合到一起后返回。
left join(左联接) 将左表中的所有数据和右表中联结字段相等的数据,整合到一起后返回。
right join(右联接) 将右表中的所有记录和左表中联结字段相等的数据,整合到一起后返回。

inner join 连接两个数据表的用法:

SELECT * FROM 表1 INNER JOIN 表2 ON 表1.字段号=表2.字段号

inner join 连接三个数据表的用法:

SELECT * FROM (表1 INNER JOIN 表2 ON 表1.字段号=表2.字段号) INNER JOIN 表3 ON 表1.字段号=表3.字段号

总结:
只要两个表的公共字段有匹配值,就将这两个表中的记录组合起来,类似数学中的取并集,并集(合并相同部分)。

推荐几个网址:
     查看 inner join on 用法示例
     sql语法大全
     sql举例大全
     Mysql教程(Windows)

网上相关资源很多,在此记录只为方便查看。

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