背景:作为sass平台,有若干机构作为系统的租户存在,用户的创建需要绑定到唯一的机构下面,机构有机构简称,设计为,根据不同的机构下的用户设立独立的数据库,平台系统根据用户所在的机构去连接不同数据库进行业务操作
1.创建注解类
@Target(ElementType.METHOD,ElementType.Type)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Ducumented
public @interface DBChangeAnno{
String value() default "master";
}
2.在数据库连接配置文件中设置多个数据源,并且数据源的名称根据机构简称设置
xx.jdbc.datasource.names=master,czbk
xx.jdbc.datasource.master.jdbcurl=jdbc:mysql://192.1.1.1:3306/db1
xx.jdbc.datasource.master.username=db1
xx.jdbc.datasource.master.passowrd=123456
xx.jdbc.datasource.master.driverClassName=com.mysql.jdbc.Driver
....
xx.jdbc.datasource.czbk.jdbcurl=jdbc:mysql://192.1.1.2:3306/db2
xx.jdbc.datasource.czbk.username=db2
xx.jdbc.datasource.czbk.passowrd=123456
xx.jdbc.datasource.czbk.driverClassName=com.mysql.jdbc.Driver
3.创建切面类等类,对所有标注了注解的方法执行前进行拦截并且切换数据源
//编写切面类,对注解方法进行前置增强,改变当前线程的数据库连接
@Slf4j
@Aspect
@Order(-1)
@ConditionalOnProperty(value = {xx.jdbc.datasource.names})
@Component
public class DBChangeInterceptor {
@Autowired
private XXXmapper mapper;//查询用户所在机构简称mapper
@Before("@annotation(dbChangeAnno)")
//这里也可加@within(dbChangeAnno)就会在类层面的所有方法进行切面,但是不能写在一起,比如@Before("@annotation(dbChangeAnno)||@within(dbChangeAnno)")这样只会生效后面的
public void switchDataSource(JoinPoint point,DBCHangeAnno anno){
String sourceName = anno.value();//默认是连接master数据库的,因为用户及机构信息是在master数据库进行维护
if(SessionUtils.getSession() != null){
sourceName = SessionUtils.getSession().getOrgId();//在session中存储当前登录用户的机构代码主键
OrgInfo orgi = mapper.sekectByPrimaryKey(sourceName);
sourceName = orgi.getShortName();
}
if(XXDataSourceContextHolder.containDataSourceName(sourceName)){
XXDataSourceContextHolder.setCurrentDataSourceName(sourceName);
}
}
//调用结束后还原线程的连接数据库到默认库中去
@After("@annotation(dbChangeAnno)")
public void restoreDataSource(JoinPoint point,DBCHangeAnno anno){
XXDataSourceContextHolder.clearCurrentDataSourceName();
}
}
//多数据源上下文类
public final class XXDataSourceContextHolder{
//数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰。
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<String>();
private static final Set<String> NAME_SET = new CopyOnWriteArraySet<String>();
private static String defaultDataSourceName = null;//程序启动时将xx.jdbc.datasource.names的第一个名称作为默认数据库
private XXDataSourceContextHolder(){
}
public static void setCurrentDataSourceName(String name){
CONTEXT_HOLDER.set(name);
}
public static String getCurrentDataSourceName(){
String name = CONTEXT_HOLDER.get();
if(name == null){
name = defaultDataSourceName;
}
return name;
}
public static void clearCurrentDataSourceName(){
CONTEXT_HOLDER.remove();
}
public static boolean containDataSourceName(String name){
return NAME_SET.contains(name);
}
public static void addDataSourceName(String name){
NAME_SET.add(name);
}
public static void setDefaultDataSourceName(String name){
XXDataSourceContextHolder.defaultDataSourceName = name;
}
}
//在数据源的配置时使用该数据源配置
@Slf4j
public class XXRoutingDataSource extends AbstractRoutingDataSource{
@Override
protected Object determinCurrentLookupKey(){
String cuurentDSName = XXDataSourceContextHolder.getCurrentDataSourceName();
if(currentDSName == null || "".equals(cuurentDSName)){
cuurentDSName = XXDataSourceContextHolder.getDefaultDataSourceName();
}
return cuurentDSName;
}
}
//在Controller类中需要切换数据库的方法增加注解
@RequestMapping("/a/b/")
public class XXController{
@RequestMapping(value = "list")
@DBChangeAnno //增加此注解表示该业务需根据用户所在机构切换不同数据库连接
public Obj list(){
return new Object;//具体业务逻辑编写
}
}
//参考文章:
https://www.jianshu.com/p/6b203f4926d5
https://www.cnblogs.com/haha12/p/10613549.html