目前,业界常用的spring boot整合多数据源的方案主要有两个,一个是Mapper分包方式,另一个是AOP切片方式。最近发现一个更为好使的方案:负责mybatis-plus的baomidou团队开源了一款动态使用多源数据库的解决方案,只需要引入dynamic-datasource的jar包,在service层或mapper层的类或方法前使用@DS注解,即可完成选择指定数据源。
一、在SpringBoot项目pom文件中引入依赖
在SpringBoot项目pom文件中,引入如下依赖:
<!-- 常用collection操作 依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
<!-- mybatis-plus 启动依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!-- datasource多数据源 启动依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<!-- mysql connector 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
二、application.properties属性文件增加数据源配置项
# 多数据源配置
#设置默认的数据源或者数据源组, 默认值即为 master
spring.datasource.dynamic.primary=master
#设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时会抛出异常,不启动会使用默认数据源.
spring.datasource.dynamic.strict=false
## 默认数据源 master
spring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/mysql-plus?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
spring.datasource.dynamic.datasource.master.username=test_user
spring.datasource.dynamic.datasource.master.password=test_pswd
spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.jdbc.Driver
## 数据源 slave
spring.datasource.dynamic.datasource.slave.url=jdbc:mysql://localhost:3306/mysql-plus-slave?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
spring.datasource.dynamic.datasource.slave.username=test_user
spring.datasource.dynamic.datasource.slave.password=test_pswd
spring.datasource.dynamic.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
说明:
1.spring.datasource.dynamic.primary=master 表示设置master数据源为primary数据源。
2.spring.datasource.dynamic.strict=false 设置严格模式,默认false不启动.。若设置为true,则在未匹配到指定数据源时会抛出异常。若设置为false,则在未匹配到指定数据源时会使用默认数据源。
3.测试使用的user表sql为:
DROP TABLE IF EXISTS`user`;
CREATE TABLE `user` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT "自增id",
`user_name` varchar(255) DEFAULT '' COMMENT "用户名称",
`age` INTEGER(10) DEFAULT 0 COMMENT "用户年龄",
`sex` INTEGER(10) DEFAULT 0 COMMENT "性别 0女 1男",
`school` varchar(255) DEFAULT '' COMMENT "用户学校",
`type` INTEGER(10) DEFAULT 0 COMMENT "类型 0正常 1马甲",
`created_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`updated_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `school` (`school`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT="用户记录表";
## default db
INSERT INTO `user`(`id`, `user_name`, `age`, `sex`, `school`, `type`) VALUES (0, '王二', 25, 0, '清华',0);
INSERT INTO `user`(`id`, `user_name`, `age`, `sex`, `school`, `type`) VALUES (0, '张三', 25,1, '清华',1);
INSERT INTO `user`(`id`, `user_name`, `age`, `sex`, `school`, `type`) VALUES (0, '李四', 25,1, '清华',1);
## custom db
INSERT INTO `user`(`id`, `user_name`, `age`, `sex`, `school`, `type`) VALUES (0, ''mvp'', 25, 0, '北大',0);
INSERT INTO `user`(`id`, `user_name`, `age`, `sex`, `school`, `type`) VALUES (0, ''jack'', 25,1, '北大',1);
INSERT INTO `user`(`id`, `user_name`, `age`, `sex`, `school`, `type`) VALUES (0, ''lily'', 25,1, '北大',1);
三、@DS 注解
-
作用: 用于切换数据源。
-
修饰范围: 在
方法上或类上。
当同时存在于方法和类上时,方法注解优先(局部优先)。 - 属性:
value
值为切换数据源名称。
如果在类或方法上没有使用@DS注解,那么会使用默认primary首选库。
四、Mapper层代码开发示例
mapper层代码示例为:
@Mapper
@Repository
public interface UserDao extends BaseMapper<UserPo> {
@Select("select * from `user` where sex = #{sex, jdbcType=INTEGER} limit 10")
List<UserPo> getByTypeDefaultDB(@Param("sex") Integer sex);
@Select("select * from `user` where sex = #{sex, jdbcType=INTEGER} limit 10")
@DS("slave")
List<UserPo> getByTypeCustomDB(@Param("sex") Integer sex);
}
五、Service层代码开发示例
service层代码开发示例如下所示。
service接口示例为:
public interface UserService extends IService<UserPo> {
List<UserPo> selectFromDefaultDB();
List<UserPo> selectFromCustomDB();
public List<UserPo> queryFromDefaultDB();
public List<UserPo> queryFromCustomDB();
}
service层接口的具体实现类的示例为:
@Service
public class UserServiceImpl extends ServiceImpl<UserDao, UserPo> implements UserService {
@Resource
private UserDao userDao;
@Override
public List<UserPo> selectFromDefaultDB() {
QueryWrapper<UserPo> wrapper = Wrappers.query();
wrapper.eq("sex", 1);
wrapper.last("limit 10");
return userDao.selectList(wrapper);
}
@Override
@DS("slave")
public List<UserPo> selectFromCustomDB() {
QueryWrapper<UserPo> wrapper = Wrappers.query();
wrapper.eq("sex", 1);
wrapper.last("limit 10");
return userDao.selectList(wrapper);
}
@Override
public List<UserPo> queryFromDefaultDB() {
QueryWrapper<UserPo> wrapper = Wrappers.query();
wrapper.eq("sex", 1);
wrapper.last("limit 10");
return this.list(wrapper);
}
@Override
@DS("slave")
public List<UserPo> queryFromCustomDB() {
QueryWrapper<UserPo> wrapper = Wrappers.query();
wrapper.eq("sex", 1);
wrapper.last("limit 10");
return this.list(wrapper);
}
}
注意:@DS
注解用在service实现类或其方法中,其value值为配置文件中spring.datasource.dynamic.datasource.xxx
后的xxx
值。
六、测试代码示例
测试代码示例,如下所示:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Resource
private UserService userService;
@Test
public void selectSomeTest() {
System.out.println("From default DB:");
List<UserPo> userPos = userService.selectFromDefaultDB();
if (CollectionUtils.isNotEmpty(userPos)) {
userPos.forEach(user -> System.out.println("user: " + user));
} else {
System.out.println("userPos is empty");
}
System.out.println("From slave DB:");
userPos = userService.selectFromCustomDB();
if (CollectionUtils.isNotEmpty(userPos)) {
userPos.forEach(user -> System.out.println("user: " + user));
} else {
System.out.println("userPos is empty");
}
}
@Test
public void queryFromDBTest() {
List<UserPo> userPos = userService.queryFromDefaultDB();
System.out.println("From default DB:");
if (CollectionUtils.isNotEmpty(userPos)) {
userPos.forEach(user -> System.out.println("user: " + user));
} else {
System.out.println("userPos is empty");
}
System.out.println("From slave DB:");
userPos = userService.queryFromCustomDB();
if (CollectionUtils.isNotEmpty(userPos)) {
userPos.forEach(user -> System.out.println("user: " + user));
} else {
System.out.println("userPos is empty");
}
}
}
经验证,测试用例符合预期。