Spring Data JPA的多數(shù)據(jù)源及分布式事務(wù)實現(xiàn)

一.Spring data JPA的多數(shù)據(jù)源實現(xiàn)

  1. 將數(shù)據(jù)源對象作為參數(shù)贴彼,傳遞到調(diào)用方法內(nèi)部,這種方式增加額外的編碼。
  2. 將Repository操作接口分包存放,Spring掃描不同的包吐葵,自動注入不同的數(shù)據(jù)源。這種方式實現(xiàn)簡單桥氏,也是一種“約定大于配置”思想的典型應(yīng)用温峭。
  3. 使用Spring AOP面向切面編程,然后在持久層接口方法上面加注解字支,不同的注解使用表示使用不同的數(shù)據(jù)源凤藏。

本文將以第二種方式實現(xiàn)JPA的多數(shù)據(jù)源支持。

1.1 修改application.yml配置多數(shù)據(jù)源

配置2個數(shù)據(jù)源堕伪,primary數(shù)據(jù)源:testdb數(shù)據(jù)庫揖庄,secondary數(shù)據(jù)源:testdb2數(shù)據(jù)庫。

spring:
  datasource:
    primary:
      jdbc-url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      jdbc-url: jdbc:mysql://localhost:3306/testdb2?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: update
    database: mysql
    show-sql: true

testdb數(shù)據(jù)庫中存在article表欠雌,testdb2中存在comment表

1.2 testdb數(shù)據(jù)庫的數(shù)據(jù)持久層配置

  1. 創(chuàng)建cn.lsp.springboot.model.testdb包蹄梢,將Article實體放入此包中;
  2. 創(chuàng)建cn.lsp.springboot.repository.testdb包桨昙,將ArticleRepository放入此包中检号;
  3. testdb數(shù)據(jù)庫的JPA數(shù)據(jù)持久層配置,需要配置:
  • DataSource數(shù)據(jù)源
  • EntityManager 實體管理器
  • EntityManagerFactoryBean 實體管理器工廠
  • PlatformTransactionManager 事務(wù)管理器
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import java.util.Map;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactoryPrimary",
        transactionManagerRef="transactionManagerPrimary",
        basePackages= {"cn.lsp.springboot.repository.testdb"})
public class JPAPrimaryConfig2 {

    @Resource
    private JpaProperties jpaProperties;

    @Resource
    private HibernateProperties hibernateProperties;

    @Primary
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix="spring.datasource.primary")  //使用application.yml的primary數(shù)據(jù)源配置
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "entityManagerPrimary")        //primary實體管理器
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
         return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
    }

    @Primary
    @Bean(name = "entityManagerFactoryPrimary")    //primary實體工廠
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {

        Map<String,Object> properties =
                hibernateProperties.determineHibernateProperties(
                        jpaProperties.getProperties(),
                        new HibernateSettings());
        return builder.dataSource(primaryDataSource())
                .properties(properties)
                .packages("cn.lsp.springboot.model.testdb")
                .persistenceUnit("primaryPersistenceUnit")
                .build();
    }

    @Primary
    @Bean(name = "transactionManagerPrimary")         //primary事務(wù)管理器
    public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
    }
}

1.3 testdb2數(shù)據(jù)庫的數(shù)據(jù)持久層配置

  1. 創(chuàng)建cn.lsp.springboot.model.testdb2包,將Comment實體放入此包中蛙酪;
  2. 創(chuàng)建cn.lsp.springboot.repository.testdb2包齐苛,將CommentRepository放入此包中;
  3. testdb2數(shù)據(jù)庫的JPA數(shù)據(jù)持久層配置桂塞,因為這一組配置不是默認配置凹蜂,該組數(shù)據(jù)源不是默認數(shù)據(jù)源,沒有@Primary注解阁危。
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import java.util.Map;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="entityManagerFactorySecondary",
        transactionManagerRef="transactionManagerSecondary",
        basePackages= { "cn.lsp.springboot.repository.testdb2" })
public class JPASecondaryConfig {

    @Resource
    private JpaProperties jpaProperties;

    @Resource
    private HibernateProperties hibernateProperties;

    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix="spring.datasource.secondary")   //使用application.yml的secondary數(shù)據(jù)源配置
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "entityManagerSecondary")      //secondary實體管理器
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return entityManagerFactorySecondary(builder).getObject().createEntityManager();
    }


    @Bean(name = "entityManagerFactorySecondary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {

        Map<String,Object> properties =
                hibernateProperties.determineHibernateProperties(
                        jpaProperties.getProperties(),
                        new HibernateSettings());
        return builder
                .dataSource(secondaryDataSource())
                .properties(properties)
                .packages("cn.lsp.springboot.model.testdb2")
                .persistenceUnit("secondaryPersistenceUnit")
                .build();
    }

    @Bean(name = "transactionManagerSecondary")
    PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
    }
}

1.4 多數(shù)據(jù)源測試

@Slf4j
@SpringBootTest
public class JpaJtaTest {

    @Resource
    private ArticleRepository articleRepository;

    @Resource
    private CommentRepository commentRepository;

    @Test
    public void testJpaJta(){
        Article article = new Article();
        article.setTitle("SpringBoot實戰(zhàn)");
        article.setContent("詳細介紹SpringBoot的各種姿勢");
        article.setAuthor("William");
        article.setCreateTime(new Date());
        Article savedArticle = articleRepository.save(article);

        Comment comment = new Comment();
        comment.setName("Tom");
        comment.setContent("內(nèi)容詳實玛痊,偏實戰(zhàn)");
        comment.setCreateTime(new Date());
        comment.setArticleId(savedArticle.getId());
        commentRepository.save(comment);
    }
}

二.JPA+atomikos實現(xiàn)分布式事務(wù)

2.1整合JTA(atomikos)

通過maven坐標(biāo)引入JTA atomikos。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

在第一節(jié)的配置基礎(chǔ)上狂打,把jdbc-url改成url擂煞,然后在spring前綴下面加:

spring:
  jta:
    atomikos:
      datasource:
        max-pool-size: 20
        borrow-connection-timeout: 60
      connectionfactory:
        max-pool-size: 20
        borrow-connection-timeout: 60
  • max-pool-size表示數(shù)據(jù)連接池最大連接數(shù),請根據(jù)自己的應(yīng)用規(guī)模進行調(diào)整
  • borrow-connection-timeout表示連接從連接池“借出”之后的超時時間趴乡,超時將拋出異常

2.2分布式事務(wù)管理器

刪掉第一節(jié)的數(shù)據(jù)源配置及事務(wù)配置代碼对省,加入如下代碼,AtomikosJtaPlatform為固定代碼不需修改晾捏;

public class AtomikosJtaPlatform extends AbstractJtaPlatform {

    private static final long serialVersionUID = 1L;

    static TransactionManager transactionManager;
    static UserTransaction transaction;

    @Override
    protected TransactionManager locateTransactionManager() {
        return transactionManager;
    }

    @Override
    protected UserTransaction locateUserTransaction() {
        return transaction;
    }
}

事務(wù)管理器的配置JPAAtomikosTransactionConfig 蒿涎,以下除了設(shè)置JPA特性的部分,為固定代碼不需修改;

@Configuration
@ComponentScan
@EnableTransactionManagement
public class JPAAtomikosTransactionConfig {

    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    //設(shè)置JPA特性
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
        //顯示sql
        hibernateJpaVendorAdapter.setShowSql(true);
        //自動生成/更新表
        hibernateJpaVendorAdapter.setGenerateDdl(true);
        //設(shè)置數(shù)據(jù)庫類型
        hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);
        return hibernateJpaVendorAdapter;
    }

    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() throws Throwable {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(false);
        AtomikosJtaPlatform.transactionManager = userTransactionManager;
        return userTransactionManager;
    }

    @Bean(name = "transactionManager")
    @DependsOn({"userTransaction", "atomikosTransactionManager"})
    public PlatformTransactionManager transactionManager() throws Throwable {
        UserTransaction userTransaction = userTransaction();
        AtomikosJtaPlatform.transaction = userTransaction;
        TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
    }

}

2.3數(shù)據(jù)源及實體管理配置(多數(shù)據(jù)源多份)

設(shè)置第一個數(shù)據(jù)庫的primary數(shù)據(jù)源及實體掃描管理(掃描testdb目錄)惦辛,實體管理器劳秋、數(shù)據(jù)源都要加上primary,以示區(qū)分:

@Configuration
@DependsOn("transactionManager")
@EnableJpaRepositories(basePackages = "cn.lsp.springboot.repository.testdb",  //注意這里
        entityManagerFactoryRef = "primaryEntityManager",
        transactionManagerRef = "transactionManager")
public class JPAPrimaryConfig {
    @Autowired
    private JpaVendorAdapter jpaVendorAdapter;

    //primary
    @Primary
    @Bean(name = "primaryDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.primary")     //注意這里
    public DataSourceProperties primaryDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Primary
    @Bean(name = "primaryDataSource", initMethod = "init", destroyMethod = "close")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(primaryDataSourceProperties().getUrl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(primaryDataSourceProperties().getPassword());
        mysqlXaDataSource.setUser(primaryDataSourceProperties().getUsername());
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("primary");
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setMaxPoolSize(20);
        return xaDataSource;
    }

    @Primary
    @Bean(name = "primaryEntityManager")
    @DependsOn("transactionManager")
    public LocalContainerEntityManagerFactoryBean primaryEntityManager() throws Throwable {

        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
        properties.put("javax.persistence.transactionType", "JTA");
        LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
        entityManager.setJtaDataSource(primaryDataSource());
        entityManager.setJpaVendorAdapter(jpaVendorAdapter);
        //這里要修改成主數(shù)據(jù)源的掃描包
        entityManager.setPackagesToScan("cn.lsp.springboot.model.testdb");
        entityManager.setPersistenceUnitName("primaryPersistenceUnit");
        entityManager.setJpaPropertyMap(properties);
        return entityManager;
    }
}

設(shè)置第二個數(shù)據(jù)庫的數(shù)據(jù)源及實體掃描管理(掃描testdb2目錄):

@Configuration
@DependsOn("transactionManager")
@EnableJpaRepositories(basePackages = "cn.lsp.springboot.repository.testdb2",   //注意這里
        entityManagerFactoryRef = "secondaryEntityManager",
        transactionManagerRef = "transactionManager")
public class JPASecondaryConfig2 {
    @Autowired
    private JpaVendorAdapter jpaVendorAdapter;


    @Bean(name = "secondaryDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")    //注意這里
    public DataSourceProperties masterDataSourceProperties() {
        return new DataSourceProperties();
    }


    @Bean(name = "secondaryDataSource", initMethod = "init", destroyMethod = "close")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource masterDataSource() throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(masterDataSourceProperties().getUrl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(masterDataSourceProperties().getPassword());
        mysqlXaDataSource.setUser(masterDataSourceProperties().getUsername());
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("secondary");
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setMaxPoolSize(20);
        return xaDataSource;
    }

    @Bean(name = "secondaryEntityManager")
    @DependsOn("transactionManager")
    public LocalContainerEntityManagerFactoryBean masterEntityManager() throws Throwable {

        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
        properties.put("javax.persistence.transactionType", "JTA");
        LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
        entityManager.setJtaDataSource(masterDataSource());
        entityManager.setJpaVendorAdapter(jpaVendorAdapter);
        //這里要修改成主數(shù)據(jù)源的掃描包
        entityManager.setPackagesToScan("cn.lsp.springboot.model.testdb2");
        entityManager.setPersistenceUnitName("secondaryPersistenceUnit");
        entityManager.setJpaPropertyMap(properties);
        return entityManager;
    }
}
  • DataSourceProperties數(shù)據(jù)源配置、Datasource數(shù)據(jù)源玻淑、EntityManager實體管理器都是2套嗽冒。分別是primary和secondary
  • 實體和Repository的掃描目錄也是2組,分別是testdb和testdb2
  • 但是事務(wù)管理器只有一個岁忘,那就是transactionManager辛慰,是基于atomikos實現(xiàn)的。事務(wù)管理器只有一個干像,決定了不同的數(shù)據(jù)源使用同一個事務(wù)管理器帅腌,從而實現(xiàn)分布式事務(wù)。

2.4 JPA分布式事務(wù)測試

service代碼:

@Slf4j
@Service
public class ArticleServiceImpl implements ArticleService {

    @Resource
    private ArticleRepository articleRepository;

    @Resource
    private CommentRepository commentRepository;

    @Override
    @Transactional
    public Article saveArticleAndComment(Article article, Comment comment) {
        Article savedArticle = articleRepository.save(article);
        comment.setArticleId(savedArticle.getId());
        commentRepository.save(comment);
//        int a= 2/0;
        return  article;
    }
}

test代碼:

@Slf4j
@SpringBootTest
public class JpaJtaTest {

    @Resource
    private ArticleService articleService;

    @Test
    public void saveArticle() {
        Article article = new Article();
        article.setTitle("SpringBoot實戰(zhàn)");
        article.setContent("詳細介紹SpringBoot的各種姿勢");
        article.setAuthor("William");
        article.setCreateTime(new Date());

        Comment comment = new Comment();
        comment.setName("Tom");
        comment.setContent("內(nèi)容詳實麻汰,偏實戰(zhàn)");
        comment.setCreateTime(new Date());

        articleService.saveArticleAndComment(article, comment);
    }
}

service里面分別向testdb插入article速客,testdb2插入comment,數(shù)據(jù)插入都成功五鲫。人為制造一個被除數(shù)為0的異常溺职,數(shù)據(jù)插入都失敗。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末位喂,一起剝皮案震驚了整個濱河市浪耘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌塑崖,老刑警劉巖七冲,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異规婆,居然都是意外死亡澜躺,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門抒蚜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掘鄙,“玉大人,你說我怎么就攤上這事嗡髓〔倌” “怎么了?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵饿这,是天一觀的道長浊伙。 經(jīng)常有香客問我,道長蛹稍,這世上最難降的妖魔是什么吧黄? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任部服,我火速辦了婚禮唆姐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘廓八。我一直安慰自己奉芦,他們只是感情好赵抢,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著声功,像睡著了一般烦却。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上先巴,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天其爵,我揣著相機與錄音,去河邊找鬼伸蚯。 笑死摩渺,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的剂邮。 我是一名探鬼主播摇幻,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挥萌!你這毒婦竟也來了绰姻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤引瀑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后伤疙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體银酗,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年徒像,在試婚紗的時候發(fā)現(xiàn)自己被綠了黍特。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡锯蛀,死狀恐怖灭衷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情旁涤,我是刑警寧澤翔曲,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站劈愚,受9級特大地震影響瞳遍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜菌羽,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一掠械、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦猾蒂、人聲如沸均唉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舔箭。三九已至,卻和暖如春蚊逢,著一層夾襖步出監(jiān)牢的瞬間层扶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工烙荷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留怒医,地道東北人。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓奢讨,卻偏偏與公主長得像稚叹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拿诸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

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