公司项目有连接多个不同数据库的需求,特研究了一下,根据网上的资料,造了一个基于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。
配置数据源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 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
@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 ,因源码不满足的需求,因此我在此基础做了修改。
项目工程结构 项目依赖配置文件4.0.0 org.springframework.boot spring-boot-starter-parent2.2.2.RELEASE me.mason.demo dynamic-datasource0.0.1-SNAPSHOT dynamic-datasource Demo project for dynamic datasource 1.8 org.springframework.boot spring-boot-starter-weborg.springframework.boot spring-boot-configuration-processorcom.alibaba druid-spring-boot-starter1.1.9 org.springframework.boot spring-boot-starter-jdbcorg.springframework.boot spring-boot-starter-aopmysql mysql-connector-javaruntime com.baomidou mybatis-plus-boot-starter3.3.0 org.projectlombok lomboktrue org.springframework.boot spring-boot-starter-testtest org.junit.vintage junit-vintage-engineorg.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 public class DataSourceConstants { public static final String DS_KEY_MASTER = "master"; public static final String DS_KEY_SLAVE = "slave"; }动态数据源名称上下文处理
public class DynamicDataSourceContextHolder { private static final ThreadLocal获取当前动态数据源方法DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>(); public static void setContextKey(String key){ DATASOURCE_CONTEXT_KEY_HOLDER.set(key); } 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() { MapAOP切面
@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; private Long id; private String name; private String phone; private String title; private String email; private String gender; private Date dateOfBirth; 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 baseMapperTestUserService{ List selectAll(@Param(Constants.WRAPPER) Wrapper wrapper); }
@Service //@DS(DataSourceConstants.DS_KEY_SLAVE) public class TestUserService { @Autowired private TestUserMapper testUserMapper; // @DS(DataSourceConstants.DS_KEY_MASTER) public ListTestUserControllergetMasterUser(){ QueryWrapper queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } // @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; MapMapperXmlresult = 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; // @DS(DataSourceConstants.DS_KEY_MASTER) public List效果getMasterUser(){ QueryWrapper queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } // @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; // @DS(DataSourceConstants.DS_KEY_MASTER) public List效果 方法上使用注解getMasterUser(){ QueryWrapper queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } // @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; @DS(DataSourceConstants.DS_KEY_SLAVE) public List效果getMasterUser(){ QueryWrapper queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } @DS(DataSourceConstants.DS_KEY_MASTER) public List getSlaveUser(){ return testUserMapper.selectList(null); } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)