Ormlite全解析(非安卓)

添加ormlite注解

       使用@DatabaseTable和@DatabaseField添加注解,表示数据库的表和字段,其实也可以通过Java代码和Spring XML代码配置类。

       @DatabaseTable添加注解的时候可以指定表名,默认情况下,和字段名一样。

高级用户可能想要在这里添加一个daoClass参数指定Dao对象的类,用于操作这个类。 这个daoClass通过DaoManager使用, 查看DaoManager小节。

    OrmLite的字段注解参数包括

        columnName        dataType        defaultValue        width        canBeNull        id        generatedId        generatedIdSequence        foreign        useGetSet        unknownEnumName        throwIfNull        persisted        format        unique        uniqueCombo        index        uniqueIndex        indexName        uniqueIndexName        foreignAutoRefresh        maxForeignAutoRefreshLevel        allowGeneratedIdInsert        columnDefinition        foreignAutoCreate        version        foreignColumnName

2.  使用标准的JPA(javax.persistence annotations)注解(暂不考虑)

使用@Entity代替@DatabaseTable代替,可以指定一个name参数值表示表名,如果不指定,默认使用类名。

用@Column,@GeneratedValue,@OneToOne ,@ManyToOne, @JoinColumn, 和@Version指定字段属性。

支持的JPA注解包括

@Entity@Column@Id@GeneratedValue@OneToOne or @ManyToOne@JoinColumn@Version2.1.3  添加一个无参构造函数通过query获取对象时,这个无参构造函数用于创建对象。2.2  支持持久化的数据类型DatabaseField注解是有一个dataType属性,表明java数据类型和sql数据类型进行的对应数据转换,包括如下:

String             (DataType.STRING)

String             (DataType.LONG_STRING)

String             (DataType.STRING_BYTES)

boolean or Boolean                 (DataType.BOOLEAN or                                         DataType.BOOLEAN_OBJ)

java.util.Date                 (DataType.DATE)

java.util.Date             (DataType.DATE_LONG)

java.util.Date             (DATE_STRING)

byte or Byte             (DataType.BYTE or                                 DataType.BYTE_OBJ)

byte array             (DataType.BYTE_ARRAY)

char or Character             (DataType.CHAR or                                         DataType.CHAR_OBJ)

short or Short             (DataType.SHORT or                             DataType.SHORT_OBJ)

int or Integer                     (DataType.INTEGER or                                 DataType.INTEGER_OBJ)

long or Long         (DataType.LONG or                                 DataType.LONG_OBJ)

float or Float                 (DataType.FLOAT or                                     DataType.FLOAT_OBJ)

double or Double         (DataType.DOUBLE or DataType.DOUBLE_OBJ)

Serializable             (DataType.SERIALIZABLE)

enum or Enum         (DataType.ENUM_STRING)

enum or Enum             (DataType.ENUM_INTEGER)

UUID                     (DataType.UUID)

BigInteger             (DataType.BIG_INTEGER)

BigDecimal             (DataType.BIG_DECIMAL)

BigDecimal             (DataType.BIG_DECIMAL_NUMERIC)

DateTime             (DataType.DATE_TIME)

java.sql.Date         (DataType.SQL_DATE)

java.sql.Timestamp         (DataType.TIME_STAMP)

每个dao类负责操作一个对应的持久化类。DAO类的创建,包含两个参数,第一个是持久化类的类名,第二个是该类对应的id类型,可以通过DaoManager的静态方法直接创建,如:

Dao accountDao =

DaoManager.createDao(connectionSource, Account.class);

建议创建DAO接口,然后创建实现类,并且通过daoClass指定

/** Account DAO which has a String id (Account.name) */public interface AccountDao extends Dao {    // empty wrapper, you can add additional DAO methods here}

/** JDBC implementation of the AccountDao interface. */

public class AccountDaoImpl extends BaseDaoImpl

  implements AccountDao {

    // this constructor must be defined

    public AccountDaoImpl(ConnectionSource connectionSource)

      throws SQLException {

        super(connectionSource, Account.class);

    }

}

要让自定义DAO类生效,需要将其指定到对应实体中@DatabaseTable的daoClass属性上。

@DatabaseTable(daoClass = AccountDaoImpl.class)

public class Account {

  …

}


2.5  ORMLite支持的数据库类型

2.6  结合使用 Tying It All Together

基本流程

// h2为例,

String databaseUrl = "jdbc:h2:mem:account";

JdbcConnectionSource connectionSource =

  new JdbcConnectionSource(databaseUrl);

// 通过connectionSource实例化Dao

AccountDaoImpl accountDao = new AccountDaoImpl(connectionSource);

// 如果需要创建表,则调用下面的call

TableUtils.createTable(connectionSource, Account.class);

// 创建Account的一个实例

Account account = new Account("Jim Coakley");

// 持久化account对象到数据库

accountDao.create(account);

// 销毁数据源关闭潜在连接

connectionSource.destroy();


2.7  创建表和schema2.7.1  TableUtils类

        TableUtils 类提供了一系列静态方法,用于创建和删除表,以及提供schema语句。

 createTable(ConnectionSource, Class)  用于创建表,返回成功的语句数

        TableUtils.createTable(connectionSource, Account.class);

 createTableIfNotExists(ConnectionSource, Class) 同上,但不支持所有类型的数据库

 createTable(ConnectionSource, DatabaseTableConfig) 同上,

getCreateTableStatements(ConnectionSource, Class) 

createTable(ConnectionSource, DatabaseTableConfig)功能同上,不过通过一个DatabaseTableConfig对象类表示需要创建的表的字段和字段类型。

createTableIfNotExists(ConnectionSource, DatabaseTableConfig) 功能同上,但不支持所有的数据库。

dropTable(ConnectionSource, Class, boolean ignoreErrors) 删除表,不能恢复,慎用。ignoreErrors参数,如

果删除时,表没有创建,那么将ignoreErrors设为true则忽视所有异常。

dropTable(ConnectionSource, DatabaseTableConfig, boolean ignoreErrors) 删除表,同上

getCreateTableStatements(ConnectionSource, Class) 类似于createTable,但是该方法返回的是一个语句列表,

这个列表可用于创建一个类,在部分数据库的初始化过程中,比较有用。

getCreateTableStatements(ConnectionSource, DatabaseTableConfig) 同上

clearTable(ConnectionSource, Class)清除所有数据

clearTable(ConnectionSource, DatabaseTableConfig) 使用DatabaseTableConfig清除所有数据


2.7.2 TableCreator类

TableCreator类是为Spring设计的,但是在其他的配置中也可用。它通过ConnectionSource和DAOs列表配置。

如果系统属性ormlite.auto.create.tables被设为true则,与DAOs关联的表会被自动创建。如果系统属性

ormlite.auto.drop.tables属性被设为true,则会自动删除表。这点在测试的时候比较有用。你可以在测试开始的脚

本中设置系统属性,并在生产脚本中关闭脚本。

List> daoList = new ArrayList>();

daoList.add(accountDao);

TableCreator creator =

   new TableCreator(connectionSource, daoList);

// create the tables if the right system property is set

creator.maybeCreateTables();

// later, we may want to drop the tables that were created

creator.maybeDropTables();


2.8  标识列(字段)

    数据库行可以通过特定的id列标识。行本身不需要一个标识字段,但是许多DAO操作(update, delete, refresh

)需要一个表示字段。这个标识字段可以有用户指定也可以由数据库自动生成。数据行中id列的值时唯一的,如果需

要根据id操作数据,则必须指定id字段,可以通过下列三种方法中的一种,且只允许用一种指定。@DatabaseField属

性的id,generatedId, generatedIdSequence

2.8.1 通过id指定

例如:

public class Account {

    @DatabaseField(id = true)

    private String name;

    …

}

使用:

Account account = accountDao.queryForId("John Smith");

if (account == null) {

    // the name "John Smith" does not match any rows

}

2.8.2 通过generatedId指定

可以通过long或者int型属性生成标识字段,数据行的字段会自动生成

例如:

public class Order {

    @DatabaseField(generatedId = true)

    private int id;

    …

}

当订单对象被创建并被存储到数据库中的时候,生成的id值会被返回并被设置给对象,大多数数据库类型,生成的id

值从1开始,并每次递增1。

使用:

//通过指定id创建对象

Order order = new Order("Jim Sanders", 12.34);

orderDao.create(order);

System.out.println("Order id " + order.getId() +

   " was persisted to the database");

//查询id为1372的订单对象

order = orderDao.queryForId(1372);

if (order == null) {

   // none of the order rows have an id of 1372

}

2.8.3 通过generatedIdSequence创建id

有些数据库使用序列号生成器提供id值,如果你使用generatedId = true创建数据库,则序列名会由ORMLite自动生

成,不过,你需要设置一个序列蜜瓜匹配现有的schema,你可以使用generatedIdSequence值。

例如:

public class Order {

    @DatabaseField(generatedIdSequence = "order_id_seq")

    private int id;

    …

}

2.9 DAO使用

通过使用DAO的方法,下列数据库操作可以很轻松的完成。

 1. 创建并持久化对象到数据库

      Account account = new Account();

      account.name = "Jim Coakley";

      accountDao.create(account);

   2. 通过id列查询

      Account account = accountDao.queryForId(name);

      if (account == null) {

        account not found handling …

      }

  3. 更新域数据库关联的对象的数据行。

      account.password = "_secret";

      accountDao.update(account);

4. 在数据库数据发生改变时刷新对象,需要id值。

      accountDao.refresh(account);

  5. 删除数据行。数据库中删除后,内存中持有的数据库还可用。

      accountDao.delete(account);

  6. 通过DAO对象遍历数据行。DAO本身也是一个迭代器,因此可以直接遍历数据库数据行。

      // page through all of the accounts in the database

      for (Account account : accountDao) {

         System.out.println(account.getName());

      }

      你必须遍历迭代器的所有数据库对象,并关闭潜在的sql对象。 如果你没有通过一个循环遍历所有的数据,

ORMLite并不会关闭潜在对象,数据库的连接会被弱化并且在后面被GC回收,这回导致程序Bug,所以最好使用一个

try...finally包装迭代器。下面的情况是错误的“必须遍历所有的对象”。

      for (Account account : accountDao) {

         if (account.getName().equals("Bob Smith")) {

           // you can't return, break, or throw from here

           return account;

         }

      }

  7. 直接使用迭代器

      你也可以选择直接使用迭代器,因为for循环式最佳的。这样允许你使用try...finally达到一种更好的模式,

如:

      CloseableIterator iterator = accountDao.closeableIterator();

      try {

         while (iterator.hasNext()) {

            Account account = iterator.next();

            System.out.println(account.getName());

         }

      } finally {

         // close it at the end to close underlying SQL statement

         iterator.close();    

      }

   8. 获得一个包装迭代器“wrapped iterable”

   CloseableWrappedIterable wrappedIterable =

       accountDao.getWrappedIterable();

       try {

          for (Account account : wrappedIterable) {

              …

          }

       } finally {

          wrappedIterable.close();

   }

2.10  索引属性

   id字段默认是索引属性,非id字段在DatabaseField属性属性中指定index值为true,所以优化查询,在表被删除

时,索引也会被删除。

   public class Account {

       @DatabaseField(id = true)

       private String name;

       // 给city属性索引索引,便于款速查询更大的表

       @DatabaseField(index = true)

       private String city;

       …

   }

   索引名默认为account_city_idx,可以通过一个indexName=“othername”指定其他的索引名称。如果经常通过两

个字段进行查询,可以通过为两个字段设置相同的索引名,如:

   @DatabaseField(indexName = "account_citystate_idx")

   private String city;

   @DatabaseField(indexName = "account_citystate_idx")

   private String state;

   这种情况下,如果只根据其中的一个字段查询,则并不会优化查询,不同的数据库类型,单字段索引和多字段索

引的效率比较可能不行同。

   创建唯一索引可以通过@DatabaseField注解的uniqueIndex=true和uniqueIndexName='indexname'属性指定。这样

,效果上和上面的设置时一样的,但是这样可以创建确保没有两行数据具有相同的索引属性值。

2.11  原生的SQL语句问题

2.11.11 原生的查询操作

    1. 使用DAO中的queryRaw方法创建原生数据库查询,这个方法会返回一个GenericRawResult对象,代表一个字符

串数组,对象数组,或者用户包装对象的数组。 可以查看HenericRaeResult的文档获取更多用法。

    // find out how many orders account-id #10 has

    GenericRawResults rawResults = orderDao.queryRaw( "select count(*) from orders where

account_id = 10");

    // there should be 1 result

    List results = rawResults.getResults();

    // the results array should have 1 value

    String[] resultArray = results.get(0);

    // this should print the number of orders that have this account-id

    System.out.println("Account-id 10 has " + resultArray[0] + " orders");

   2. 如果喜欢使用preparedStatementString()方法的话,也可以使用QueryBuilder创建原生查询语句,详情可以

查看QueryBuilderBasic这节。

    QueryBuilder qb = accountDao.queryBuilder();

    qb.where().ge("orderCount", 10);

    results = accountDao.queryRaw(qb.prepareStatementString());


   3. 如果需要使用QueryBuilder携带参数创建原生查询语句的话们可以使用下列的方法:

    QueryBuilder qb = accountDao.queryBuilder();

    // we specify a SelectArg here to generate a ? in statement string below

    qb.where().ge("orderCount", new SelectArg());

    // the 10 at the end is an optional argument to fulfill SelectArg above

    results = accountDao.queryRaw(qb.prepareStatementString(), 10);

   4. 也可以使用下面的方式携带参数创建原生查询

   QueryBuilder qb = accountDao.queryBuilder();

   // select 2 aggregate functions as the return

   qb.selectRaw("MIN(orderCount)", "MAX(orderCount)");

   // the results will contain 2 string values for the min and max

   results = accountDao.queryRaw(qb.prepareStatementString());

   String[] values = results.getFirstResult();

   5. 对于数据量大的结果集,应该考虑使用GenericRawResults对象的iterator()方法,这会使用到数据库分页。

例如:

   // return the orders with the sum of their amounts per account

   GenericRawResults rawResults =

   orderDao.queryRaw("select account_id,sum(amount) from orders group by account_id");

   // page through the results

   for (String[] resultArray : rawResults) {

      System.out.println("Account-id " + resultArray[0] + " has "

       + resultArray[1] + " total orders");

   }

   rawResults.close();

   6.  如果你只需要查询单个值(如聚合函数MAX的值),则可以使用queryRawValue(...)方法:

   long maxUnits = orderDao.queryRawValue("select max(units) from orders");

   7.  如果你的部分属性值不能被合适地转换成字符串,并且你在结果字段中传递了返回类型,那么可以返回属性

为Object[]类型,如下:

   // return the orders with the sum of their amounts per account

   GenericRawResults rawResults =

   orderDao.queryRaw(

       "select account_id,sum(amount) from orders group by account_id",

       new DataType[] { DataType.LONG, DataType.INTEGER });

       //page through the results

       for (Object[] resultArray : rawResults) {

       System.out.println("Account-id " + resultArray[0] + " has "

       + resultArray[1] + " total orders");

   }

   rawResults.close();

   注意: select * 可能会根据数据库的类型返回不同的orders的属性顺序。 为了确保返回的数据类型数组与返回

的字段相匹配,你必须明确指定属性,并且不能为SQL 的 *。

   8.  你也可以通过传递一个RawRowMapper对象,将返回的结果映射到你自己的对象中。 这样会通过一个字符串数

组调用映射对象,并允许将字符创转换到对象中。DAO提供了一个默认的RawRowMapper,可以通过

orderDAO.getRawRowMapper()获得,这个对象知道如何将字符串数组转换到对象中。

   如果结果比较复杂,你也可以自定义Mapper,如:

   // return the orders with the sum of their amounts per account

   GenericRawResults rawResults =

   orderDao.queryRaw(

      "select account_id,sum(amount) from orders group by account_id",

      new RawRowMapper() {

            public Foo mapRow(String[] columnNames,

              String[] resultColumns) {

                return new Foo(Long.parseLong(resultColumns[0]),

                    Integer.parseInt(resultColumns[1]));

              }

      });

   // page through the results

   for (Foo foo : rawResults) {

     System.out.println("Account-id " + foo.accountId + " has "

     + foo.totalOrders + " total orders");

   }

   rawResults.close();

   注意: 查询和结果字符串可能根据不同的数据库有不同的情况。例如:

   1.  有些数据库要求字段名必须是大写,有些则要求是小写。

   2.  如果字段名或者表名有保留的话,你可能必须应用他们。

   3.  结果字段名也可能是大写或者小写。

   4.  select * 可能根据数据库类型返回的顺序不同。

   注: 和其他ORMLite迭代器一样,你必须遍历迭代器中的所有结果,以使语句自动关闭,或者调用

GeneratedRawResults.close()方法确保迭代器及相关的数据库连接关闭。

   注: 如果你正在使用QueryBuilder#preparedStatementString()方法创建查询,则可能会自动添加一个id列。

2.11.2  原生的更新语句

   在DAO的函数不满足你的灵活性时,可能要用到原生更新语句,更新语句必须包含保留关键字 INSERT, DELETE,

或者UPDATE,例如:

   fooDao.updateRaw("INSERT INTO accountlog (account_id, total) " + "VALUES ((SELECT account_id,sum

(amount) FROM accounts));

2.11.3  原生执行语句

   在DAO的函数不满足你的灵活性时,可能要用到原生执行语句修改表。

   fooDao.executeRaw("ALTER TABLE accountlog DROP COLUMN partner");

2.12 外部对象属性

   ORMLite支持外键的概念,既一个对象的一个或者多个属性对应相同数据库中其他表中的一个对象。例如,如果有

一个Order对象,并且每个Order具有一个对应的Account对象,那么,这个Order对象可能具有一个外部对象的

Account属性。对于这个外部对象属性,只有Account的id属性会被持久化到Order表中作为account_id列的值,例如

,Order类像下面这样:


   @DatabaseTable(tableName = "orders")

   public class Order {

      @DatabaseField(generatedId = true)

      private int id;


      @DatabaseField(canBeNull = false, foreign = true)

      private Account account;


      …

   }

   当订单表创建的时候,会生成下面的语句

   CREATE TABLE `orders`(`id` INTEGER AUTO_INCREMENT , `account_id` INTEGER,

      PRIMARY KEY (`id`));

   注:属性名并不叫account,而是account_id。 如果你需要通过这个属性去查询的话,应该指定为account_id,

或者是通过DatabaseField注解的columnName参数指定一个字段名。

   通过外步对象创建属性的时候,一定注意,外在对象不会自动被创建。如果你的外在对象有一个数据库提供的id

,那么,你需要在这个对象被引用之前创建这个id,例如:

   Account account = new Account("Jim Coakley");

   accountDao.create(account);

   // this will create the account object and set any generated ids

// now we can set the account on the order and create it

   Order order = new Order("Jim Sanders", 12.34);

   order.setAccount(account);

   …

   orderDao.create(order);

   如果你想要让他们在某些情况下自建,可以使用foreignAutoCreate设置,参考foreignAutoCreate

   当你查询一个订单时,你会获得一个account的属性对象,这个对象只有id属性被设定了值,其他属性都是默认值

。如果你需要使用这个account对象的其他值,则必须调用accountDAO类的refresh方法获得这个Account对象的属性

   Order order = orderDao.queryForId(orderId);

   System.out.println("Account-id on the order should be set: " + order.account.id);

   // this should print null for order.account.name

   System.out.println("But other fields on the account should not be set: " + order.account.name);

   // so we refresh the account using the AccountDao

   accountDao.refresh(order.getAccount());

   System.out.println("Now the account fields will be set: " + order.account.name);


   也可以使用foreignAutoRefresh设置自动刷新外部对象的数据。参考foreignAutoRefresh.

   注意: 由于我们使用到刷新,所以外部对象必须有id属性。

   你可以使用不同的方式查询外部对象属性。下面的例子展示了查询匹配所有account属性的订单的代码。因为id属

性石name,所以你可以通过account的name属性进行查询。

   // query for all orders that match a certain account

   List results =

   orderDao.queryBuilder().where().eq("account_id", account.getName()).query();

   或者,你可以让ORMLite从account对象中取出id属性,下面的代码与上面的代码等价:

   // ORMLite will extract and use the id field internally

   List results = orderDao.queryBuilder().where().eq("account_id", account).query();  


2.13  外部集合属性

   在上一届的指南中我们给出了订单类具有一个外部对象属性的实例。外部集合允许你添加一个订单集合到账户表

中。无论何时,当账户对象通过查询语句返回,或者通过DAO对象刷新时,一个独立的查询会创建,用于查询对应的

订单,并且将这个订单集合设置到账户对象中。集合中所有的订单都有一个关联的外部对象鱼账户对象匹配。

   public class Account {

       …

       @ForeignCollectionField(eager = false)

       ForeignCollection orders;

       …

   }

   上面的例子显示,@ForeignCollectionField注解表明了订单属性是一个与账户相匹配的订单集合。订单的属性类

型必须是@ForeignCollection或者是Collection,其他类型的集合不支持,因为他们有太多方法要支持,太过

重量级。@ForeignCollectionField注解支持的属性包括下面这些:

  eager

   有两种类型的外部集合:eager和lazy,如果设置eager为true,则独立的查询语句会被创建,对应的订单会被存

储在一个集合中。如果eager设为false(默认值),则集合会被认为是“lazy”的,只有集合中的方法被调用时,才

会调用DAO.iterator()方法,遍历数据库。

    注:默认情况下,如果如果你有一个eager类型的对象集合,并且他们本身也有eager集合,那么内部的集合会

因为性能原因被创建为lazy的。如果需要改变这一点的话, 可以查看下面的maxEagerLevel设置。

  maxEagerLevel

   这个属性能设置一个eager外部集合的外部集合扩展次数。如果你查询A,并且A具有一个外部eager集合属性B, B

具有一个外部集合属性C,...,无论何时,当你查询A的时候,一些列的数据库操作会发生。莫仍情况下,这个值为1

,表示如果你查询A,那么集合B会以eager方式获得,但是B中的每个对象的eager集合C会以lazy集合的方式加载。你

应该根据自己的实际情况设置这个值。

  columnName

   列名。只有当你想要匹配传递到DAO.assignEmptyForeignCollection(Object, String)中的string,或者指定想

要通过queryBuilder。selectColumn(...)返回的集合时使用。

  orderColumnName

   orderAscending

   foreignFieldName

2.14 支持DAO的对象

下面是让对象本身支持数据库操作,而不是使用DAO进行的ORM模式的另外一种方式。例如,给定一个数据对象foo,可以调用foo.refresh()而不是调用fooDao.refresh(foo)。默认是使用Dao类,这样可以让你的数据类拥有它自己的层次,并将数据库代码和Dao分离。不过,如果你乐意使用这种模式的话,可以使用BaseDaoEnable类。

   所有可以属性自身的类都需要继承BaseDaoEnable类。例如:

@DatabaseTable(tableName = "accounts")

public class Account extends BaseDaoEnabled {

    @DatabaseField(id = true)

    private String name;

    @DatabaseField(canBeNull = false)

    private String password;

    …

    第一次创建对象时,需要使用DAO对象,或者你需要设置dao到对象中使其自建。

    account.setDao(accountDao);

    account.create();

    不过,无论何时,一个对象作为ORMLite的查询结果返回时,DAO已经被设置到这个对象中。

    Account account = accountDao.queryForId(name);

    account.setPassword(newPassword);

    account.update();

   这一点对外部属性也是同样的。

    Order order = orderDao.queryForId(orderId);

    // load all of the fields from the account

    order.getAccount().refresh();

   BaseDaoEnable的javadoc具有最新的自操作列表,不过,目前这种类能做的内容如下:

   create

   使用DAO或者调用对象的setDao()方法。

   refresh

   刷新对象,以免其在数据库中有更新。

   update

   对象在内容中改变后,将数据更新到数据库。

   updateId

   如果需要更新对象的id,必须使用这个方法。不能改变对象的id属性,然后调用update方法,否则数据这个对象

无法找到。

   delete

   从数据库中删除对象。

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

推荐阅读更多精彩内容