MyBatis入门

1、MyBatis 是优秀的持久层框架
2、MyBatis 使用 XML 将 SQL 与程序解耦,便于维护
3、MyBatis 学习简单,执行高效,是 JDBC 的延伸

中文参考网址:https://mybatis.net.cn/

一、mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!--goods_id => goodsId 驼峰命名转换-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--设置默认指向的数据库-->
    <environments default="dev">
        <!--配置环境,不同的环境不同的id名字-->
        <!--开发环境-->
        <environment id="dev">
            <!--采用JDBC方式对数据库事务进行commit/rollback-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <!--如果 type="POOLED", 代表使用连接池,close则是将连接回收到连接池中-->
            <!--如果 type="UNPOOLED", 代表直连,close会调用Connection.close()方法关闭连接-->
            <dataSource type="POOLED">
                <!--数据库驱动-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!--数据库连接字符串-->
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=ture&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
                <!--数据库用户名-->
                <property name="username" value="root"/>
                <!--数据库密码-->
                <property name="password" value="123456"/>
            </dataSource>
        </environment>

        <!--生产环境-->
        <environment id="prd">
            <!--采用JDBC方式对数据库事务进行commit/rollback-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <!--数据库驱动-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!--数据库连接字符串-->
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=ture&amp;characterEncoding=UTF-8"/>
                <!--数据库用户名-->
                <property name="username" value="root"/>
                <!--数据库密码-->
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

二、初始化工具类 MyBatisUtils

/**
 * MyBatis工具类,创建全局唯一的 SqlSessionFactory 对象
 */
public class MyBatisUtils {

    // 利用 static(静态) 属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;

    // static块用于初始化静态变量
    // 利用静态块在初始化类时实例化 sqlSessionFactory 对象
    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (IOException e) {
            e.printStackTrace();
            // 初始化错误时,通过抛出 ExceptionInInitializerError 通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 创建一个新的 SqlSession 对象
     * @return SqlSession对象
     */
    public static SqlSession openSession() {
        return sqlSessionFactory.openSession();
    }

    /**
     * 释放一个有效的 SqlSession 对象
     * @param session 准备释放的 SqlSession 对象
     */
    public static void closeSession(SqlSession session) {
        if (session != null) {
            session.close();
        }
    }
}

三、MyBatis 数据操作

  • 1、创建实体类(Entity)
  • 2、创建Mapper XML
  • 3、编写<select> SQL标签
  • 4、开启驼峰命名映射
  • 5、新增<mapper>
  • 6、SqlSession执行select语句

(一)数据查询

1、无参数查询

    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods order by goods_id desc limit 10
    </select>

2、单参数查询

    <!--单参数传递,使用parameterType指定参数的数据类型即可,SQL中#{value}提取参数-->
    <select id="selectById" parameterType="Integer" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where goods_id = #{ value }
    </select>
    // 测试用例
    @Test
    public void testSelectOne() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Goods goods = sqlSession.selectOne("goods.selectById", 1602);
            System.out.println(goods.getTitle());
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

3、多参数查询

    <!--多参数传递,使用parameterType指定Map接口,SQL中#{key}提取参数-->
    <select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods
        where current_price between #{ min } and #{ max }
        order by current_price
        limit 0, #{ limit }
    </select>
    // 测试用例
    @Test
    public void testSelectByPriceRange() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            HashMap map = new HashMap<>();
            map.put("min", 100);
            map.put("max", 500);
            map.put("limit", 10);
            List<Goods> list = sqlSession.selectList("goods.selectByPriceRange", map);
            for (Goods g : list) {
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

4、获取多表关联查询结果

    <!--利用LinkedHashMap保存多表关联结果(使用Map查询结果没有顺序)
    MyBatis会将每一条记录包装为LinkedHashMap对象
    key是字段名,value是字段对应的值,字段类型根据标结构进行自动判断
    优点:易于扩展,易于使用
    缺点:太过灵活,无法进行编译时检查
    -->
    <select id="selectGoodsMap" resultType="java.util.LinkedHashMap">
        select t.*, c.category_name
        from t_goods t join t_category c
        where t.category_id=c.category_id;
    </select>
    // 测试用例
    @Test
    public void testSelectGoodsMap() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            List<Map> list = sqlSession.selectList("goods.selectGoodsMap");
            for (Map map :
                    list) {
                System.out.println(map);
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

5、 ResultMap结果映射

    <!--结果映射-->
    <resultMap id="rsGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <!--设置主键字段与属性映射-->
        <id property="goods.goodsId" column="goods_id"></id>
        <!--设置非主键字段与属性映射-->
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="category.categoryId" column="category_id"></result>
        <result property="category.categoryName" column="category_name"></result>
        <result property="category.parentId" column="parent_id"></result>
        <result property="category.categoryLevel" column="category_level"></result>
        <result property="category.categoryOrder" column="category_order"></result>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rsGoods">
        select t.*, c.*
        from t_goods t join t_category c
        where t.category_id=c.category_id;
    </select>
    // 测试用例
    @Test
    public void testSelectGoddsDTO() throws Exception {  
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            List<GoodsDTO> list = sqlSession.selectList("goods.selectGoodsDTO");
            for (GoodsDTO g : list) {
                System.out.println(g.getGoods().getTitle());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

6、 动态SQL查询

    <select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods
        <where>
            <if test="categoryId != null">
                and category_id = #{ categoryId }
            </if>
            <if test="currentPrice != null">
                and current_price &lt; #{ currentPrice }
            </if>
        </where>
    </select>
    // 测试用例
    @Test
    public void testDynamicSQL() {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Map map = new HashMap<>();
            map.put("category_id", 44);
            map.put("currentPrice", 500);
            List<Goods> list = sqlSession.selectList("goods.dynamicSQL", map);
            for (Goods g : list) {
                System.out.println(g.getTitle());
            }
        } catch (Exception e) {
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

7、OneToMany对象关联查询

    <!--
        resultMap可用于说明一对多或者多对一的映射逻辑
        id 是 resultMap 属性引用的标志
        type 指向One的实体(Goods)
    -->
    <resultMap id="rmGoods1" type="com.imooc.mybatis.entity.Goods">
        <!--映射goods对象的主键到goods_id字段-->
        <id column="goods_id" property="goodsId"></id>
        <!--
            collection的含义是,在
            select * from t_goods limit 0,10 得到结果后,对所有Goods对象遍历得到goods_id字段值,
            并代入到goodsDetail命名空间的findByGoodsId的SQL中执行查询
            将得到的"商品详情"集合赋值给goodsDetails List 对象
        -->
        <collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id" />
    </resultMap>
    <select id="selectOneToMany" resultMap="rmGoods1">
        select * from t_goods limit 0,10;
    </select>
    // 测试用例
    @Test
    public void testOneToMany() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            List<Goods> list = sqlSession.selectList("goods.selectOneToMany");
            for (Goods g : list) {
                System.out.println(g.getTitle() + ":" + g.getGoodsDetails().size());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

8、ManyToOne对象关联查询

    <resultMap id="rmGoodsDetail" type="com.imooc.mybatis.entity.GoodsDetail">
        <id column="gd_id" property="gdId" />
        <association property="goods" select="goods.selectById" column="goods_id" />
    </resultMap>
    <select id="selectManyToOne" resultMap="rmGoodsDetail">
        select * from t_goods_detail limit 32,20;
    </select>
    // 测试用例
    @Test
    public void testManyToOne() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            List<GoodsDetail> list = sqlSession.selectList("goodsDetail.selectManyToOne");
            for (GoodsDetail g : list) {
                System.out.println(g.getGdPicUrl() + ":" + g.getGoods().getTitle());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

(二)数据插入

    <!--selectKey标签是通用方案,适用于所有数据库,但编写麻烦
        selectKey标签需要明确编写获取最新主键的SQL语句
    -->
    <insert id="insert" parameterType="com.imooc.mybatis.entity.Goods">
        insert into t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        values (#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
        <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
            select last_insert_id();
        </selectKey>
    </insert>

    <!--useGeneratedKeys属性只支持"自增主键"数据库,使用简单
        useGeneratedKeys属性会自动根据驱动生成对应的SQL语句
    -->
    <insert id="insert" parameterType="com.imooc.mybatis.entity.Goods" useGeneratedKeys="true" keyProperty="goodsId" keyColumn="goods_id">
        insert into t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        values (#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
    </insert>
    // 测试用例
    @Test
    public void testInsert() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Goods goods = new Goods();
            goods.setTitle("测试标题");
            goods.setSubTitle("测试副标题");
            goods.setOriginalCost(12.f);
            goods.setCurrentPrice(1231.f);
            goods.setDiscount(0.1f);
            goods.setIsFreeDelivery(1);
            goods.setCategoryId(1);
            int num = sqlSession.insert("goods.insert", goods);
            sqlSession.commit();
            System.out.println(goods.getGoodsId());
        } catch (Exception e) {
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }

    }

(三)数据更新

    <update id="update" parameterType="com.imooc.mybatis.entity.Goods">
        update t_goods
        set
            title=#{title},
            sub_title=#{subTitle},
            original_cost=#{originalCost},
            current_price=#{currentPrice},
            discount=#{discount},
            is_free_delivery=#{isFreeDelivery},
            category_id=#{categoryId}
        where goods_id=#{goodsId};
    </update>
    // 测试用例
    @Test
    public void testUpdate() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Goods goods = sqlSession.selectOne("goods.selectById", 740);
            goods.setTitle("更新测试商品标题");
            int num = sqlSession.update("goods.update", goods);
            sqlSession.commit();
        } catch (Exception e) {
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

(四)数据删除

    <delete id="delete" parameterType="Integer">
        delete from t_goods where goods_id=#{value}
    </delete>
    // 测试用例
    @Test
    public void testDelete() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            int num = sqlSession.delete("goods.delete", 740);
            sqlSession.commit();
        } catch (Exception e) {
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

四、MyBatis 预防SQL注入攻击

SQL注入是指攻击者利用SQL漏洞,绕过系统约束,越权获取数据的攻击方式

sql注入.png

MyBatis两种传值方式

  • ${} 文本替换,未经任何处理对SQL文本替换
  • #{}预编译传值,使用预编译传值可以预防SQL注入

五、MyBatis工作流程

work_process.png

六、MyBatis日志管理

  • 日志文件是用于记录系统操作事件的记录文件或文件集合
  • 日志保存历史数据,是诊断问题以及理解系统活动的重要依据
logback官网:https://logback.qos.ch/
1、在 resources目录下新建logback.xml文件(文件名为固定写法),文件内容如下
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <!--
        日志输出级别(优先级高到低)
        error:错误 - 系统的故障日志
        warn:警告 - 存在风险或使用不当的日志
        info:一般性消息
        debug:程序内部用于调试信息
        trace:程序运行的跟踪信息
    -->
    <root level="debug">
        <appender-ref ref="console" />
    </root>
</configuration>

七、MyBatis二级缓存

1、一级缓存默认开启,缓存范围 SqlSession 会话
2、二级缓存手动开启,属于范围 Mapper Namespace

1、二级缓存运行规则
  • 1、二级缓存后默认所有查询操作均使用缓存
  • 2、写操作 commit 提交时对该 namespace 缓存强制清空
  • 3、配置 userCache=false 可以不用缓存
  • 4、配置 flushCache=true 代表强制清空缓存
    @Test
    public void testLv1Cache() throws Exception {
        // 一级缓存默认开启,缓存范围 SqlSession 会话
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Goods goods = sqlSession.selectOne("goods.selectById", 1603);
            Goods goods1 = sqlSession.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode() + ":" + goods1.hashCode());
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }

        try {
            sqlSession = MyBatisUtils.openSession();
            Goods goods = sqlSession.selectOne("goods.selectById", 1603);
            // 写操作 commit 提交时对该 namespace 缓存强制清空
            sqlSession.commit();
            Goods goods1 = sqlSession.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode() + ":" + goods1.hashCode());
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }
    <!--
        开启二级缓存
        eviction:是缓存的清除策略,当缓存对象数量达到上限后,自动触发对应算法对缓存对象清除
        1、LRU  -  最近最久未使用:移除最长时间不被使用的对象(比如size位512,当缓存超过512时,移除这512个钟最长时间不被使用的对象)
        2、LFU  -  最近最少使用:移除最近访问频率最低的对象
        3、FIFO  -  先进先出:按对象进入缓存的顺序来移除它们
        4、SOFT  -  软引用:移除基于垃圾收集器状态和软引用规则的对象
        5、WEAK  -  弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象

        flushInterval:代表间隔多长时间自动清空缓存,单位毫秒,600000 = 10分钟
        size:缓存存储上限,用于保存对象或集合(1个集合算1个对象)的数量上限
        readOnly:设置为true,代表返回只读缓存,每次从缓存取出的是缓存对象本身,这种执行效率较高
                 设置为false,代表每次取出的是缓存对象的"副本",每一次取出的对象都是不同的,这种安全性较高
    -->
    <!--useCache="false"代表不使用缓存,返回对象为list数据量较大的时候,不建议使用缓存,命中率较低,一般单个对象建议使用缓存-->
    <!-- flushCache="true"在sql执行后强制清空缓存,一般在插入数据之后使用-->
    <cache eviction="LRU" flushInterval="600000" size="512" readOnly="true" />

    @Test
    public void testLv2Cache() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Goods goods = sqlSession.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode());
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }

        try {
            sqlSession = MyBatisUtils.openSession();
            Goods goods = sqlSession.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode());
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

八、PageHelper分页插件

官网地址:https://pagehelper.github.io/

1、使用流程
  • 1、maven 引入 PageHelperjssqlparser
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.3.1</version>
        </dependency>
        <dependency>
            <groupId>com.github.jsqlparser</groupId>
            <artifactId>jsqlparser</artifactId>
            <version>4.4</version>
        </dependency>
  • 2、mybatis-config.xml 增加 Plugin 配置
    <!--启用Pagehelper分页插件-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--设置数据库类型-->
            <property name="helperDialect" value="mysql"/>
            <!--分页合理化-->
            <property name="reasonable" value="true"/>
        </plugin>
    </plugins>
  • 3、代码中使用 PageHelper.startPage() 自动分页
    <select id="selectPage" resultType="com.imooc.mybatis.entity.Goods">
        select *
        from t_goods
        where current_price &lt; 1000
    </select>
    @Test
    public void testSelectPage() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            /*startPage方法会自动将下一次查询进行分页*/
            PageHelper.startPage(2, 10);
            Page<Goods> page = (Page)sqlSession.selectList("goods.selectPage");
            System.out.println("总页数:" + page.getPages());
            System.out.println("总记录数:" + page.getTotal());
            System.out.println("开始行号:" + page.getStartRow());
            System.out.println("结束行号:" + page.getEndRow());
            System.out.println("当前页码:" + page.getPageNum());
            List<Goods> list = page.getResult(); // 当前页数据
            for (Goods g : list) {
                System.out.println(g.getTitle());
            }
            System.out.println("");
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

九、MyBatis批处理

1、批量增加
    <!--批量插入-->
    <insert id="batchInsert" parameterType="java.util.List">
        insert into t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        values
        <foreach collection="list" item="item" index="index" separator=",">
            (#{item.title}, #{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})
        </foreach>
    </insert>
    @Test
    public void tesBatchInsert() throws Exception {
        SqlSession sqlSession = null;
        try {
            long st = new Date().getTime();
            sqlSession = MyBatisUtils.openSession();
            List list = new ArrayList();
            for (int i = 0; i < 10000; i++) {
                Goods goods = new Goods();
                goods.setTitle("测试商品哈哈");
                goods.setSubTitle("测试子标题");
                goods.setOriginalCost(200f);
                goods.setCurrentPrice(100f);
                goods.setDiscount(0.5f);
                goods.setIsFreeDelivery(1);
                goods.setCategoryId(43);
                list.add(goods);
            }
            sqlSession.insert("goods.batchInsert", list);
            sqlSession.commit();
            long et = new Date().getTime();
            System.out.println("执行时间:" + (et - st) + "毫秒");
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }
2、批量删除
    <!--批量删除-->
    <delete id="batchDelete" parameterType="java.util.List">
        delete from t_goods where goods_id in
        <foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
            #{item}
        </foreach>
    </delete>
    @Test
    public void testBatchDelete() throws Exception {
        SqlSession sqlSession = null;
        try {
            long st = new Date().getTime();
            sqlSession = MyBatisUtils.openSession();
            List list = new ArrayList();
            for (int i = 2674; i < 10750; i++) {
                list.add(i);
            }
            sqlSession.delete("goods.batchDelete", list);
            sqlSession.commit();
            long et = new Date().getTime();
            System.out.println("执行时间:" + (et - st) + "毫秒");

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