Spring Data JPA-基础篇(二)

前置文章:

Spring Data JPA-基础篇(一)

前言:

前置文章中我们已经介绍了基础JPA的使用方式,JPA是操作数据库的一种ORM规范,而文章中使用的Hibernate是其具体的实现。
本文则是使用Spring Data JPA来进行具体的CRUD操作。

零、本文纲要

一、基础准备

  1. pom.xml配置
  2. applicationContext.xml
  3. Customer实体类
  4. CustomerDao接口

二、基础CRUD

  1. 测试准备
  2. 测试查询
  3. 测试保存
  4. 测试删除
  5. 测试查询所有
  6. 测试聚合查询
  7. 判断是否存在

三、JPQL形式查询

  1. 条件查询
  2. 多条件查询
  3. 更新操作

四、SQL形式查询

  1. 查询全部
  2. 模糊匹配

五、方法命名规则查询

  1. 条件查询
  2. 模糊匹配
  3. 多条件查询

一、基础准备

1. pom.xml配置

① 属性控制
    <!--属性控制-->
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <spring.version>5.1.20.RELEASE</spring.version>
        <hibernate.version>5.0.7.Final</hibernate.version>
        <javax.el.version>2.2.4</javax.el.version>
        <slf4j.version>1.6.6</slf4j.version>
    </properties>
② 依赖管理

此处依赖部分并没有过多说明,因为现在大家使用的Spring Boot、Spring Cloud都在淡化我们依赖配置的内容,更关注代码开发本身。

    <!--依赖管理-->
    <dependencies>
        <!--Junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--Spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.9.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--el-->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>${javax.el.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.web</groupId>
            <artifactId>javax.el</artifactId>
            <version>${javax.el.version}</version>
        </dependency>
        <!--hibernate-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.2.1.Final</version>
        </dependency>
        <!--MySQL-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!--c3p0数据库连接池-->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <!--log-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
    </dependencies>

2. applicationContext.xml

① 基础约束准备

引入了beans、aop、context、jdbc、tx、jpa这些命名空间。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/jdbc
       http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/data/jpa
       http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

</beans>
② 数据源配置
    <!-- 1. 创建数据库连接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///test"/>
        <property name="user" value="root"/>
        <property name="password" value="root"/>
    </bean>
③ 创建entityManagerFactory对象
    <!-- 2. 创建entityManagerFactory对象 -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!-- ① 配置数据源 -->
        <property name="dataSource" ref="dataSource"/>

        <!-- ② 指定实体类所在的包 -->
        <property name="packagesToScan" value="com.stone.domain"/>

        <!-- ③ 配置JPA的实现厂商(供应商) -->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
        </property>

        <!-- ④ JPA的供应商适配器 -->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!-- a、配置是否自动创建数据库表 -->
                <property name="generateDdl" value="false"/>
                <!-- b、指定数据库类型 -->
                <property name="database" value="MYSQL"/>
                <!-- c、数据库方言:支持的特有语法,MySQL分页limit,Oracle分页ROWNUM -->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
                <!-- d、是否显示SQL语句 -->
                <property name="showSql" value="true"/>
            </bean>
        </property>

        <!--⑤ JPA方言:高级特性-->
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
        </property>
    </bean>
④ 配置事务管理器
    <!-- 3. 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
⑤ 整合Spring Data JPA
    <!-- 4. 整合Spring Data JPA -->
    <jpa:repositories base-package="com.stone.dao" transaction-manager-ref="transactionManager"
                      entity-manager-factory-ref="entityManagerFactory"/>
⑥ 声明式事务

此处非必要

    <!-- txAdvice -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save" propagation="REQUIRED"/>
            <tx:method name="insert" propagation="REQUIRED"/>
            <tx:method name="update" propagation="REQUIRED"/>
            <tx:method name="delete" propagation="REQUIRED"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <!-- aop -->
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.stone.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
    </aop:config>
⑦ 配置包扫描
    <context:component-scan base-package="com.stone"/>
⑧ 整合其他配置文件

此处暂时没有。

3. Customer实体类

/**
 * 客户实体类
 * → 配置映射关系
 *      1. 实体类和表的映射关系;
 *      2. 实体类中属性和表中字段的映射关系;
 * → 实体类和表的映射关系
 *      ① @Entity:声明实体类;
 *      ② @Table:配置实体类和表的映射关系;
 *          → name:配置数据库表名称;
 * → 实体类中属性和表中字段的映射关系
 *      ① @Id:声明主键的配置;
 *      ② @GeneratedValue:配置主键生成策略;
 *          → strategy:主键策略
 *              a、 GenerationType.IDENTITY  自增,MySQL数据库;
 *                  底层数据库必须支持自动增长,采用数据库自增方式对ID进行自增。
 *              b、 GenerationType.SEQUENCE  序列,Oracle数据库;
 *                  底层数据库支持序列。
 *              c、 GenerationType.TABLE
 *                  JPA提供的一种机制,通过一张数据库表的形式帮助完成主键自增。
 *              d、 GenerationType.AUTO
 *                  由程序自动选择主键生成策略。
 *      ③ Column:实体类属性与表字段映射
 *          → name:数据库表字段名称
 */
@Table(name = "cst_customer")
@Entity
public class Customer {

    /*客户主键*/
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId;

    /*客户名称*/
    @Column(name = "cust_name")
    private String custName;

    /*客户资源来源*/
    @Column(name = "cust_source")
    private String custSource;

    /*客户级别*/
    @Column(name = "cust_level")
    private String custLevel;

    /*客户所属行业*/
    @Column(name = "cust_industry")
    private String custIndustry;

    /*客户的联系方式*/
    @Column(name = "cust_phonoe")
    private String custPhone;

    /*客户地址*/
    @Column(name = "cust_address")
    private String custAddress;

    ...

}

4. CustomerDao接口

NOTE
JpaRepository<T, ID>,该接口封装了基础CRUD操作:
→ T:操作的实体类类型;
→ ID:实体类中主键属性的类型。

JpaSpecificationExecutor<T>,该接口封装了复杂查询(分页):
→ T:操作的实体类类型。

/**
 * 符合SpringDataJpa规范的接口
 * → JpaRepository<T, ID>,该接口封装了基础CRUD操作
 * T:操作的实体类类型;
 * ID:实体类中主键属性的类型。
 * → JpaSpecificationExecutor<T>,该接口封装了复杂查询(分页)
 * T:操作的实体类类型。
 */
public interface CustomerDao extends JpaRepository<Customer, Long>, 
JpaSpecificationExecutor<Customer> {
    ...
}

二、基础CRUD

1. 测试准备

@RunWith(SpringJUnit4ClassRunner.class) //声明Spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml") //指定Spring容器配置信息
public class CustomerDaoTest {

    @Autowired
    private CustomerDao customerDao;

    ...

}

2. 测试查询

NOTE
findOne → em.find() → 立即加载;
getOne → em.getReference() → 延迟加载;

    /**
     * 测试跟据ID查询,findOne方法
     */
    @Test
    public void testFindOne() {
        Customer customer = customerDao.findOne(1L);
        System.out.println(customer);
    }

    /**
     * 跟据ID从数据库查询,getOne方法需要使用@Transactional注解
     * findOne → em.find():立即加载
     * getOne → em.getReference():延迟加载
     */
    @Test
    @Transactional
    public void testGetOne() {
        Customer customer = customerDao.getOne(5L);
        System.out.println(customer);
    }

3. 测试保存

NOTE
① 实体对象未设置主键属性值:直接保存;
② 实体对象设置主键属性值:
→ a、主键存在,进行更新;
→ b、主键不存在,跟据主键策略生成主键进行插入(保存)。

    /**
     * 测试更新|保存,save方法
     * → 先查询,后更新|保存
     * 如果没有设置ID属性,则进行保存操作;如果设置了ID属性,则会进行更新|保存。
     * ① 如果指定ID数据存在,则进行更新操作;
     * ② 如果指定ID数据不存在,则进行保存操作(注意:保存时主键会依据主键策略自动生成,而不是指定的主键)。
     */
    @Test
    public void testSave() {
        Customer customer = new Customer();
        customer.setCustId(22L);
        customer.setCustName("Stone");
        customerDao.save(customer);
    }

4. 测试删除

    /**
     * 测试删除delete
     * → 先查询,后删除
     */
    @Test
    public void testDelete() {
        customerDao.delete(21L);
    }

5. 测试查询所有

    /**
     * 测试查询所有findAll
     */
    @Test
    public void testFindAll() {
        List<Customer> customers = customerDao.findAll();
        for (Customer customer : customers) {
            System.out.println(customer);
        }
    }

6. 测试聚合查询

    /**
     * 测试聚合(统计)查询
     */
    @Test
    public void testCount() {
        long count = customerDao.count();
        System.out.println(count);
    }

7. 判断是否存在

    /**
     * 测试查询判断是否存在
     * 传统方式:
     * ① 判断查询结果是否为空null;
     * ② 判断查询结果条数是否大于0;【JPA采用此方式】
     */
    @Test
    public void testExists() {
        boolean exists = customerDao.exists(4L);
        System.out.println(exists);
    }

三、JPQL形式查询

1. 条件查询

① 接口方法准备
    /**
     * 跟据客户名称查询客户信息
     * JPQL:FROM Customer WHERE custName = ?
     * 注意:在@Query注解的value属性中,需要使用"?1"来指定具体占位。
     *
     * @param custName 客户名称
     * @return 客户信息
     */
    @Query(value = "FROM Customer WHERE custName = ?1")
    public Customer findJpql(String custName);
② 测试
    /**
     * 测试JPQL查询
     */
    @Test
    public void testJpql() {
        Customer yinRui = customerDao.findJpql("Yin Rui");
        System.out.println(yinRui);
    }

2. 多条件查询

① 接口方法准备
    /**
     * JPQL:FROM Customer WHERE custName = ? AND custId = ?
     *
     * @param custName 客户名称
     * @param custId   客户ID
     * @return 客户信息
     */
    @Query(value = "FROM Customer WHERE custName = ?1 AND custId = ?2")
    public Customer findCustNameAndCustId(String custName, Long custId);
② 测试
    /**
     * 测试JPQL多条件查询
     */
    @Test
    public void testJpqlAnd() {
        Customer nicholasDunn = customerDao.findCustNameAndCustId("Nicholas Dunn", 19L);
        System.out.println(nicholasDunn);
    }

3. 更新操作

① 接口方法准备

NOTE
相比于查询方法,此处需要配合 @Modifying 注解一起使用。

    /**
     * 更新客户姓名(@Query:代表进行查询;@Modifying:代表当前执行方法进行更新操作。)
     * SQL:UPDATE cst_customer SET cust_name = ? WHERE cust_id = ?
     * JPQL:UPDATE Customer SET custName = ? WHERE custId = ?
     *
     * @param custName
     * @param custId
     */
    @Query(value = "UPDATE Customer SET custName = ?1 WHERE custId = ?2")
    @Modifying //(@Modifying+@Query)一起使用,代表更新操作。
    public void updateCustomer(String custName, Long custId);
② 测试
    /**
     * 测试JPQL更新操作
     * → SpringDataJpa中使用jpql完成 更新|删除操作 需要手动添加事务支持;
     * → 在@Test注解下,默认SpringDataJpa执行完成会进行数据回滚;
     * → 使用@Rollback(value = false),关闭自动回滚。
     */
    @Test
    @Transactional //添加事务支持
    @Rollback(value = false)
    public void testUpdate() {
        customerDao.updateCustomer("Jeff Stone", 1L);
    }

四、SQL形式查询

1. 查询全部

① 接口方法准备
    /**
     * SQL:SELECT * FROM cst_customer
     * → 使用@Query注解,配置:
     * ① value属性:sql;
     * ② nativeQuery属性:使用本地sql查询,true;反之,false。
     *
     * @return 查询对象集合
     */
    @Query(value = "SELECT * FROM cst_customer", nativeQuery = true)
    public List<Object[]> findSql();
② 测试
    /**
     * 测试使用本地SQL查询全部客户信息
     */
    @Test
    public void testSql() {
        List<Object[]> objects = customerDao.findSql();
        for (Object[] object : objects) {
            System.out.println(Arrays.toString(object));
        }
    }

2. 模糊匹配

① 接口方法准备
    /**
     * 使用SQL查询,进行模糊匹配
     * SQL:SELECT * FROM cst_customer WHERE cust_name LIKE ?
     *
     * @param custName 客户名称
     * @return 客户信息
     */
    @Query(value = "SELECT * FROM cst_customer WHERE cust_name LIKE ?1", nativeQuery = true)
    public List<Object[]> findSqlLike(String custName);
② 测试
    /**
     * 测试SQL查询,模糊匹配
     */
    @Test
    public void testSqlLike() {
        List<Object[]> objects = customerDao.findSqlLike("%Hicks");
        for (Object[] object : objects) {
            System.out.println(Arrays.toString(object));
        }
    }

五、方法命名规则查询

1. 条件查询

① 接口方法准备

NOTE
查询方法写法:findBy + 属性名称(field_name),等值匹配

    /**
     * 方法命名规则查询,是对JPQL查询的更深层的封装,使用该规则可以不用再写JPQL语句。
     * → 查询方法写法:findBy + 属性名称(field_name),等值匹配
     * ① 以findBy开头;
     * ② 拼接对象中的属性,属性首字母改为大写;
     *
     * @param custName 客户名称
     * @return 客户信息
     */
    public Customer findByCustName(String custName);
② 测试
    /**
     * 测试按照方法命名规则查询
     */
    @Test
    public void testFindByCustName() {
        Customer itoRiku = customerDao.findByCustName("Ito Riku");
        System.out.println(itoRiku);
    }

2. 模糊匹配

① 接口方法准备

NOTE
查询方法写法:findBy + 属性名称(field_name) + 查询方式[Like|Isnull]

    /**
     * 跟据方法命名规则,进行模糊匹配
     * → 查询方法写法:findBy + 属性名称(field_name) + 查询方式[Like|Isnull]
     *
     * @param custName 客户名称
     * @return 客户信息
     */
    public List<Customer> findByCustNameLike(String custName);
② 测试
    /**
     * 测试模糊匹配查询(注意:由于时占位符的形式进行填充,"%"的位置信息需要自己判断。)
     */
    @Test
    public void testFindByCustNameLike() {
        List<Customer> customers = customerDao.findByCustNameLike("%Yuito");
        for (Customer customer : customers) {
            System.out.println(customer);
        }
    }

3. 多条件查询

① 接口方法准备

NOTE
查询方法写法:findBy + 属性名称(field_name) + 查询方式[Like|Isnull] + 多条件连接符(And|Or) + 属性名称(field_name) + 查询方式[Like|Isnull]

    /**
     * 跟据方法命名规则,进行多条件查询
     * → 查询方法写法:findBy + 属性名称(field_name) + 查询方式[Like|Isnull] + 多条件连接符(And|Or)
     * + 属性名称(field_name) + 查询方式[Like|Isnull]
     *
     * @param CustName 客户名称
     * @param CustAddress 客户地址
     * @return 客户信息
     */
    public List<Customer> findByCustNameLikeAndCustAddress(String CustName, String CustAddress);
② 测试
    /**
     * 测试多条件查询
     */
    @Test
    public void testFindByCustNameLikeAndCustAddress() {
        List<Customer> customers =
                customerDao.findByCustNameLikeAndCustAddress("Xue%", "536 Wall Street");
        for (Customer customer : customers) {
            System.out.println(customer);
        }
    }

六、结尾

以上即为Spring Data JPA-基础篇(二)的全部内容,感谢阅读。

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

推荐阅读更多精彩内容