利用SpringBoot实现多数据源的两种方式总结

网友投稿 473 2022-09-20


利用SpringBoot实现多数据源的两种方式总结

目录前言基于dynamic-datasource实现多数据源dynamic-datasource介绍dynamic-datasource的相关约定 引入dynamic-datasource依赖配置数据源使用 @DS 切换数据源基于AOP手动实现多数据源项目工程结构项目依赖配置文件自定义注解编写DataSourceConstants动态数据源名称上下文处理获取当前动态数据源方法动态数据源配置AOP切面编写TestUser实体TestUserMapperTestUserServiceTestUserControllerMapperXml启动测试效果总结

前言

公司项目有连接多个不同数据库的需求,特研究了一下,根据网上的资料,造了一个基于AOP方式的数据源切换轮子,但继续探索,突然发现有开源的多数据源管理启动器。不过,本篇两种方式都会介绍。

基于dynamic-datasource实现多数据源

dynamic-datasource介绍

dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。

其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x

dynamic-datasource特性

支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。

支持数据库敏感配置信息 加密 ENC()。

支持每个数据库独立初始化表结构schema和数据库database。

支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。

支持 自定义注解 ,需继承DS(3.2.0+)。

提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。

提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。

提供 自定义数据源来源 方案(如全从数据库加载)。

提供项目启动后 动态增加移除数据源 方案。

提供Mybatis环境下的 纯读写分离 方案。

提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。

支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。

提供 基于seata的分布式事务方案。

提供 本地多数据源事务方案。 附:不能和原生spring事务混用。

我们目前只探讨使用dynamic-datasource进行数据源切换,其他请自行搜索

dynamic-datasource的相关约定

dynamic-datasource只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD。

配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。

切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。

默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。

方法上的注解优先于类上注解。

DS支持继承抽象类上的DS,暂不支持继承接口上的DS。

引入dynamic-datasource依赖

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 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。

注解

结果

不使用@DS注解

默认数据源,即primary: mysql

@DS(“dsName”)

dsName可以为组名也可以为具体某个库的名称

@DS使用实例

@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");

}

}

基于AOP手动实现多数据源

本次代码参考 https://github.com/mianshenglee/my-example/tree/master/multi-datasource/dynamic-datasource ,因源码不满足的需求,因此我在此基础做了修改。

项目工程结构

项目依赖

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"&gthttp://;

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

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"&gthttp://;

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;

}

编写DataSourceConstants

/**

* 数据源常量

**/

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 dataSourceMap = new HashMap<>(2);

dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());

dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());

//设置动态数据源

DynamicDataSource dynamicDataSource = new DynamicDataSource();

dynamicDataSource.setTargetDataSources(dataSourceMap);

dynamicDataSource.setDefaultTargetDataSource(masterDataSource());

return dynamicDataSource;

}

}

AOP切面

/**

* 切面

*/

@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();

}

}

}

编写TestUser实体

@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() {

}

}

TestUserMapper

@Repository

public interface TestUserMapper extends BaseMapper {

/**

* 自定义查询

* @param wrapper 条件构造器

* @return

*/

List selectAll(@Param(Constants.WRAPPER) Wrapper wrapper);

}

TestUserService

@Service

//@DS(DataSourceConstants.DS_KEY_SLAVE)

public class TestUserService {

@AutowireiHyjfBjd

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);

}

}

TestUserController

@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);

}

}

MapperXml

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

select * from test_user

${ew.customSqlSegment}

启动测试

不使用注解

@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

*/

// @DiHyjfBjS(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);

}

}

效果

总结


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:做什么样的产品能够被顶级投资人看好?| PHM-LAB专访+视频干货(一个好的投资者)
下一篇:MUX VLAN(木星在中国古代被称为)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~