SpringBoot多数据源的两种实现方式实例(springboot多数据源原理)

网友投稿 299 2022-08-03


SpringBoot多数据源的两种实现方式实例(springboot多数据源原理)

目录前言基于dynamic-datasource实现多数据源dynamic-datasource介绍dynamic-datasource特性使用 @DS 切换数据源@DS使用实例基于AOP手动实现多数据源总结

前言

公司项目有连接多个不同数据库的需求,特研究了一下,根据网上的资料,造了一个基于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">

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">

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-packagehttp://=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

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

}

}

TestUserService

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

}

}

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

*/

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

}

}

效果

总结


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

上一篇:Java中Stream流中map和forEach的区别详解(stream中的map方法)
下一篇:IDEA远程管理docker镜像及容器服务的实现(idea链接docker)
相关文章

 发表评论

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