JPA开发利器fastjpa使用介绍

介绍一款JPA开发利器fastjpa,它提供了对SpringBoot框架中关于对JPA的操作的二次封装 ,提供了面向对象的方式来操作JPQL/HQL,旨在减少sql语句编写,快速提高开发效率,使代码书写显的更加优雅和增加可读性

工具特性:


  • 面向对象方式的更新、删除和查询操作
  • 查询指定列名和函数列
  • 分组查询和过滤
  • 列表查询和过滤
  • 表连接查询和过滤
  • 支持子查询
  • 分页查询和过滤

安装


<dependency>
      <groupId>com.github.paganini2008.springworld</groupId>
      <artifactId>fastjpa-spring-boot-starter</artifactId>
      <version>2.0.2</version>
</dependency>

fastjpa-spring-boot-starter 依赖spring-boot-starter-data-jpa, 实质上是对JPA Criteria查询API(QBC)的再封装,并设计成流式风格的API(有点类似python的orm框架sqlalchemy) ,使得JPA面向对象查询的API不再难用

fastjpa 核心接口:


  • EntityDao
  • Model
  • JpaQuery
  • JpaPage
  • Filter
  • Column
  • Field
  • JpaGroupBy
  • JpaSort
  • JpaPageResultSet
  • JpaQueryResultSet
  • JpaUpdate
  • JpaDelete
    大家有兴趣的话,可以研究其源码
下面通过几个示例来演示一下fastjpa的几个核心接口的用法

比如,现在有3个实体,用户,订单,商品

用户实体
@Getter
@Setter
@Entity
@Table(name = "demo_user")
public class User {
    
    @Id
    @Column(name = "id", nullable = false, unique = true)
    private Long id;
    
    @Column(name = "name", nullable = false, length = 45)
    private String name;
    
    @Column(name = "phone", nullable = false, length = 45)
    private String phone;
    
    @Column(name = "vip", nullable = true)
    @org.hibernate.annotations.Type(type = "yes_no")
    private Boolean vip;

}
商品实体
@Getter
@Setter
@Entity
@Table(name = "demo_product")
public class Product {

    @Id
    @Column(name = "id", nullable = false, unique = true)
    private Long id;

    @Column(name = "name", nullable = false, length = 45)
    private String name;

    @Column(name = "price", nullable = false, precision = 11, scale = 2)
    private BigDecimal price;

    @Column(name = "origin", nullable = true, length = 225)
    private String origin;

}
订单实体
@Getter
@Setter
@Entity
@Table(name = "demo_order")
public class Order {

    @Id
    @Column(name = "id", nullable = false, unique = true)
    private Long id;

    @Column(name = "discount", nullable = true)
    private Float discount;

    @Column(name = "price", nullable = false, precision = 11, scale = 2)
    private BigDecimal price;

    @OneToOne(targetEntity = Product.class)
    @JoinColumn(nullable = false, name = "product_id", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT))
    private Product product;

    @ManyToOne(targetEntity = User.class)
    @JoinColumn(nullable = false, name = "user_id", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT))
    private User user;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "create_time", columnDefinition = "timestamp null ")
    private Date createTime;

}

然后定义对应的Dao,需要继承fastjpa提供的EntityDao,但如果你不想使用fastjpa, 直接继承JpaRepositoryImplementation就行了

UserDao
public interface UserDao extends EntityDao<User, Long> {

}
OrderDao
public interface OrderDao extends EntityDao<Order, Long> {

}
ProductDao
public interface ProductDao extends EntityDao<Product, Long> {

}

EntityDao是fastjpa 所有API的入口,看一下它的源码:

@NoRepositoryBean
public interface EntityDao<E, ID> extends JpaRepositoryImplementation<E, ID>, NativeSqlOperations<E> {

    Class<E> getEntityClass();

    boolean exists(Filter filter);

    long count(Filter filter);

    List<E> findAll(Filter filter);

    List<E> findAll(Filter filter, Sort sort);

    Page<E> findAll(Filter filter, Pageable pageable);

    Optional<E> findOne(Filter filter);

    <T extends Comparable<T>> T max(String property, Filter filter, Class<T> requiredType);

    <T extends Comparable<T>> T min(String property, Filter filter, Class<T> requiredType);

    <T extends Number> T avg(String property, Filter filter, Class<T> requiredType);

    <T extends Number> T sum(String property, Filter filter, Class<T> requiredType);

    JpaUpdate<E> update();

    JpaDelete<E> delete();

    JpaQuery<E, E> query();

    <T> JpaQuery<E, T> query(Class<T> resultClass);

    JpaQuery<E, Tuple> multiquery();

    JpaPage<E, E> select();

    <T> JpaPage<E, T> select(Class<T> resultClass);

    JpaPage<E, Tuple> multiselect();

}

其中:
update()方法对应的是update操作
delete()方法对应的是delete操作
query()方法对应的是列表操作
select()方法对应的是分页操作
multiquery()方法对应的也是列表操作,但和query()方法不同的是,它返回的是javax.persistence.Tuple类型的数据,它用于封装分组或多表连接的查询的数据结构
multiselect()也是类似的
另外说一下,fastjpa组件还支持本地sql查询的使用,会在文章的末尾粗略介绍一下。

现在,回到前面的例子,继续讲一下如何使用fastjpa的API,

首先要对这3个实体,分别插入一些数据,并设置相关的关联关系
比如这里,假定一个商品一个订单,一个用户可以下多个订单,这里只是为了演示一下而已

Filter

相当于where条件
支持 lt, lte, gt, gte, eq, ne, like, in, between, isNull, notNull等比较操作符
支持 and, or, not 逻辑操作符

比较操作符举例:
        LogicalFilter filter = Restrictions.gt("price", 50); // 价格大于50元
        productDao.query().filter(filter).selectThis().list().forEach(pro -> {
            System.out.println(pro);
        });
// Hibernate: select product0_.id as id1_1_, product0_.name as name2_1_, product0_.origin as origin3_1_, product0_.price as price4_1_ from demo_product product0_ where product0_.price>50.0

类似的还有:

LogicalFilter filter = Restrictions.between("price", 10, 50);
filter = Restrictions.in("price", Arrays.asList(10,20,30,40,50));
filter =Restrictions.like("name", "%猴头菇%");
filter = Restrictions.eq("orignal", "Shanghai");
逻辑操作符举例:

and

LogicalFilter filter = Restrictions.between("price", 10, 50);
filter = filter.and(Restrictions.like("name", "%猴头菇%"));
filter = filter.and(Restrictions.eq("orignal", "Shanghai"));
// 相当于 where price between (10,50) and name like '%猴头菇%' and orignal='Shanghai'
productDao.query().filter(filter).selectThis().list().forEach(pro -> {
    System.out.println(pro);
});

or

LogicalFilter filter = Restrictions.eq("orignal", "Shanghai");
filter = filter.or(Restrictions.eq("orignal", "New York"));
// 相当于 where orignal='Shanghai' or orignal='New York'
productDao.query().filter(filter).selectThis().list().forEach(pro -> {
    System.out.println(pro);
});

not

LogicalFilter filter = Restrictions.eq("orignal", "Shanghai");
filter = filter.and(Restrictions.eq("orignal", "New York"));
filter = filter.not();
 // 取反,相当于 where orignal!='Shanghai' and orignal!='New York'
productDao.query().filter(filter).selectThis().list().forEach(pro -> {
    System.out.println(pro);
});
JpaGroupBy

分组,相当于group by
举例:

productDao.multiquery().groupBy("origin").select(Column.forName("origin"), Fields.count(Fields.toInteger(1)).as("count")).list()
.forEach(t -> {
        System.out.println("origin: "+t.get("origin") + "\tcount: " + t.get("count"));
});
// Hibernate: select product0_.origin as col_0_0_, count(1) as col_1_0_ from demo_product product0_ group by product0_.origin

Column

表示一个列
举例:

productDao.multiquery().select(Column.forName("name"), Column.forName("price")).list(10).forEach(t -> {
        System.out.println("name: "+t.get("name") + "\t price: " + t.get("price"));
});
// Hibernate: select product0_.name as col_0_0_, product0_.price as col_1_0_ from demo_product product0_ limit ?

查询多个列也可以这样:

productDao.multiquery().select(new String[] { "name", "price" }).list(10).forEach(t -> {
    System.out.println("name: " + t.get("name") + "\t price: " + t.get("price"));
});
// 或者这样:
ColumnList columnList = new ColumnList().addColumn("name").addColumn("price");
productDao.multiquery().select(columnList).list(10).forEach(t -> {
    System.out.println("name: " + t.get("name") + "\t price: " + t.get("price"));
});
Field

用来表示函数,常量等
举例:
聚合函数

ColumnList columnList = new ColumnList()
                .addColumn(Fields.max("price", BigDecimal.class), "maxPrice")
                .addColumn(Fields.min("price", BigDecimal.class), "minPrice")
                .addColumn(Fields.avg("price", Double.class), "avgPrice")
                .addColumn(Fields.count(Fields.toInteger(1)), "count")
                .addColumn("origin");
productDao.multiquery().groupBy("origin").select(columnList).setTransformer(Transformers.asBean(ProductVO.class)).list().forEach(vo -> {
    System.out.println(vo);
});

常用函数:
concat

ColumnList columnList = new ColumnList()
    .addColumn(Fields.concat(Fields.concat(Fields.max("price", String.class), "/"), Fields.min("price", String.class)), "repr")
    .addColumn("origin");
productDao.multiquery().groupBy("origin").select(columnList).setTransformer(Transformers.asBean(ProductVO.class)).list().forEach(vo -> {
    System.out.println(vo);
});
// Hibernate: select concat(concat(max(cast(product0_.price as char)), '/'), min(cast(product0_.price as char))) as col_0_0_, product0_.origin as col_1_0_ from demo_product product0_ group by product0_.origin

其他常用函数

ColumnList columnList = new ColumnList()
       .addColumn(Function.build("LOWER", String.class, "name"), "name")
       .addColumn(Function.build("UPPER", String.class, "origin"), "origin");
productDao.multiquery().select(columnList).list(10).forEach(t -> {
    System.out.println("name: " + t.get("name") + "\t origin: " + t.get("origin"));
});
// Hibernate: select lower(product0_.name) as col_0_0_, upper(product0_.origin) as col_1_0_ from demo_product product0_ limit ?

Case When

        IfExpression<String, String> ifExpression = new IfExpression<String, String>(Property.forName("origin", String.class));
        ifExpression = ifExpression.when("Shanghai", "Asia")
                                   .when("Tokyo", "Asia")
                                   .when("New York", "North America")
                                   .when("Washington", "North America")
                                   .otherwise("Other Area");
        ColumnList columnList = new ColumnList().addColumn(ifExpression, "Area")
                                                .addColumn(Fields.count(Fields.toInteger(1)), "Count");
        productDao.multiquery().groupBy(Fields.toInteger(1)).select(columnList).list().forEach(t -> {
            System.out.println("Area: " + t.get(0) + "\t Count: " + t.get(1));
        });
// Hibernate: select case product0_.origin when 'Shanghai' then 'Asia' when 'Tokyo' then 'Asia' when 'New York' then 'North America' when 'Washington' then 'North America' else 'Other Area' end as col_0_0_, count(1) as col_1_0_ from demo_product product0_ group by 1
子查询示例
示例1
        JpaSubQuery<Order,Order> subQuery = orderDao.query().subQuery(Order.class, "o").filter(Restrictions.eq("o", "id", "100")).select("o", "product.id");
        Product product = productDao.query().filter(Restrictions.eq("id", subQuery)).selectThis().first();
        System.out.println(product);
// Hibernate: select product0_.id as id1_1_, product0_.name as name2_1_, product0_.origin as origin3_1_, product0_.price as price4_1_ from demo_product product0_ where product0_.id=(select order1_.product_id from demo_order order1_ where order1_.id=100) limit ?
示例2
        JpaQuery<Order,Order> jpaQuery = orderDao.query();
        JpaSubQuery<Product, BigDecimal> subQuery = jpaQuery.subQuery(Product.class, "p", BigDecimal.class)
                .select(Fields.avg(Property.forName("p", "price")));
        jpaQuery.filter(Restrictions.gte("price", subQuery)).selectThis().list(10).forEach(pro -> {
            System.out.println(pro);
        });
// Hibernate: select order0_.id as id1_0_, order0_.create_time as create_t2_0_, order0_.discount as discount3_0_, order0_.price as price4_0_, order0_.product_id as product_6_0_, order0_.receiver as receiver5_0_, order0_.user_id as user_id7_0_ from demo_order order0_ where order0_.price>=(select avg(product1_.price) from demo_product product1_) limit ?
示例3
        JpaQuery<Order,Order> jpaQuery = orderDao.query();
        JpaSubQuery<Product, BigDecimal> subQuery = jpaQuery.subQuery(Product.class, "p", BigDecimal.class)
                .select(Fields.avg(Property.forName("p", "price")));
        jpaQuery.filter(Restrictions.gte("price", subQuery)).selectThis().list(10).forEach(pro -> {
            System.out.println(pro);
        });
// Hibernate: select order0_.id as id1_0_, order0_.create_time as create_t2_0_, order0_.discount as discount3_0_, order0_.price as price4_0_, order0_.product_id as product_6_0_, order0_.receiver as receiver5_0_, order0_.user_id as user_id7_0_ from demo_order order0_ where 1=1 and (exists (select user1_.id from demo_user user1_ where user1_.name=order0_.receiver)) limit ?

排序示例
        orderDao.query().filter(Restrictions.gte("price", 50)).sort(JpaSort.desc("createTime"), JpaSort.asc("price")).selectThis().list(10)
                .forEach(pro -> {
                    System.out.println(pro);
                });
// Hibernate: select order0_.id as id1_0_, order0_.create_time as create_t2_0_, order0_.discount as discount3_0_, order0_.price as price4_0_, order0_.product_id as product_6_0_, order0_.receiver as receiver5_0_, order0_.user_id as user_id7_0_ from demo_order order0_ where order0_.price>=50.0 order by order0_.create_time desc, order0_.price asc limit ?
关联查询示例
左连接
        PageResponse<Tuple> pageResponse = orderDao.multiselect().leftJoin("product", "p")
                .filter(Restrictions.gte("p", "price", 50)).sort(JpaSort.desc("createTime")).selectAlias("p")
                .list(PageRequest.of(10));
        for (PageResponse<Tuple> current : pageResponse) {
            System.out.println("第" + current.getPageNumber() + "页");
            for (Tuple tuple : current.getContent()) {
                System.out.println(Arrays.toString(tuple.toArray()));
            }
        }
// Hibernate: select product1_.id as id1_1_, product1_.name as name2_1_, product1_.origin as origin3_1_, product1_.price as price4_1_ from demo_order order0_ left outer join demo_product product1_ on order0_.product_id=product1_.id where product1_.price>=50.0 order by order0_.create_time desc limit ?, ?
右连接
        ColumnList columnList = new ColumnList();
        columnList.addColumn("id");
        columnList.addColumn("u", "name");
        columnList.addColumn("price");
        columnList.addColumn("createTime");
        PageResponse<Order> pageResponse = orderDao.select().rightJoin("user", "u").filter(Restrictions.gte("price", 50))
                .sort(JpaSort.desc("createTime")).select(columnList).list(PageRequest.of(10));
        for (PageResponse<Order> current : pageResponse) {
            System.out.println("第" + current.getPageNumber() + "页");
            for (Order order : current.getContent()) {
                System.out.println(order);
            }
        }
  // 然而运行上面代码,JPA会报错,因为JPA目前尚不支持Right Join!
内连接
        ColumnList columnList = new ColumnList();
        columnList.addColumn("id");
        columnList.addColumn("p","name");
        columnList.addColumn(Property.forName("p", "price"),"originalPrice");
        columnList.addColumn("price");
        columnList.addColumn("createTime");
        PageResponse<Tuple> pageResponse = orderDao.multiselect().join("product", "p")
                .filter(Restrictions.gte("p", "price", 50)).sort(JpaSort.desc("createTime")).select(columnList)
                .list(PageRequest.of(10));
        for (PageResponse<Tuple> current : pageResponse) {
            System.out.println("第" + current.getPageNumber() + "页");
            for (Tuple tuple : current.getContent()) {
                System.out.println(Arrays.toString(tuple.toArray()));
            }
        }
// Hibernate: select order0_.id as col_0_0_, product1_.name as col_1_0_, product1_.price as col_2_0_, order0_.price as col_3_0_, order0_.create_time as col_4_0_ from demo_order order0_ inner join demo_product product1_ on order0_.product_id=product1_.id where product1_.price>=50.0 order by order0_.create_time desc limit ?, ?
列表和分页查询
查询订单列表:
        orderDao.query().filter(Restrictions.gt("price", 50)).sort(JpaSort.desc("createTime")).selectThis()
                .setTransformer(Transformers.asBean(OrderVO.class, null, (model, order, output) -> {
                    Product product = order.getProduct();
                    output.setProductName(product.getName());
                    output.setOrigin(product.getOrigin());
                    User user = order.getUser();
                    output.setUsername(user.getName());
                    output.setPhone(user.getPhone());
                })).list(50).forEach(vo -> {
                    System.out.println(vo);
                });
// Hibernate: select order0_.id as id1_0_, order0_.create_time as create_t2_0_, order0_.discount as discount3_0_, order0_.price as price4_0_, order0_.product_id as product_6_0_, order0_.receiver as receiver5_0_, order0_.user_id as user_id7_0_ from demo_order order0_ where order0_.price>50.0 order by order0_.create_time desc limit ?
分页查询订单:
        PageResponse<OrderVO> pageResponse = orderDao.select().filter(Restrictions.gt("price", 50)).sort(JpaSort.desc("createTime"))
                .selectThis().setTransformer(Transformers.asBean(OrderVO.class, null, (model, order, output) -> {
                    Product product = order.getProduct();
                    output.setProductName(product.getName());
                    output.setOrigin(product.getOrigin());
                    User user = order.getUser();
                    output.setUsername(user.getName());
                    output.setPhone(user.getPhone());
                })).list(PageRequest.of(1, 10));
        for (PageResponse<OrderVO> current : pageResponse) {
            System.out.println("第" + current.getPageNumber() + "页");
            for (OrderVO vo : current.getContent()) {
                System.out.println(vo);
            }
        }
// Hibernate: select count(1) as col_0_0_ from demo_order order0_ where order0_.price>50.0
// Hibernate: select order0_.id as id1_0_, order0_.create_time as create_t2_0_, order0_.discount as discount3_0_, order0_.price as price4_0_, order0_.product_id as product_6_0_, order0_.receiver as receiver5_0_, order0_.user_id as user_id7_0_ from demo_order order0_ where order0_.price>50.0 order by order0_.create_time desc limit ?, ?
分页查询会出现两条sql, 一条count语句,一条查询语句
删除操作
int rows = productDao.delete().filter(Restrictions.gt("price", 990)).execute();
System.out.println("Effected rows: " + rows);
// Hibernate: delete from demo_product where price>990.0
子查询删除:
        JpaSubQuery<Order, Order> subQuery = productDao.delete().subQuery(Order.class);
        subQuery.select("product");
        int rows = productDao.delete().filter(Restrictions.in("id", subQuery).not()).execute();
        System.out.println("Effected rows: " + rows);
// Hibernate: delete from demo_product where id not in  (select order1_.product_id from demo_order order1_)
更新操作
int rows = userDao.update().set("vip", true).filter(Restrictions.eq("vip", false)).execute();
System.out.println("Effected rows: " + rows);
// Hibernate: update demo_user set vip=? where vip=?
子查询更新:
        JpaSubQuery<Order, Order> subQuery = userDao.update().subQuery(Order.class).filter(Restrictions.gte("price", 500)).select("user");
        int rows = userDao.update().set("vip", true).filter(Restrictions.in("id", subQuery)).execute();
        System.out.println("Effected rows: " + rows);
// update demo_user set vip=? where id in (select order1_.user_id from demo_order order1_ where order1_.price>=500.0)

本地查询示例


对于fastjpa操作JPA对象难以满足实际业务的场景,还是建议直接使用sql, 即本地查询

简单查询返回关联实体
ResultSetSlice<Order> resultSetSlice = orderDao.select("select * from demo_order where price>?", new Object[] { 50 });
        PageResponse<Order> pageResponse = resultSetSlice.list(PageRequest.of(1, 10));
        for (PageResponse<Order> current : pageResponse) {
            System.out.println("第" + current.getPageNumber() + "页");
            for (Order order : current.getContent()) {
                System.out.println("Order Id: "+order.getId() + ", Product Name: " + order.getProduct().getName() + ", Username: " + order.getUser().getName());
            }
        }
分组查询及分页
        ResultSetSlice<Map<String, Object>> resultSetSlice = orderDao.selectForMap(
                "select origin,max(price) as maxPrice,min(price) as minPrice,avg(price) as avgPrice from demo_product group by origin",
                new Object[0]);
        PageResponse<Map<String, Object>> pageResponse = resultSetSlice.list(PageRequest.of(1, 5));
        for (PageResponse<Map<String, Object>> current : pageResponse) {
            System.out.println("第" + current.getPageNumber() + "页");
            for (Map<String, Object> vo : current.getContent()) {
                System.out.println(vo);
            }
        }

事实上,fastjpa是为了提升开发人员使用JPA的效率而出现的,如果出现使用fastjpa不能满足业务需求的情况,请果断使用本地sql

最后附上启用JPA配置的核心代码, 可以直接食用

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(repositoryFactoryBeanClass = EntityDaoFactoryBean.class, basePackages = { "com.demo.jpalearning.dao" })
public class JpaConfig {

    public static final String PRIMARY_ENTITY_FACTORY_BEAN_NAME = "entityManagerFactory";

    @Primary
    @Bean(PRIMARY_ENTITY_FACTORY_BEAN_NAME)
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, EntityManagerFactoryBuilder builder,
            JpaProperties jpaProperties, HibernateProperties hibernateProperties) {
        Map<String, Object> properties = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(),
                new HibernateSettings());
        return builder.dataSource(dataSource).properties(properties).packages("com.demo.jpalearning.entity").build();
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

}

别忘了配置文件添加:

#Jpa Configuration
spring.jpa.database=MYSQL
spring.jpa.show-sql=true
spring.jpa.format-sql=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

源码地址:https://github.com/paganini2008/fastjpa-spring-boot-starter.git

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

推荐阅读更多精彩内容