自定义持久化框架

1. 分析jdbc操作问题

1.1 数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。
1.2 Sq|语句在代码中硬编码, 造成代码不易维护,实际应用中sq|变化的可能较大,sq|变动需要改变java代码。
1.3 使用preparedStatement向 占有位符号传参数存在硬编码,因为sq|语句的where条件不一 定,可能多也可能少,修改sq|还要修改代码,系统不易维护。
1.4 对结果集解析存在硬编码(查询列名),sq|变化导 致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便

2.问题解决思路

2.1 数据库频繁创建连接、释放资源:连接池
2.2 sq|语句及参数硬编码:配置文件
2.3手动解析封装返回结果集:反射、内省

3.自定义框架设计

3.1 提供核心配置文件
3.2 sqlMapConfig.xml:存放数据源信息,引入mapper.xml
3.3 Mapper .xml: sql语句的配置文件信息
3.4 实现步骤:
3.4.1.读取配置文件
3.4.2 创建javaBean来存储
-(1) Configuration: 存放数据库基本信息、Map<唯一标识,Mapper> 唯一标识: namespace+"."+id
-(2) MappedStatement: sql语句、statement类型 、输入参数java类型、输出参数java类型
3.4.3 解析配置文件
3.4.3.1 创建SqISessionFactroyBuilder类: 方法: SqlSessionFactory build():
第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement中
第二:创建SqlSessionFactory的实现类DefaultSqISession
3.4.4 创建SqlSessionFactory: 方法: openSession(): 获取sqlSession接口的实 现类实例对象
3.4.5 创建SqISession接口及实现类:主要封装CRUD方法 具体实现:封装JDBC完成对数据库表的查询操作

4.自定义持久层框架的实现:

创建sqlMapConfig.xml 文件

    <!--数据库配置信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver">

        <property name="jdbcUrl" value="jdbc:mysql://192.168.0.125/learn">

        <property name="username" value="root">

        <property name="password" value="root">

    <!--存放mapper.xml的全路径-->

    <mapper resource="UserMapper.xml">

</configuration>

创建 mapper.xml

<mapper namespace="com.learn.dao.IUserDao">
    <!--sql的唯一标识由namespace和id组成 statementId-->
    <select id="findAll" resultType="com.learn.pojo.User">
        select * from user
    </select>

    <!-- 多参数传递 使用面向对象  以对象传递-->
    <select id="findByCondition" resultType="com.learn.pojo.User" paramterType="com.learn.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>
</mapper>

创建 user 实体

package com.learn.pojo;

/**
 * @Author liu
 * @Title: User
 * @ProjectName IPersistence_test
 * @Description: //TODO
 * @date 2020/6/19 15:33
 */
public class User {
    private Integer id;
    private String username;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

创建maven子模块导入相关依赖

 <!--设置jdk编译版本-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>

创建configuration类,此类用来存储datasource以及mapper.xml的配置信息

package com.learn.pojo;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author liu
 * @Title: Configuration
 * @ProjectName IPersistence_test
 * @Description: //TODO
 * @date 2020/6/19 12:46
 */
public class Configuration {

    private DataSource dataSource;
    /**
     * key: statementId sql唯一标识 由xml文件中的namespace和id组成
     */
    Map<String,MappedStatement> mappedStatementMap = new HashMap<>();

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Map<String, MappedStatement> getMappedStatementMap() {
        return mappedStatementMap;
    }

    public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
        this.mappedStatementMap = mappedStatementMap;
    }
}

创建MappedStatement类,此类用来存储mapper.xml的相关信息

package com.learn.pojo;

/**
* @Author liu
* @Title: MappedStatement
* @ProjectName IPersistence_test
* @Description: //TODO
* @date 2020/6/19 12:42
*/
public class MappedStatement {

   /**
    * 1.id标识
    * 2.返回值类型
    * 3.参数类型
    * sql语句
    */
   private String id;
   private String resultType;
   private String paramterType;
   private String sql;

   public String getId() {
       return id;
   }

   public void setId(String id) {
       this.id = id;
   }

   public String getResultType() {
       return resultType;
   }

   public void setResultType(String resultType) {
       this.resultType = resultType;
   }

   public String getParamterType() {
       return paramterType;
   }

   public void setParamterType(String paramterType) {
       this.paramterType = paramterType;
   }

   public String getSql() {
       return sql;
   }

   public void setSql(String sql) {
       this.sql = sql;
   }
}

创建Resources类,此类用来根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中。

package com.learn.io;

import java.io.InputStream;

/**
 * @Author liu
 * @Title: Resources
 * @ProjectName IPersistence_test
 * @Description: //TODO
 * @date 2020/6/19 12:28
 */
public class Resources {
    /**
     * 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
     * @param path 配置文件的路径
     * @return
     */
    public static InputStream getResourceAsSteam(String path) {
        InputStream inputStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return inputStream;
    }
}

创建SqlSessionFactoryBuilder

package com.learn.sqlsession;

import com.learn.config.XMLConfigBuilder;
import com.learn.pojo.Configuration;
import org.dom4j.DocumentException;

import java.beans.PropertyVetoException;
import java.io.InputStream;

/**
 * @Author liu
 * @Title: SqlSessionFactoryBuilder
 * @ProjectName IPersistence_test
 * @Description: //TODO
 * @date 2020/6/19 12:53
 */
public class SqlSessionFactoryBuilder {

    /**
     * 1.使用dom4j解析配置文件,将解析出来的内容封装到configuration中
     * 2.创建sqlSessionFactory对象
     *
     * @param inputStream
     * @return
     */
    public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
        //1.dom4j解析进行封装
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);

        //2.创建sqlSessionFactory对象: 工厂类:生产sqlSession:会话对象  sqlSession封装 crud方法

        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return defaultSqlSessionFactory;
    }
}

创建XMLConfigerBuilder类

package com.learn.config;

import com.learn.io.Resources;
import com.learn.pojo.Configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

/**
 * @Author liu
 * @Title: XMLConfigBuilder
 * @ProjectName IPersistence_test
 * @Description: //TODO
 * @date 2020/6/19 12:58
 */
public class XMLConfigBuilder {

    private Configuration configuration;

    public XMLConfigBuilder() {
        this.configuration = new Configuration();
    }

    /**
     * 该方法就是使用dom4j对配置文件进行解析,封装configuration
     *
     * @param inputStream
     * @return
     */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(inputStream);
        // 获取根对象 拿到configuration
        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name, value);
        }
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));
        configuration.setDataSource(comboPooledDataSource);
        //mapper.xml 解析: 1.拿到路径 2.根据路径拿到字节输入流 3.dom4j 进行解析
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsSteam);
        }
        return configuration;
    }
}

创建XMLMapperBuilder类,此类用来解析业务表的xml文件

package com.learn.config;

import com.learn.pojo.Configuration;
import com.learn.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

/**
 * @Author liu
 * @Title: XMLMapperBuilder
 * @ProjectName IPersistence_test
 * @Description: //TODO
 * @date 2020/6/19 14:13
 */
public class XMLMapperBuilder {

    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    public void parse(InputStream inputStream) throws DocumentException {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");
        //此处只解析了查询操作,其余crud操作举一反三
        List<Element> list = rootElement.selectNodes("//select");
        for (Element element : list) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramterType = element.attributeValue("paramterType");
            String sqlText = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParamterType(paramterType);
            mappedStatement.setSql(sqlText);
            String key = namespace + "." + id;
            configuration.getMappedStatementMap().put(key, mappedStatement);
        }
    }
}

创建sqlSessionFactory݊工厂

package com.learn.sqlsession;

/**
 * @Author liu
 * @Title: SqlSessionFactory
 * @ProjectName IPersistence_test
 * @Description: //TODO
 * @date 2020/6/19 12:56
 */
public interface SqlSessionFactory {

    public SqlSession openSession();
}

创建DefaultSqlSessionFactory

package com.learn.sqlsession;

import com.learn.pojo.Configuration;

/**
 * @Author liu
 * @Title: DefaultSqlSessionFactory
 * @ProjectName IPersistence_test
 * @Description: //TODO
 * @date 2020/6/19 15:16
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }
}

sqlSession݊接口DefaultSqlSession实现类

package com.learn.sqlsession;

import java.util.List;

/**
 * @Author liu
 * @Title: SqlSession
 * @ProjectName IPersistence_test
 * @Description: //TODO
 * @date 2020/6/19 15:20
 */
public interface SqlSession {

    /**
     * 查询所有
     * @param <E>
     * @param statementId
     * @param params
     * @return
     */
    public <E> List<E> selectList(String statementId,Object... params) throws Exception;

    /**
     * 根据条件查询单个
     * @param statementId
     * @param params
     * @param <T>
     * @return
     */
    public <T> T selectOne(String statementId,Object... params) throws Exception;

    /**
     * 为dao接口生成代理实现类
     * @param mapperClass
     * @param <T>
     * @return
     */
    public <T> T getMapper(Class<?> mapperClass);
}
package com.learn.sqlsession;

import com.learn.pojo.Configuration;
import com.learn.pojo.MappedStatement;

import java.lang.reflect.*;
import java.util.List;

/**
 * @Author liu
 * @Title: DefaultSqlSession
 * @ProjectName IPersistence_test
 * @Description: //TODO
 * @date 2020/6/19 15:21
 */
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        //完成对jdbc的封装,完成对simpleExecutor里的query方法的调用
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        List<E> list = simpleExecutor.query(configuration, mappedStatement, params);
        return list;
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<T> t = selectList(statementId, params);
        if (t.size() == 1) {
            return t.get(0);
        } else {
            throw new RuntimeException("查询结果为空或者返回结果过多");
        }
    }

    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        //使用jdk动态代理 来为dao接口生成代理对象并返回
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //底层都还是去执行jdbc代码 根据不同情况调用不同的crud方法
                //准备参数 1.statmentId :sql语句的唯一标识: namespace.id
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statmentId = className + "." + methodName;
                //准备参数2 : params:args
                //获取被调用方法的返回值类型
                Type genericReturnType = method.getGenericReturnType();
                if (genericReturnType instanceof ParameterizedType) {
                    List<Object> objects = selectList(statmentId, args);
                    return objects;
                }
                return selectOne(statmentId, args);
            }
        });
        return (T) proxyInstance;
    }
}

创建Executor

package com.learn.sqlsession;

import com.learn.pojo.Configuration;
import com.learn.pojo.MappedStatement;

import java.sql.SQLException;
import java.util.List;

/**
 * @Author liu
 * @Title: Executor
 * @ProjectName IPersistence_test
 * @Description: 封装jdbc
 * @date 2020/6/19 15:42
 */
public interface Executor {

    /**
     * 查询接口
     * 对jdbc进行封装
     * @param configuration
     * @param mappedStatement
     * @param params
     * @param <E>
     * @return
     */
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement,Object... params) throws SQLException, Exception;
}

创建SimpleExecutor

package com.learn.sqlsession;

import com.learn.config.BoundSql;
import com.learn.pojo.Configuration;
import com.learn.pojo.MappedStatement;
import com.learn.utils.GenericTokenParser;
import com.learn.utils.ParameterMapping;
import com.learn.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

/**
* @Author liu
* @Title: SimpleExecutor
* @ProjectName IPersistence_test
* @Description: //TODO
* @date 2020/6/19 15:46
*/
public class SimpleExecutor implements Executor {

   @Override
   public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
       //1.注册驱动 获取数据库连接
       Connection connection = configuration.getDataSource().getConnection();
       // 2.获取sql语句  转换sql语句为能被jdbc能识别的语句,转换的过程中,还需要对#{}中的值进行解析存储
       String sql = mappedStatement.getSql();
       BoundSql boundSql = getBoundSql(sql);
       // 3.获取与处理对象 preparedStatement
       PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
       //4. 设置参数
       //获取到参数的全路径
       String paramterType = mappedStatement.getParamterType();
       Class<?> paramterTypeClass = getClassType(paramterType);
       List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
       for (int i = 0; i < parameterMappings.size(); i++) {
           ParameterMapping parameterMapping = parameterMappings.get(i);
           String content = parameterMapping.getContent();
           //反射  根据参数名称获取实体属性中的值 然后根据参数获取到params中具体的值
           Field declaredField = paramterTypeClass.getDeclaredField(content);
           //防止对象是私有的,设置暴力访问
           declaredField.setAccessible(true);
           Object o = declaredField.get(params[0]);
           preparedStatement.setObject(i + 1, o);
       }
       //5.执行sql
       ResultSet resultSet = preparedStatement.executeQuery();
       String resultType = mappedStatement.getResultType();
       Class<?> resultTypeClass = getClassType(resultType);
       ArrayList<Object> objects = new ArrayList<>();
       //6.封装返回结果集
       while (resultSet.next()) {
           Object o = resultTypeClass.newInstance();
           //取出元数据 (表)
           ResultSetMetaData metaData = resultSet.getMetaData();
           //获取到总列数 getColumnCount
           for (int i = 1; i <= metaData.getColumnCount(); i++) {
               //获取到字段名   columnName下表为1开始
               String columnName = metaData.getColumnName(i);
               // 获取到字段的值
               Object value = resultSet.getObject(columnName);
               // 使用反射或者内省 根据数据库表和实体的对应关系完成封装 并生成读写方法
               PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
               Method writeMethod = propertyDescriptor.getWriteMethod();
               writeMethod.invoke(o, value);
           }
           objects.add(o);
       }
       return (List<E>) objects;
   }

   private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
       if (paramterType != null) {
           Class<?> aClass = Class.forName(paramterType);
           return aClass;
       }
       return null;
   }

   /**
    * 完成对#{} 的解析
    * 1.讲#{}使用 ? 进行代替。
    * 2.解析出#{} 中的值进行存储
    *
    * @param sql
    * @return
    */
   private BoundSql getBoundSql(String sql) {
       //标记处理类:配置标记解析器来完成对占位符的解析处理工作
       ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
       GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
       //解析后的sql
       String parseSql = genericTokenParser.parse(sql);
       //#{} 中解析出来的参数名称
       List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
       BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
       return boundSql;
   }
}

创建BoundSql类,用来存储转换过程中的sql语句,以及#{} 中的参数名称

package com.learn.config;

import com.learn.utils.ParameterMapping;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author liu
 * @Title: BoundSql
 * @ProjectName IPersistence_test
 * @Description: 用来存储转换过程中的sql语句,以及#{} 中的参数名称
 * @date 2020/6/19 16:02
 */
public class BoundSql {
    /**
     * 解析过后的sql
     */
    private String sqlText;
    private List<ParameterMapping> parameterMappings = new ArrayList<>();

    public String getSqlText() {
        return sqlText;
    }

    public void setSqlText(String sqlText) {
        this.sqlText = sqlText;
    }

    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }

    public void setParameterMappings(List<ParameterMapping> parameterMappings) {
        this.parameterMappings = parameterMappings;
    }

    public BoundSql(String sqlText, List<ParameterMapping> parameterMappings) {
        this.sqlText = sqlText;
        this.parameterMappings = parameterMappings;
    }
}

好了我们的自定义持久化框架目前已经搭建完毕,具体目录结构如下图:


QQ图片20200621011502.png

源码地址:https://gitee.com/FirstXiaoMaGe/custom-persistence-framework

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