SpringBoot中配置mybatis多数据源

多数据源配置

背景

在日常业务开发中,偶尔还是会遇到多数据源配置需求的项目,比如在业务系统迁移时,需要将SQLServer数据库中的数据导入MySQL数据库中。再比如在我经过的项目中,有静态化网站数据库和搜索引擎数据库两个数据源。

如何配置

在数据访问中包含数据源、事务管理器、会话管理工厂、会话模板、mybatis配置文件、mybatis数据库脚本映射文件(SQL Map)。其他的常用配置,比如驼峰命名映射配置(mybatis.configuration.map-underscore-to-camel-case)、类型别名配置(mybatis.type-aliases-package)、类型处理器配置(mybatis.type-handlers-package)等,不管是单数据源场景还是多数据源场景都建议统一在mybatis.xml配置文件中进行集中配置管理。

  1. 在基类中配置数据源
@Slf4j
public class BaseDataSourceConfig {

    @Value("${spring.datasource.driverClassName}")
    protected String driverClass;

    @Value("${spring.datasource.druid.filters}")
    protected String filters;

    @Value("${spring.datasource.druid.initialSize}")
    protected Integer initialSize;

    @Value("${spring.datasource.druid.minIdle}")
    protected Integer minIdle;

    @Value("${spring.datasource.druid.maxActive}")
    protected Integer maxActive;

    @Value("${spring.datasource.druid.maxWait}")
    protected Integer maxWait;

    @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
    protected Integer timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
    protected Integer minEvictableIdleTimeMillis;

    @Value("${spring.datasource.druid.validationQuery}")
    protected String validationQuery;

    @Value("${spring.datasource.druid.testWhileIdle}")
    protected Boolean testWhileIdle;

    @Value("${spring.datasource.druid.testOnBorrow}")
    protected Boolean testOnBorrow;

    @Value("${spring.datasource.druid.testOnReturn}")
    protected Boolean testOnReturn;

    @Value("${spring.datasource.druid.poolPreparedStatements}")
    protected Boolean poolPreparedStatements;

    @Value("${spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize}")
    protected Integer maxPoolPreparedStatementPerConnectionSize;

    @Value("${spring.datasource.druid.connectionProperties}")
    protected Properties connectionProperties;

    /**
     * 初始化
     *
     * @return DataSource
     */
    protected DruidDataSource initDataSource(String url, String userName, String password) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(userName);
        dataSource.setPassword(password);
        dataSource.setMaxActive(maxActive);
        dataSource.setMinIdle(minIdle);
        dataSource.setInitialSize(initialSize);
        dataSource.setMaxWait(maxWait);
        dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        dataSource.setTestWhileIdle(testWhileIdle);
        dataSource.setTestOnBorrow(testOnBorrow);
        dataSource.setTestOnReturn(testOnReturn);
        dataSource.setValidationQuery(validationQuery);
        dataSource.setPoolPreparedStatements(poolPreparedStatements);
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        dataSource.setConnectProperties(connectionProperties);
        try {
            dataSource.setFilters(filters);
        } catch (SQLException e) {
            log.error("BaseDataSourceConfig error", e);
        }
        return dataSource;
    }

    /**
     * 初始化SessionFactory
     *
     * @param dataSource          数据源
     * @param configLocation      配置文件地址
     * @param mapperLocations     mapper地址
     * @return SqlSessionFactory
     * @throws Exception 异常
     */
    public SqlSessionFactory initSessionFactory(DataSource dataSource, String configLocation, String mapperLocations)
            throws Exception {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setMapperLocations(resolver.getResources(mapperLocations));
        sessionFactory.setVfs(SpringBootVFS.class);
        sessionFactory.setConfigLocation(resolver.getResource(configLocation));
        return sessionFactory.getObject();
    }
  1. 扩展基类,创建第一个数据源
@Configuration
@MapperScan(basePackages = OwDataSourceConfig.PACKAGE, sqlSessionFactoryRef = "owSqlSessionFactory")
public class OwDataSourceConfig extends BaseDataSourceConfig {
    static final String PACKAGE = "com.xxx.ow.dao";

    @Value("${ow.mybatis.mapper-locations}")
    private String mapperLocations;

    @Value("${ow.mybatis.config-location}")
    private String configLocation;

    @Value("${ow.datasource.url}")
    private String url;

    @Value("${ow.datasource.username}")
    private String userName;

    @Value("${ow.datasource.password}")
    private String password;

    @Bean(name = "owDataSource")
    @Primary
    public DataSource owDataSource() {
        return initDataSource(url, userName, password);
    }

    @Bean(name = "owTransactionManager")
    @Primary
    public DataSourceTransactionManager masterTransactionManager() {
        return new DataSourceTransactionManager(owDataSource());
    }

    @Bean(name = "owSqlSessionFactory")
    @Primary
    public SqlSessionFactory owSqlSessionFactory(@Qualifier("owDataSource") DataSource dataSource)
            throws Exception {
        return initSessionFactory(dataSource, configLocation, mapperLocations);
    }
    @Primary
    @Bean(name = "owSqlSessionTemplate")
    public SqlSessionTemplate owSqlSessionTemplate(
            @Qualifier("owSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
  1. 扩展基类,创建第二个数据源
@Configuration
@MapperScan(basePackages = SearchDataSourceConfig.PACKAGE, sqlSessionFactoryRef = "searchSqlSessionFactory")
public class SearchDataSourceConfig extends BaseDataSourceConfig {
   static final String PACKAGE = "com.xxx.ow.search.dao";
   @Value("${search.mybatis.mapper-locations}")
   private String mapperLocations;

   @Value("${search.mybatis.config-location}")
   private String configLocation;

   @Value("${search.datasource.url}")
   private String url;

   @Value("${search.datasource.username}")
   private String userName;

   @Value("${search.datasource.password}")
   private String password;

   @Bean(name = "searchDataSource")
   public DataSource searchDataSource() {
       return initDataSource(url, userName, password);
   }

   @Bean(name = "searchTransactionManager")
   public DataSourceTransactionManager masterTransactionManager() {
       return new DataSourceTransactionManager(searchDataSource());
   }

   @Bean(name = "searchSqlSessionFactory")
   public SqlSessionFactory masterSqlSessionFactory(@Qualifier("searchDataSource") DataSource dataSource)
           throws Exception {
       return initSessionFactory(dataSource, configLocation, mapperLocations, typeAliasesPackage, typeHandlersPackage);
   }

   @Bean(name = "searchSqlSessionTemplate")
   public SqlSessionTemplate searchSqlSessionTemplate(
           @Qualifier("searchSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
       return new SqlSessionTemplate(sqlSessionFactory);
   }
}
  1. 在application.properties中增加配置
    配置每一个数据源的config-location和mapper-locations。
ow.mybatis.config-location=mybatis.xml
ow.mybatis.mapper-locations=classpath*:/com/xxx/ow/mapper/**/*.xml

search.mybatis.config-location=mybatis-search.xml
search.mybatis.mapper-locations=classpath*:/com/xxx/ow/search/mapper/**/*.xml

在mybatis.xml配置文件中配置更多选项,比如驼峰命名映射配置(mybatis.configuration.map-underscore-to-camel-case)、类型别名配置(mybatis.type-aliases-package)、类型处理器配置(mybatis.type-handlers-package),如下所示

<!DOCTYPE configuration
    PUBLIC "-//config.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
        <package name="com.xxx.ow.entity"/>
        <package name="com.xxx.ow.enums"/>
        <package name="com.xxx.ow.dto"/>
    </typeAliases>
    <typeHandlers>
        <typeHandler handler="com.xxx.ow.enums.typehandler.UniversalEnumHandler"
                     javaType="com.xxx.ow.enums.ArticleOfProclamationEnum"/>
    </typeHandlers>
</configuration>

第二种方式

还有一种简单的方法是使用SpringBoot的注入数据源配置属性,比如DruidDataSource。比如像下面这样:

@Configuration
@MapperScan(basePackages = {"com.xxx.dataimport.dao.mysql"}, sqlSessionFactoryRef = "mySqlSessionFactory")
public class MySqlConfig {

    @Value("${spring.datasource.mysql.driver-class-name}")
    private String driverClassName;

    @Value("${spring.datasource.mysql.jdbc-url}")
    private String jdbcUrl;

    @Value("${spring.datasource.mysql.username}")
    private String username;

    @Value("${spring.datasource.mysql.password}")
    private String password;

    @Primary
    @Bean(name = "mysqlDataSource")
    public DataSource dataSource() {
        DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.type(com.alibaba.druid.pool.DruidDataSource.class);
        dataSourceBuilder.driverClassName(driverClassName);
        dataSourceBuilder.url(jdbcUrl);
        dataSourceBuilder.username(username);
        dataSourceBuilder.password(password);
        return dataSourceBuilder.build();
    }

    @Primary
    @Bean(name = "mysqlTransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("mysqlDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);

    }

    @Primary
    @Bean(name = "mySqlSessionFactory")
    public SqlSessionFactory basicSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource basicDataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(basicDataSource);
        
        factoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis.xml")[0]);
        PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource[] resources1 = resourcePatternResolver.getResources("classpath:com/xxx/ow/dataimport/mapper/mysql/**/*.xml");
        factoryBean.setMapperLocations(resources1);
        return factoryBean.getObject();
    }

    @Primary
    @Bean(name = "mysqlSqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(
            @Qualifier("mySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
@Configuration
@MapperScan(basePackages = "com.xxx.ow.dataimport.dao.sqlserver", sqlSessionFactoryRef = "sqlServerSessionFactory")
public class SqlServerConfig {

    @Value("${spring.datasource.sqlserver.driver-class-name}")
    private String driverClassName;

    @Value("${spring.datasource.sqlserver.jdbc-url}")
    private String jdbcUrl;

    @Value("${spring.datasource.sqlserver.username}")
    private String username;

    @Value("${spring.datasource.sqlserver.password}")
    private String password;

    @Bean(name = "sqlServerDataSource")
    public DataSource dataSource() {
        DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.type(com.alibaba.druid.pool.DruidDataSource.class);
        dataSourceBuilder.url(jdbcUrl);
        dataSourceBuilder.driverClassName(driverClassName);
        dataSourceBuilder.username(username);
        dataSourceBuilder.password(password);
        return dataSourceBuilder.build();
    }

    @Bean(name = "sqlServerTransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("sqlServerDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);

    }

    @Bean(name = "sqlServerSessionFactory")
    public SqlSessionFactory basicSqlSessionFactory(@Qualifier("sqlServerDataSource") DataSource basicDataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(basicDataSource);
      
        factoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis.xml")[0]);
        factoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath:com/xxx/ow/dataimport/mapper/sqlserver/**/*.xml"));
        return factoryBean.getObject();
    }

    @Bean(name = "sqlServerSqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(
            @Qualifier("sqlServerSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
spring.datasource.mysql.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.mysql.jdbc-url=jdbc:mysql://xxx:3316/xxx_ow?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&autoReconnect=true&failOverReadOnly=false&connectTimeout=10000&serverTimezone=Asia/Shanghai
spring.datasource.mysql.username=app_owner
spring.datasource.mysql.password=

spring.datasource.sqlserver.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.sqlserver.jdbc-url=jdbc:sqlserver://127.0.0.1:1433;databaseName=ringcms_20210702_2
spring.datasource.sqlserver.username=sa
spring.datasource.sqlserver.password=

########### druid data source配置
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.druid.filters=stat
# 初始连接数
spring.datasource.druid.initialSize=5
# 最小空闲连接
spring.datasource.druid.minIdle=5
# 最大连接数
spring.datasource.druid.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.druid.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.druid.minEvictableIdleTimeMillis=300000
spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL
spring.datasource.druid.testWhileIdle=true
spring.datasource.druid.testOnBorrow=false
spring.datasource.druid.testOnReturn=false
# 打开PSCache。如果用Oracle, 则把poolPreparedStatements配置为true, mysql 5.5之后建议true
spring.datasource.druid.poolPreparedStatements=true
# 指定每个连接上PSCache的大小
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

# druid 登录账号
spring.datasource.druid.stat-view-servlet.login-username=superadmin
spring.datasource.druid.stat-view-servlet.login-password=Moshou@2018
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
# IP白名单,多个用,分隔 (没有配置或者为空,则允许所有访问)
#spring.datasource.druid.stat-view-servlet.allow=
# IP黑名单 (存在共同时,deny优先于allow)
#spring.datasource.druid.stat-view-servlet.deny=
# 禁用HTML页面上的“Reset All”功能
spring.datasource.druid.stat-view-servlet.reset-enable=true

spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.map,*.woff,*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
########### druid data source配置

第三种方式,使用@ConfigurationProperties注入数据源配置

  1. 数据库配置
spring:
  datasource:
    test1:
        jdbc-url: jdbc:mysql://localhost:3306/alice_test?serverTimezone=CTT&useUnicode=true&characterEncoding=utf8
        driverClassName: com.mysql.cj.jdbc.Driver
        username: root
        password: xxx
    test2:
        jdbc-url: jdbc:mysql://localhost:3306/alice_test_two?serverTimezone=CTT&useUnicode=true&characterEncoding=utf8
        driverClassName: com.mysql.cj.jdbc.Driver
        username: root
        password: xxx
 
mybatis:
  mapper-locations: classpath:*/mapper/**.xml
  1. 配置类
  • 主数据源
package com.alice.springboot.config;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 
import javax.sql.DataSource;
 
@Configuration
@MapperScan(basePackages = "com.alice.springboot.mapper.test", sqlSessionFactoryRef = "test1SqlSessionFactory")
public class DataSourceConfig1 {
 
    // 将这个对象放入Spring容器中
    @Bean(name = "test1DataSource")
    // 表示这个数据源是默认数据源
    @Primary
    // 读取application.properties中的配置参数映射成为一个对象
    // prefix表示参数的前缀
    @ConfigurationProperties(prefix = "spring.datasource.test1")
    public DataSource getDateSource1()
    {
        return DataSourceBuilder.create().build();
    }
 
    @Bean(name = "test1SqlSessionFactory")
    // 表示这个数据源是默认数据源
    @Primary
    // @Qualifier表示查找Spring容器中名字为test1DataSource的对象
    public SqlSessionFactory test1SqlSessionFactory(@Qualifier("test1DataSource") DataSource datasource)
            throws Exception
    {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(datasource);
        bean.setMapperLocations(
                // 设置mybatis的xml所在位置
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/test/*.xml"));
      bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis.xml"));
        return bean.getObject();
    }
 
    @Bean("test1SqlSessionTemplate")
    // 表示这个数据源是默认数据源
    @Primary
    public SqlSessionTemplate test1SqlSessionTemplate(
            @Qualifier("test1SqlSessionFactory") SqlSessionFactory sessionFactory)
    {
        return new SqlSessionTemplate(sessionFactory);
    }
}
  • 次数据源
package com.alice.springboot.config;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 
import javax.sql.DataSource;
 
@Configuration
@MapperScan(basePackages = "com.alice.springboot.mapper.testTwo", sqlSessionFactoryRef = "test2SqlSessionFactory")
public class DataSourceConfig2 {
    @Bean(name = "test2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.test2")
    public DataSource getDateSource2()
    {
        return DataSourceBuilder.create().build();
    }
 
    @Bean(name = "test2SqlSessionFactory")
    public SqlSessionFactory test2SqlSessionFactory(@Qualifier("test2DataSource") DataSource datasource)
            throws Exception
    {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(datasource);
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/testTwo/*.xml"));
bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-two.xml"));
        return bean.getObject();
    }
 
    @Bean("test2SqlSessionTemplate")
    public SqlSessionTemplate test2SqlSessionTemplate(
            @Qualifier("test2SqlSessionFactory") SqlSessionFactory sessionFactory)
    {
        return new SqlSessionTemplate(sessionFactory);
    }
}

参考资料

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

推荐阅读更多精彩内容