听说爱点赞的人都月瘦十斤,月入十万哟!
自从接触了JAVA,真的是每天都学到新知识,很具有挑战性,今天开发过程中,springboot项目需要连接mysql数据库和sql server数据库.通过查资料,终于完成了接口测试,记录一下,以便帮助更多的人.
通过百度查询 关键字:springboot 多数据源,给出的例子基本都是多套源策略,什么是多套源呢,看下图:
多套源策略1.png
这种策略虽然简单、直接、好理解,也符合开闭原则(再添加数据库,原来的数据库信息不用修改,只添加即可)
但是资源浪费,代码冗余,缺乏灵活。需要针对每一个数据源写一套操作,不推荐使用。
今天我来介绍使用AOP切面进行动态数据源配置。用户可以根据实际业务需要,统一操作逻辑,只需要在切换数据源的地方进行切换即可,超级方便。流程图如下:
动态数据源2.png
今天举例连接mysql数据库和sql server数据库
1,包结构说明
|--common
|--annotation //自定义注解
|--aop //切面
|--context //自定义
|--config //数据源配置
|--controller //访问接口
|--entity //实体类
|--mapper //数据库操作
|--service //服务类
|--impl //实现类
2,pom.xml 引入相关包。
其中sqlserver引入jar包比较特殊,不能直接maven引入,需要自己下载,并添加到本地maven默认地址,sqljdbc4:jar:4.0问题解决方案
com.baomidou
mybatis-plus-boot-starter
3.2.0
mysql
mysql-connector-java
runtime
5.1.47
com.microsoft.sqlserver
sqljdbc4
4.0
org.springframework.boot
spring-boot-starter-aop
3,接下来就是配置数据库信息了,在application.yml中添加
注:这里可以看出mysql和sqlserver的配置是不同的。数据库名引入不同。
mysql是端口号/book_test,
sqlserver是端口号;DatabaseName=project
spring:值对应
datasource:
master1:
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/book_test?useUnicode=true&useSSL=false&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
username: ***
password: ***
master2:
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc-url: jdbc:sqlserver://****:1433;DatabaseName=project
username: **
password: **
4,前期准备工作已完成,接下来进行动态数据源配置
4.1数据源配置:根据连接信息把数据源注入到spring中.config/DynamicDataSourceConfig.java
package com.springboot.test.config;
/**
* @date: Created in 2020/8/12 13:36
* @description: 动态数据源配置
* @version: 1.0
*/
//import me.mason.demo.dynamicdatasource.constants.DataSourceConstants;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
// 添加此配置,否则 报`The dependencies of some of the beans in the application context form a cycle`
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
@Configuration
@PropertySource("classpath:application.yml")
@MapperScan(basePackages = "com.springboot.test.mapper")
public class DynamicDataSourceConfig {
@Bean("master1")
@ConfigurationProperties(prefix = "spring.datasource.master1")
public DataSource master1DataSource() {
return DataSourceBuilder.create().build();
}
@Bean("master2")
@ConfigurationProperties(prefix = "spring.datasource.master2")
public DataSource master2DataSource() {
return DataSourceBuilder.create().build();
}
//设置动态数据源为主数据源
@Bean
@Primary
public DataSource dynamicDataSource() {
Map dataSourceMap = new HashMap<>(2);
dataSourceMap.put("master1", master1DataSource());
dataSourceMap.put("master2", master2DataSource());
//设置动态数据源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(master1DataSource());
return dynamicDataSource;
}
}
4.2 添加动态数据源类.config/DynamicDataSource.java
package com.springboot.test.config;
import com.springboot.test.common.context.DynamicDataSourceContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @date: Created in 2020/8/12 14:07
* @description: 动态数据源
* @version: 1.0
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getContextKey();
}
}
4.3动态返回数据源。contxt/DynamicDataSourceContextHolder.java
package com.springboot.test.common.context;
/**
* @date: Created in 2020/8/12 14:02
* @description: TODO
* @version: 1.0
*/
public class DynamicDataSourceContextHolder {
/**
* 动态数据源名称上下文
*/
private static final ThreadLocal DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
/**
* 设置数据源
* @param key
*/
public static void setContextKey(String key){
System.out.println("切换数据源"+key);
DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
}
/**
* 获取数据源名称
* @return
*/
public static String getContextKey(){
String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
return key == null?"master1":key;
}
/**
* 删除当前数据源名称
*/
public static void removeContextKey(){
DATASOURCE_CONTEXT_KEY_HOLDER.remove();
}
}
4.3 定义数据源注解,这里很关键哟,定义了注解,之后再server层就可以直接@注解使用了.common/annotation/DS.java。不要问为什么把注解定义为java,因为大家都这样做,DS也是DataSource的简称。
package com.springboot.test.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @date: Created in 2020/8/12 13:55
* @description: TODO
* @version: 1.0
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
/**
* 数据源名称
* @return
*/
String value() default "master1";
}
4.4 定义切面
package com.springboot.test.common.aop;
import com.springboot.test.common.annotation.DS;
import com.springboot.test.common.context.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* @date: Created in 2020/8/12 14:00
* @description: TODO
* @version: 1.0
*/
@Aspect
@Component
public class DynamicDataSourceAspect {
@Pointcut("@annotation(com.springboot.test.common.annotation.DS)")
public void dataSourcePointCut(){
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dsKey = getDSAnnotation(joinPoint).value();
DynamicDataSourceContextHolder.setContextKey(dsKey);
try{
return joinPoint.proceed();
}finally {
DynamicDataSourceContextHolder.removeContextKey();
}
}
/**
* 根据类或方法获取数据源注解
* @param joinPoint
* @return
*/
private DS getDSAnnotation(ProceedingJoinPoint joinPoint){
Class> targetClass = joinPoint.getTarget().getClass();
DS dsAnnotation = targetClass.getAnnotation(DS.class);
// 先判断类的注解,再判断方法注解
if(Objects.nonNull(dsAnnotation)){
return dsAnnotation;
}else{
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
return methodSignature.getMethod().getAnnotation(DS.class);
}
}
}
5,接下来就是service层使用AOP进行数据源切换了。
在service/impl/TestUserServiceImpl.java页面
/**
* 查询master1库User
* @return
*/
@DS("master1") //这个是自定义注解
public List getMasterUser(){
QueryWrapper queryWrapper = new QueryWrapper<>();
return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}
/**
* 查询master2库User
* @return
*/
@DS("master2") //这个是自定义注解
public List getSlaveUser(){
return testUserMapper.selectList(null);
}
controller层
/**
* 查询全部
*/
@GetMapping("/listall")
public Object listAll() {
int initSize = 2;
Map result = new HashMap<>(initSize);
//默认master数据源查询
List masterUser = testUserService.getMasterUser();
result.put("master1", masterUser);
//从slave数据源查询
List slaveUser = testUserService.getSlaveUser();
result.put("master2", slaveUser);
//返回数据
return ResponseResult.success(result);
}
看,是不是超级简单?没有数据库切换代码,只需要关注业务逻辑即可,省心好多。你学会了吗?