Spring Boot 中Spring Data JPA的應(yīng)用

關(guān)于Spring Data

Spring社區(qū)的一個頂級工程,主要用于簡化數(shù)據(jù)(關(guān)系型&非關(guān)系型)訪問,如果我們使用Spring Data來開發(fā)程序的話,那么可以省去很多低級別的數(shù)據(jù)訪問操作,如編寫數(shù)據(jù)查詢語句、DAO類等,我們僅需要編寫一些抽象接口并定義相關(guān)操作即可,Spring會在運行期間的時候創(chuàng)建代理實例來實現(xiàn)我們接口中定義的操作。

關(guān)于Spring Data子項目

Spring Data擁有很多子項目,除了Spring Data Jpa外,還有如下子項目。

  • Spring Data Commons
  • Spring Data MongoDB
  • Spring Data Redis
  • Spring Data Solr
  • Spring Data Gemfire
  • Spring Data REST
  • Spring Data Neo4j

首先了解JPA是什么?

JPA(Java Persistence API)是Sun官方提出的Java持久化規(guī)范。它為Java開發(fā)人員提供了一種對象/關(guān)聯(lián)映射工具來管理Java應(yīng)用中的關(guān)系數(shù)據(jù)。他的出現(xiàn)主要是為了簡化現(xiàn)有的持久化開發(fā)工作和整合ORM技術(shù),結(jié)束現(xiàn)在Hibernate,TopLink,JDO等ORM框架各自為營的局面。值得注意的是,JPA是在充分吸收了現(xiàn)有Hibernate,TopLink,JDO等ORM框架的基礎(chǔ)上發(fā)展而來的,具有易于使用,伸縮性強等優(yōu)點。從目前的開發(fā)社區(qū)的反應(yīng)上看,JPA受到了極大的支持和贊揚,其中就包括了Spring與EJB3.0的開發(fā)團隊。

注意:JPA是一套規(guī)范,不是一套產(chǎn)品,那么像Hibernate,TopLink,JDO他們是一套產(chǎn)品,如果說這些產(chǎn)品實現(xiàn)了這個JPA規(guī)范,那么我們就可以叫他們?yōu)镴PA的實現(xiàn)產(chǎn)品。

關(guān)于Spring Data Jpa

Spring Data JPA 是 Spring 基于ORM 框架、JPA 規(guī)范的基礎(chǔ)上封裝的一套JPA應(yīng)用框架,可使開發(fā)者用極簡的代碼即可實現(xiàn)對數(shù)據(jù)的訪問和操作。它提供了包括增刪改查等在內(nèi)的常用功能,且易于擴展!學(xué)習(xí)并使用 Spring Data JPA 可以極大提高開發(fā)效率!

Spring Data Jpa是Spring Data的一個子項目,主要用于簡化數(shù)據(jù)訪問層的實現(xiàn),使用Spring Data Jpa可以輕松實現(xiàn)增刪改查、分頁、排序等。

示例:Spring Boot + Spring Data Jpa

添加POM.XML文件

<!--Spring Data Jpa-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!--Web應(yīng)用-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<!--緩存-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<!--整合Swagger2-->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.6.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.6.0</version>
</dependency>

編寫實體類User

package com.pingkeke.rdf.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import java.io.Serializable;

/**
 * User.
 *
 * 注意下這里的@NamedQuery注解,
 * 大致意思就是讓我們在Repository接口中定義的findByName方法不使用默認(rèn)的查詢實現(xiàn),
 * 取而代之的是使用這條自定義的查詢語句去查詢,如果這里沒有標(biāo)注的話,會使用默認(rèn)實現(xiàn)的。
 */
@Entity
@NamedQuery(name = "User.findByName", query = "select name,address from User u where u.name=?1")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    long id;
    @Column(name = "name")
    String name;
    @Column(name = "address")
    String address;

    public long getId()
    {
        return id;
    }

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

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getAddress()
    {
        return address;
    }

    public void setAddress(String address)
    {
        this.address = address;
    }

}

編寫Repository接口

這里將編寫兩個Repository接口,僅僅用于示例,實際中可以合并成一個:

package com.pingkeke.rdf.repository;

import com.pingkeke.rdf.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * UserJpaRepository.
 * 這里的UserJpaRepository接口實現(xiàn)了JpaRepository接口;
 *
 * 實際上JpaRepository實現(xiàn)了PagingAndSortingRepository接口,
 * PagingAndSortingRepository接口實現(xiàn)了CrudRepository接口,
 * CrudRepository接口實現(xiàn)了Repository接口;

 簡單說明下:

 Repository接口是一個標(biāo)識接口,里面是空的;

 CrudRepository接口定義了增刪改查方法;

 PagingAndSortingRepository接口用于分頁和排序;

 由于JpaRepository接口繼承了以上所有接口,所以擁有它們聲明的所有方法;
 */
public interface UserJpaRepository extends JpaRepository<User,Long> {

}
package com.pingkeke.rdf.repository;

import com.pingkeke.rdf.domain.User;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;

import java.util.List;

/**
 * UserRepository.
 *
 * 這里的UserRepository接口主要定義了一些查詢方法;

 比如這里的findByNameAndAddress和findByName方法,
 我們是不需要額外定義其它查詢語句就可以直接執(zhí)行的,
 Spring Data Jpa會根據(jù)實體類的屬性名字以及方法名自動實現(xiàn)該方法;

 PS:由于我們在實體類中聲明了@NamedQuery注解,
 實際上findByName方法會使用@NamedQuery注解標(biāo)注的查詢語句去查詢;

 另外這里的findByName1方法使用了HQL語句查詢;

 findByName2方法使用了原始的sql語句查詢;
 */
public interface UserRepository extends Repository<User, Long> {

    List<User> findByNameAndAddress(String name, String address);

    @Query(value = "from User u where u.name=:name")
    List<User> findByName1(@Param("name") String name);

    @Query(value = "select * from #{#entityName} u where u.name=?1", nativeQuery = true)
    List<User> findByName2(String name);

    List<User> findByName(String name);
}

編寫Service

package com.pingkeke.rdf.service;

import com.pingkeke.rdf.domain.User;
import org.springframework.data.domain.Page;

import java.util.List;

/**
 * IUserService.
 */
public interface IUserService {

    List<User> findAll();

    void saveUser(User book);

    User findOne(long id);

    void delete(long id);

    List<User> findByName(String name);

    Page<User> findPage(int page,int size);
}
package com.pingkeke.rdf.service.impl;

import com.pingkeke.rdf.domain.User;
import com.pingkeke.rdf.repository.UserJpaRepository;
import com.pingkeke.rdf.repository.UserRepository;
import com.pingkeke.rdf.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * UserServiceImpl.
 */

@Service
@Transactional
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserJpaRepository userJpaRepository;
    @Autowired
    private UserRepository userRepository;

    public List<User> findAll()
    {
        return userJpaRepository.findAll();
    }

    public List<User> findByName(String name)
    {
        List<User> userList1 = userRepository.findByName1(name);
        List<User> userList2 = userRepository.findByName2(name);
        List<User> userList3 = userRepository.findByNameAndAddress(name, "111");
        System.out.println("userList1:" + userList1);
        System.out.println("userList2:" + userList2);
        System.out.println("userList3:" + userList3);
        return userRepository.findByName(name);
    }

    public void saveUser(User user)
    {
        userJpaRepository.save(user);
    }

    @Cacheable("users")
    public User findOne(long id)
    {
        System.out.println("Cached Pages");
        return userJpaRepository.findOne(id);
    }

    public void delete(long id)
    {
        userJpaRepository.delete(id);
    }


    /**
     * Pageable 是Spring Data庫中定義的一個接口,
     * 該接口是所有分頁相關(guān)信息的一個抽象,
     * 通過該接口,我們可以得到和分頁相關(guān)所有信息(例如pageNumber、pageSize等),
     * 這樣,Jpa就能夠通過pageable參數(shù)來得到一個帶分頁信息的Sql語句。
     *
     * Page類也是Spring Data提供的一個接口,
     * 該接口表示一部分?jǐn)?shù)據(jù)的集合以及其相關(guān)的下一部分?jǐn)?shù)據(jù)、數(shù)據(jù)總數(shù)等相關(guān)信息,
     * 通過該接口,我們可以得到數(shù)據(jù)的總體信息(數(shù)據(jù)總數(shù)、總頁數(shù)...)
     * 以及當(dāng)前數(shù)據(jù)的信息(當(dāng)前數(shù)據(jù)的集合、當(dāng)前頁數(shù)等)

     * @return
     */
    public Page<User> findPage(int page,int size) {

        // int page=0,size=10;
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        Pageable pageable = new PageRequest(page, size, sort);
        return userJpaRepository.findAll(pageable);

    }



}

分頁和排序

Pageable定義了很多方法,但其核心的信息只有兩個:一是分頁的信息(page、size),二是排序的信息。Spring Data Jpa提供了PageRequest的具體實現(xiàn),我們只提供分頁以及排序信息即可。

我們可以看到,我們只需要在方法的參數(shù)中直接定義一個pageable類型的參數(shù),當(dāng)Spring發(fā)現(xiàn)這個參數(shù)時,Spring會自動的根據(jù)request的參數(shù)來組裝該pageable對象,Spring支持的request參數(shù)如下:

  • page,第幾頁,從0開始,默認(rèn)為第0頁
  • size,每一頁的大小,默認(rèn)為20
  • sort,排序相關(guān)的信息,以property,property(,ASC|DESC)的方式組織,例如sort=firstname&sort=lastname,desc表示在按firstname正序排列基礎(chǔ)上按lastname倒序排列
    這樣,我們就可以通過url的參數(shù)來進行多樣化、個性化的查詢,而不需要為每一種情況來寫不同的方法了。

通過url來定制pageable很方便,但唯一的缺點是不太美觀,因此我們需要為pageable設(shè)置一個默認(rèn)配置,這樣很多情況下我們都能夠通過一個簡潔的url來獲取信息了。
Spring提供了@PageableDefault幫助我們個性化的設(shè)置pageable的默認(rèn)配置。例如@PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)表示默認(rèn)情況下我們按照id倒序排列,每一頁的大小為15。

請求返回示例

{
    "content": [
        {
            "id": 102, 
            "name": "110", 
            "address": "111"
        }, 
        {
            "id": 101, 
            "name": "2", 
            "address": "111"
        }
    ], 
    "last": false, 
    "totalPages": 2, 
    "totalElements": 3, 
    "number": 0, 
    "size": 2, 
    "sort": [
        {
            "direction": "DESC", 
            "property": "id", 
            "ignoreCase": false, 
            "nullHandling": "NATIVE", 
            "ascending": false, 
            "descending": true
        }
    ], 
    "first": true, 
    "numberOfElements": 2
}

怎么樣,信息是不是很豐富,代碼是不是很簡單,快點來嘗試一下Jpa的分頁查詢吧。

編寫Controller

package com.pingkeke.rdf.controller;

import com.pingkeke.rdf.domain.User;
import com.pingkeke.rdf.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * UserController.
 * http://localhost:8080/swagger-ui.html
 */
@Api(tags = "用戶管理相關(guān)接口")
@RestController
@RequestMapping(value = "/users")
public class UserController {

    @Autowired
    private IUserService userService;

    @ApiOperation(value="創(chuàng)建用戶", notes="根據(jù)User對象創(chuàng)建用戶")
    @RequestMapping(value = "/add/{id}/{name}/{address}", method=RequestMethod.GET)
    public User addUser(@PathVariable int id, @PathVariable String name,
                        @PathVariable String address)
    {
        User user = new User();
        user.setId(id);
        user.setName(name);
        user.setAddress(address);
        userService.saveUser(user);
        return user;
    }

    @ApiOperation(value="刪除用戶", notes="根據(jù)url的id來指定刪除對象")
    @ApiImplicitParam(name = "id", value = "用戶ID", required = true, paramType="path", dataType = "Long")
    @RequestMapping(value="/{id}", method=RequestMethod.DELETE)
    public void deleteUser(@PathVariable Long id)
    {
        userService.delete(id);
    }

    @ApiOperation(value="獲取用戶列表", notes="")
    @RequestMapping(value={""}, method= RequestMethod.GET)
    public List<User> getUsers()
    {
        return userService.findAll();
    }

    @ApiOperation(value="分頁獲取用戶列表", notes="")
    @RequestMapping(value={"getPageUsers/{page}/{size}"}, method= RequestMethod.GET)
    public Page<User> getPageUsers(@PathVariable int page, @PathVariable int size)
    {
        return userService.findPage(page,size);
    }

    @ApiOperation(value="獲取用戶詳細(xì)信息", notes="根據(jù)url的id來獲取用戶詳細(xì)信息")
    @ApiImplicitParam(name = "id", value = "用戶ID", required = true, paramType="path", dataType = "Long")
    @RequestMapping(value="/{id}", method=RequestMethod.GET)
    public User getUser(@PathVariable int id)
    {
        User user = userService.findOne(id);
        return user;
    }

    @ApiOperation(value="獲取用戶列表", notes="根據(jù)用戶名查詢")
    @RequestMapping(value = "/search/name/{name}", method=RequestMethod.GET)
    public List<User> getUserByName(@PathVariable String name)
    {
        List<User> users = userService.findByName(name);
        return users;
    }

}



/**
 *
 * spring-data-jpa-example
 *
 * http://localhost:8080/users/
 http://localhost:8080/users/add/100/110/111
 http://localhost:8080/users/delete/100
 http://localhost:8080/users/search/name/2
 http://localhost:8080/users/100
 */

配置datasource

spring.jpa.show-sql = true
logging.level.org.springframework.data=DEBUG
spring.jpa.hibernate.ddl-auto=

spring.datasource.url=jdbc:mysql://localhost:3306/tdf_db?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

示例代碼地址

https://github.com/ChenQianPing/spring-data-jpa-example

整合Thymeleaf模板引擎

Thymeleaf

簡介

Thymeleaf是一個跟Velocity、FreeMarker類似的模板引擎,它可以完全替代JSP,相較與其他的模板引擎,它有如下三個極吸引人的特點:

  • Thymeleaf在有網(wǎng)絡(luò)和無網(wǎng)絡(luò)的環(huán)境下皆可運行,即它可以讓美工在瀏覽器查看頁面的靜態(tài)效果,也可以讓程序員在服務(wù)器查看帶數(shù)據(jù)的動態(tài)頁面效果。這是由于它支持 html 原型,然后在 html 標(biāo)簽里增加額外的屬性來達到模板+數(shù)據(jù)的展示方式。瀏覽器解釋 html 時會忽略未定義的標(biāo)簽屬性,所以thymeleaf的模板可以靜態(tài)地運行;當(dāng)有數(shù)據(jù)返回到頁面時,Thymeleaf 標(biāo)簽會動態(tài)地替換掉靜態(tài)內(nèi)容,使頁面動態(tài)顯示。
  • Thymeleaf開箱即用的特性。它提供標(biāo)準(zhǔn)和spring標(biāo)準(zhǔn)兩種方言,可以直接套用模板實現(xiàn)JSTL、OGNL表達式效果,避免每天套模板、改jstl、改標(biāo)簽的困擾。同時開發(fā)人員也可以擴展和創(chuàng)建自定義的方言。
  • Thymeleaf提供spring標(biāo)準(zhǔn)方言和一個與SpringMVC完美集成的可選模塊,可以快速的實現(xiàn)表單綁定、屬性編輯器、國際化等功能。

編輯pom文件,引入Thymeleaf

<!--整合Thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Thymeleaf配置

# thymeleaf
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.check-template-location=true
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.cache=false

新建模板文件

在resources文件夾下新增templates目錄,用于存放模板文件,新增hello.html。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>springboot-thymeleaf demo</title>
</head>
<body>
<p th:text="'hello, ' + ${name} + '!'" />
</body>
</html>

編輯業(yè)務(wù)代碼HelloController

package com.pingkeke.rdf.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletRequest;

/**
 * HelloController.
 */
@Controller
public class HelloController {

    @RequestMapping("/hello")
    public String hello(HttpServletRequest request, @RequestParam(value = "name", required = false, defaultValue = "springboot-thymeleaf") String name) {
        request.setAttribute("name", name);

        return "hello";
    }
}

編輯啟動類

package com.pingkeke.rdf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching

/**
 * RdfApplication
 */
public class RdfApplication extends SpringBootServletInitializer {

    /**
     * 這個類的作用與在web.xml中配置負(fù)責(zé)初始化Spring應(yīng)用上下文的監(jiān)聽器作用類似,
     * 只不過在這里不需要編寫額外的XML文件了。
     * @param application
     * @return
     */
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(RdfApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(RdfApplication.class, args);
    }
}

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內(nèi)容