公司项目有连接多个不同数据库的需求,特研究了一下,根据网上的资料,造了一个基于AOP方式的数据源切换轮子,但继续探索,突然发现有开源的多数据源管理启动器。不过,本篇两种方式都会介绍。
dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。
其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x
我们目前只探讨使用dynamic-datasource进行数据源切换,其他请自行搜索
_
分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。spring.datasource.dynamic.primary
修改。
com.baomidou
dynamic-datasource-spring-boot-starter
${version}
spring:
datasource:
dynamic:
primary: mysql #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
mysql:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
pgsql:
url: ENC(xxxxx) # 内置加密
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: org.postgresql.Driver
@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
注解
结果
不使用@DS注解
默认数据源,即primary: mysql
@DS(“dsName”)
dsName可以为组名也可以为具体某个库的名称
@Service
@DS("mysql")
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 不使用@DS注解则代表使用默认数据源
// 如果类上存在,则使用类上标注的数据源
public List selectAll() {
return jdbcTemplate.queryForList("select * from user");
}
@Override
@DS("pgsql")
// 方法上注解 优先于 类上注解,即使类上标注也优先采用方法上的标注
public List selectByCondition() {
return jdbcTemplate.queryForList("select * from user where age >10");
}
}
本次代码参考 https://github.com/mianshenglee/my-example/tree/master/multi-datasource/dynamic-datasource ,因源码不满足的需求,因此我在此基础做了修改。
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.2.RELEASE
me.mason.demo
dynamic-datasource
0.0.1-SNAPSHOT
dynamic-datasource
Demo project for dynamic datasource
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-configuration-processor
com.alibaba
druid-spring-boot-starter
1.1.9
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-aop
mysql
mysql-connector-java
runtime
com.baomidou
mybatis-plus-boot-starter
3.3.0
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.boot
spring-boot-maven-plugin
server.port=8080
server.servlet.context-path=/dd
logging.level.root=INFO
logging.level.me.mason.demo.dynamicdatasource.mapper=DEBUG
# mybatis-plus
mybatis-plus.type-aliases-package=me.mason.demo.dynamicdatasource.entity
# 默认位置,可不配置
#mybatis-plus.mapper-locations=classpath*:/mapper/*.xml
mybatis.mapper-locations=classpath*:/mapper/*.xml
# 使用数据库自增ID
mybatis-plus.global-config.db-config.id-type=auto
# master
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.master.url=jdbc:mysql://10.0.1.243:3306/scheduling?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.master.username=root
spring.datasource.master.password=123456
# slave
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.slave.url=jdbc:mysql://10.0.1.243:3306/scheduling1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.slave.username=root
spring.datasource.slave.password=123456
// 标记注解可使用在方法与类上
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
// 默认值为MASTER
String value() default DataSourceConstants.DS_KEY_MASTER;
}
/**
* 数据源常量
**/
public class DataSourceConstants {
/**
* master数据源
*/
public static final String DS_KEY_MASTER = "master";
/**
* slave数据源
*/
public static final String DS_KEY_SLAVE = "slave";
}
/**
* 动态数据源名称上下文处理
**/
public class DynamicDataSourceContextHolder {
/**
* 动态数据源名称上下文
*/
private static final ThreadLocal DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
/**
* 设置数据源
* @param key
*/
public static void setContextKey(String key){
DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
}
/**
* 获取数据源名称
* @return
*/
public static String getContextKey(){
String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
return key == null?DataSourceConstants.DS_KEY_MASTER:key;
}
/**
* 删除当前数据源名称
*/
public static void removeContextKey(){
DATASOURCE_CONTEXT_KEY_HOLDER.remove();
}
}
/**
* 动态数据源
**/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getContextKey();
}
}
/**
* 动态数据源配置
**/
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
@Configuration
// 此处我们
//@PropertySource("classpath:config/jdbc.properties")
@MapperScan(basePackages = "me.mason.demo.dynamicdatasource.mapper")
public class DynamicDataSourceConfig {
@Bean(DataSourceConstants.DS_KEY_MASTER)
// 需要与配置文件中对应
@ConfigurationProperties(prefix = "spring.datasource.master")
public DruidDataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(DataSourceConstants.DS_KEY_SLAVE)
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DruidDataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DynamicDataSource dynamicDataSource() {
Map
/**
* 切面
*/
@Aspect
@Component
//@Order(-10)
public class DynamicDataSourceAspect {
// 以在类上使用了@Service作为切入点
@Pointcut("@within(org.springframework.stereotype.Service)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Class> aClass = Class.forName(signature.getDeclaringType().getName());
// 方法优先,如果方法上存在注解,则优先使用方法上的注解
if (signature.getMethod().isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(signature.getMethod().getAnnotation(DS.class).value());
// 其次类优先,如果类上存在注解,则使用类上的注解
}else if (aClass.isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(aClass.getAnnotation(DS.class).value());
// 如果都不存在,则使用默认
} else {
DynamicDataSourceContextHolder.setContextKey(DataSourceConstants.DS_KEY_MASTER);
}
try {
return joinPoint.proceed();
} finally {
DynamicDataSourceContextHolder.removeContextKey();
}
}
}
@Data
@TableName("test_user")
public class TestUser implements Serializable {
private static final long serialVersionUID = 1L;
/** id */
private Long id;
/** 姓名 */
private String name;
/** 手机号 */
private String phone;
/** 职称职别 */
private String title;
/** 邮箱 */
private String email;
/** 性别 */
private String gender;
/** 出生时间 */
private Date dateOfBirth;
/** 1:已删除,0:未删除 */
private Integer deleted;
/** 创建时间 */
private Date sysCreateTime;
/** 创建人 */
private String sysCreateUser;
/** 更新时间 */
private Date sysUpdateTime;
/** 更新人 */
private String sysUpdateUser;
/** 版本号 */
private Long recordVersion;
public TestUser() {
}
}
@Repository
public interface TestUserMapper extends BaseMapper {
/**
* 自定义查询
* @param wrapper 条件构造器
* @return
*/
List selectAll(@Param(Constants.WRAPPER) Wrapper wrapper);
}
@Service
//@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
@Autowired
private TestUserMapper testUserMapper;
/**
* 查询master库User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List getMasterUser(){
QueryWrapper queryWrapper = new QueryWrapper<>();
return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}
/**
* 查询slave库User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List getSlaveUser(){
return testUserMapper.selectList(null);
}
}
@RestController
@RequestMapping("/user")
public class TestUserController {
@Autowired
private TestUserService testUserService;
/**
* 查询全部
*/
@GetMapping("/listall")
public Object listAll() {
int initSize = 2;
Map result = new HashMap<>(initSize);
List masterUser = testUserService.getMasterUser();
result.put("masterUser", masterUser);
List slaveUser = testUserService.getSlaveUser();
result.put("getSlaveUser", slaveUser);
return ResponseResult.success(result);
}
}
@Service
//@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
@Autowired
private TestUserMapper testUserMapper;
/**
* 查询master库User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List getMasterUser(){
QueryWrapper queryWrapper = new QueryWrapper<>();
return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}
/**
* 查询slave库User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List getSlaveUser(){
return testUserMapper.selectList(null);
}
}
该代码优先级与使用框架效果一致,即不使用注解将默认使用MASTER数据库,方法上存在注解优先使用方法上标注的注解。
已知MASTER 6条数据, SLAVE4条数据
访问 http://127.0.0.1:8080/dd/user/listall 查看效果
@Service
@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
@Autowired
private TestUserMapper testUserMapper;
/**
* 查询master库User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List getMasterUser(){
QueryWrapper queryWrapper = new QueryWrapper<>();
return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}
/**
* 查询slave库User
* @return
*/
// @DS(DataSourceConstants.DS_KEY_MASTER)
public List getSlaveUser(){
return testUserMapper.selectList(null);
}
}
@Service
@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
@Autowired
private TestUserMapper testUserMapper;
/**
* 查询master库User
* @return
*/
@DS(DataSourceConstants.DS_KEY_SLAVE)
public List getMasterUser(){
QueryWrapper queryWrapper = new QueryWrapper<>();
return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}
/**
* 查询slave库User
* @return
*/
@DS(DataSourceConstants.DS_KEY_MASTER)
public List getSlaveUser(){
return testUserMapper.selectList(null);
}
}
先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦