为什么需要多数据源

同一个项目有时会涉及到多个数据库,这时我们就要配置多个数据源。配置多数据源的常见情况有以下两种:

1)同一个项目中涉及两个或多个业务数据库,它们之间相互独立,这种情况也可以作为两个或多个项目来开发

2)两个或多个数据库之间是主从关系,主库负责写,从库负责读

多数据源的配置

1、pom.xml配置

在pom.xml中增加MyBatis-Plus多数据源依赖:

<!-- MyBatis-Plus多数据源依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.2.1</version>
</dependency>

2、配置文件配置

在配置文件application.yml中配置我们需要连接的数据库:blog和user,默认为blog

spring:
  datasource:
    dynamic:
      # 设置默认数据源
      primary: blog
      # 设置严格模式,false为不启动,启动后在未匹配到指定数据源时会抛出异常,不启动则使用默认数据源
      strict: false
      datasource:
        blog:
          url: jdbc:mysql://localhost:3306/blog
          username: root
          password: 123456
          type: com.alibaba.druid.pool.DruidDataSource
          druid:
            initialSize: 5
            minIdle: 5
            maxActive: 20
            maxWait: 2000
            validationQuery: select 'x'
            testOnBorrow: false
            testOnReturn: false
            testWhileIdle: true
        user:
          url: jdbc:mysql://localhost:3306/user
          username: root
          password: 123456
          type: com.alibaba.druid.pool.DruidDataSource
          druid:
            initialSize: 5
            minIdle: 5
            maxActive: 20
            maxWait: 2000
            validationQuery: select 'x'
            testOnBorrow: false
            testOnReturn: false
            testWhileIdle: true
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3、启动类配置

在@SpringBootApplication注解上增加exclude = DruidDataSourceAutoConfigure.class配置:

@SpringBootApplication(scanBasePackages = "com.tn222.springboot.article12.**", 
        exclude = DruidDataSourceAutoConfigure.class) 
@MapperScan(basePackages = "com.tn222.springboot.article12.dao") 
public class Article12Application { 

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

}

这个配置的作用是去掉对DruidDataSourceAutoConfigure的自动配置,否则程序会报错:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured. 
Reason: Failed to determine a suitable driver class

原因:

DruidDataSourceAutoConfigure在DynamicDataSourceAutoConfiguration之前,其会注入一个DataSourceWrapper,会在原生的spring.datasource下找url, username, password等,而我们动态数据源的配置路径是变化的。

4、实体类和dao层配置

在po文件夹下创建blog和user文件夹,分别用于存储blog数据库和user数据库的实体:

@TableName(value = "article") 
public class ArticlePo { 

    @TableId(type = IdType.AUTO) 
    private Integer id; 
    private String articleId; 
    private String title; 
 
    // get、set... 
} 

@TableName(value = "user_info") 
public class UserInfoPo { 

    @TableId(type = IdType.AUTO) 
    private Integer id; 
    private String userId; 
    private String name; 

    // get、set... 
}

注解:

@TableName:表名注解,标识实体类对应的表

@TableId:主键注解,当type = IdType.AUTO时,表示这个主键是自增主键

在dao文件夹下创建blog和user文件夹,分别用于存储blog和user的dao:

@Repository 
public interface ArticleDao extends BaseMapper<ArticlePo> { 

} 

@Repository 
@DS("user") 
public interface UserInfoDao extends BaseMapper<UserInfoPo> { 

}

注解:

@Repository:将数据访问层(DAO层)的类标识为Spring Bean

@DS:配置非默认数据源,本示例中blog为默认数据源,user为非默认数据源,在使用@DS注解时,有如下注意事项:

1)不能使用事务,否则数据源不会切换,使用的还是第一次加载的数据源

2)第一次加载数据源之后,第二次,第三次……操作其他数据源,如果数据源不存在,使用的还是第一次加载的数据源

3)数据源名称不要包含下划线,否则不能切换

5、测试验证

编写ArticleController和UserInfoController:

@RestController 
@RequestMapping("/article") 
public class ArticleController { 

    @Resource 
    private ArticleDao articleDao; 

    @GetMapping(value = "/get") 
    public ArticlePo get(@RequestParam("articleId") String articleId) { 
        ArticlePo articlePo = articleDao.selectOne(Wrappers.<ArticlePo>lambdaQuery() 
                .eq(ArticlePo::getArticleId, articleId)); 
        return articlePo; 
    } 

    @PostMapping(value = "/insert") 
    public Boolean insert(@RequestBody ArticlePo articlePo) { 
        articleDao.insert(articlePo); 
        Boolean res = false; 
        if (articlePo.getId() > 0) { 
            res = true; 
        } 
        return res; 
    } 

} 

@RestController 
@RequestMapping("/user") 
public class UserInfoController { 

    @Resource 
    private UserInfoDao userInfoDao; 

    @GetMapping(value = "/get") 
    public UserInfoPo get(@RequestParam("userId") String userId) { 
        UserInfoPo res = userInfoDao.selectOne(Wrappers.<UserInfoPo>lambdaQuery().eq(UserInfoPo::getUserId, userId)); 
        return res; 
    } 

    @PostMapping(value = "/insert") 
    public Boolean insert(@RequestBody UserInfoPo userInfoPo) { 
        userInfoDao.insert(userInfoPo); 
        Boolean res = false; 
        if (userInfoPo.getId() > 0) { 
            res = true; 
        } 
        return res; 
    } 

}

注⚠️:业务逻辑复杂时,Controller和Mapper中间会有Service层来处理业务逻辑,现在我们就简单的测试一下多数据源,所以直接使用Controller调用Mapper了

MyBatis-Plus的分页

1、配置分页插件

@Configuration 
@MapperScan("com.tn222.springboot.article12.dao") 
public class MybatisPlusConfig { 
 
    /** 
     * 分页插件 
     */ 
    @Bean 
    public PaginationInterceptor paginationInterceptor(){ 
        return new PaginationInterceptor(); 
    } 

}

2、分页方法

1)使用MyBatis-Plus的selectPage方法

@GetMapping("/listByLambda")
public Object listByLambda(@RequestParam("title") String title, @RequestParam("pageIndex") Integer pageIndex, 
                           @RequestParam("pageSize") Integer pageSize) { 
    IPage<ArticlePo> thisPage = new Page<>(pageIndex, pageSize); 
    LambdaQueryWrapper<ArticlePo> eq = Wrappers.<ArticlePo>lambdaQuery() 
            .eq(ArticlePo::getTitle, title).orderByDesc(ArticlePo::getId); 
    thisPage = articleDao.selectPage(thisPage, eq); 
    return thisPage; 
}

使用MyBatis-Plus的selectPage方法,返回了IPage<ArticlePo>,示例:

{ 
    "records": [ 
        { 
            "id": 7, 
            "articleId": "f68c5ccf9e3b4a719b369f9a84316635", 
            "title": "Mybatis-Plus update" 
        }, 
        { 
            "id": 2, 
            "articleId": "1eabc4f8ce0711ec9b05979114b13a0f", 
            "title": "Mybatis-Plus update" 
        } 
    ], 
    "total": 3, 
    "size": 2, 
    "current": 1, 
    "orders": [], 
    "searchCount": true, 
    "pages": 2 
}

2)sql分页

有时候有些分页需要关联多张表,使用LambdaQueryWrapper不太方便,这时候可以自己写sql来实现分页,主要有两种:纯sql自己实现分页和使用IPage实现分页

注⚠️:这里的sql示例就使用单表查询了,具体的可根据业务场景使用多表查询

A、纯sql自己实现分页

分页的数据list和总条数单独调用方法返回 :

@Select("select * from article where title=#{title} order by id desc limit #{offset}, #{pageSize}") 
List<ArticlePo> listByTitle(@Param("title") String title, @Param("offset") Integer offset, 
                            @Param("pageSize") Integer pageSize); 

@Select("select count(id) from article where title=#{title}") 
int countByTitle(@Param("title") String title);

B、使用IPage实现分页(常用)

@Select("select * from article where title=#{title} order by id desc") 
IPage<ArticlePo> listByPage(IPage thisPage, @Param("title") String title);

返回IPage<ArticlePo>,返回值的数据结构见“1)使用MyBatis-Plus的selectPage方法

本文简单介绍了一下MyBatis-Plus的多数据源和分页,本文示例代码