SpringBoot配置多数据源

这几天一直在研究SpringBoot配置多数据源的方法,网上方法和轮子都挺多的,我没选择使用网上的轮子,自己手把手网上找资源,配置的,今天终于实现了配置多数据源,先贴出一篇博客地址,本来是有两篇的,这两篇博客写的都很nice,在这里先感谢下两篇博客的博主,还有一篇找不到了,就只能先贴出一篇了。

https://blog.csdn.net/Mango_hc/article/details/91453794](https://blog.csdn.net/Mango_hc/article/details/91453794)

先发一下我yml文件的配置

spring:
  datasource:
    master: #这个master和other是自定义的,名称随便取即可
     #数据库连接地址,因为我本地的MySQL版本是8.0所以需要配置下时区为serverTimezone=UTC,其他版本的MySQL需不需要配置我不太清楚。
      jdbc-url: jdbc:mysql://localhost:3306/masterdatabase?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
      #用户名
      username: sakura
       #密码
      password: sakura
      #连接驱动
      driver-class-name: com.mysql.cj.jdbc.Driver
      #数据库类型
      database: mysql
    other:
      jdbc-url: jdbc:mysql://localhost:3306/otherdatabase?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
      username: sakura
      password: sakura
      driver-class-name: com.mysql.cj.jdbc.Driver
      database: mysql
  jpa:
    #指定数据类型
    database: mysql

第一种方式:注入SqlSessionTemplate

这一种方式局限性比较大,但是配置较少,使用起来也只能配置mapper.xml使用,配置如下。

  1. 创建一个DataMasterSourceConfig类,该类为主数据的配置类,除了创建该类之外还需要创建一个DataOtherSourceConfig类,该类为从数据源配置类,如果还有第三个第四个数据源还需要创建其他的从数据类,代码如下
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
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.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

/**
 - @author 李七夜
 - @version 1.0
 - Created by 李七夜 on 2020/6/12 17:35
 */
 //@Configuration:用于定义配置类,在Spring启动的时候,这个会被注入到Spring中,会替换掉原本的DataSource配置,优先从我们配置的这个类里取
//@MapperScan 扫描Dao层所在的包,并且将我们配置好的sqlSessionFactoryRef注入进去
@Configuration
@MapperScan(basePackages = "com.sakura.tm.dao1.tm", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class DataMasterSourceConfig {
	
	/**
	*@Primary,优先取master数据源配置
	*@Bean将这个方法声明为一个bean,在Spring启动后可以注入这个bean
	*@ConfigurationProperties,获取yml文件的配置,前缀就选我们自定义配置的master
	*/
	@Primary
	@Bean(name = "dataMasterSource")
	@ConfigurationProperties(prefix = "spring.datasource.master")
	public DataSource getDataMasterSourceConfig() {
		return DataSourceBuilder.create().build();
	}
	
	/**
	*自定义的sqlSessionFactory
	*/
	@Primary
	@Bean(name = "masterSqlSessionFactory")
	public SqlSessionFactory masterSqlSessionFactory(@Qualifier("dataMasterSource") DataSource datasource)
			throws Exception {
		SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
		bean.setDataSource(datasource);
		bean.setMapperLocations(
				// 设置mybatis的xml所在位置
				new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/mapper/*.xml"));
		return bean.getObject();
	}
	/**
	*自定义的sqlSessionTemplate
	*/
	@Primary
	@Bean("masterSqlSessionTemplate")
	public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sessionfactory) {
		return new SqlSessionTemplate(sessionfactory);
	}
}

其他从数据源配置跟数据源配置一样,只是不需要加入@Primary注解,这样会导致Spring启动异常,不知道优先注入那个数据源。

  • 到这一步其实多数据源配置已经好了,接下来可以写个测试类测试一下,先注入SqlSessionTemplate,然后使用@Qualifier注解注入你想要访问的数据源
    在这里插入图片描述

第二种方式:使用AOP

使用AOP灵活性较高,但是配置也比较繁琐,不过局限性不大,无论是使用注解、通用mapper、mapper.xml都可以使用。

  • 先编写一个DynamicDataSource类,这个类要继承AbstractRoutingDataSource类,这个AbstractRoutingDataSource类型是实现aop多数据源的关键,这里我直接引用我看的那篇博主的一段话描述

spring为我们提供了AbstractRoutingDataSource,即带路由的数据源。继承后我们需要实现它的determineCurrentLookupKey(),该方法用于自定义实际数据源名称的路由选择方法,由于我们将信息保存到了ThreadLocal中,所以只需要从中拿出来即可。


```java
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @author 李七夜
 * @version 1.0
 * Created by 李七夜 on 2020/6/15 11:22
 */
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {

	//数据源路由,此方用于产生要选取的数据源逻辑名称
	@Override
	protected Object determineCurrentLookupKey() {
		//从线程共享中获取数据源名称
		return DynamicDataSourceHolder.getDataSource();
	}
}

  • 然后在编写一个DynamicDataSourceHolder类,在注入DataSource过程中这一步骤处于单线程状态,所以需要加入一个锁来保证线程安全。
/**
 * @author 李七夜
 * @version 1.0
 * Created by 李七夜 on 2020/6/16 10:39
 */
public class DynamicDataSourceHolder {

	//本地线程共享对象
	private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

	public static void putDataSouce(String name){
		THREAD_LOCAL.set(name);
	}

	public static String getDataSource(){
		return THREAD_LOCAL.get();
	}

	public static void removeDataSource(){
		THREAD_LOCAL.remove();
	}
}

  • 在创建一个DBProperties类,这个类用于存放多数据源的配置信息,在Spring启动完成后会从yml文件中读取,跟第一种操作类似,一个HikariDataSource代表一个数据源配置

import com.zaxxer.hikari.HikariDataSource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author 李七夜
 * @version 1.0
 * Created by 李七夜 on 2020/6/16 10:46
 */
@Data
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DBProperties {

	private HikariDataSource master;
	private HikariDataSource other;
}

  • 接下来就是创建DataSourceConfig配置类,这个类会注入我们想要的DataSource
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.util.Map;

/**
 * @author 李七夜
 * @version 1.0
 * Created by 李七夜 on 2020/6/16 10:48
 */
@Slf4j
@Configuration
@EnableScheduling
public class DataSourceConfig {
	@Autowired
	private DBProperties dbProperties;

	/**
	 * 设置动态数据源,通过@Primary 来确定主DataSource
	 *
	 */
	@Bean(name = "dataSource")
	public DynamicDataSource dataSource() {
		DynamicDataSource dynamicDataSource = new DynamicDataSource();
		//1.设置默认数据源
		dynamicDataSource.setDefaultTargetDataSource(dbProperties.getMaster());
		//2.配置多数据源
		Map<Object, Object> map = Maps.newHashMap();
		map.put("master", dbProperties.getMaster());
		map.put("other", dbProperties.getOther());
		//3.存放数据源集
		dynamicDataSource.setTargetDataSources(map);
		return dynamicDataSource;
	}
}
  • 接下来还需要个自定义注解标签,用于动态的选择数据源

```java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 目标数据源注解,注解在方法上指定数据源的名称
 * @author 李七夜
 * @version 1.0
 * Created by 李七夜 on 2020/6/16 10:50
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {

	//此处接收数据源名称
	String value();
}
  • 最后就是编写AOP层了,在访问Dao层的根据方法注解判断注入什么类型的DataSource,使用该类型注入datasource可以笼罩整个Dao层,根据注入的@DataSource选择数据源
import com.sakura.tm.config.dao.DataSource;
import com.sakura.tm.config.dao.DynamicDataSourceHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author 李七夜
 * @version 1.0
 * Created by 李七夜 on 2020/6/16 10:51
 */
@Component
@Aspect
@Slf4j
public class DataSourceAspect {

	//切入点在dao层的方法上,配置aop的切入点
	@Pointcut("execution( * com.sakura.tm.dao1..*(..))")
	public void dataSourcePointCut1() {
	}

	@Before("dataSourcePointCut1() ")
	public void before(JoinPoint joinPoint) {
		Object target = joinPoint.getTarget();
		String method = joinPoint.getSignature().getName();
		Class<?>[] clazz = target.getClass().getInterfaces();
		Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
		try {
			Method m = clazz[0].getMethod(method, parameterTypes);
			//如果方法上存在切换数据源的注解,则根据注解内容进行数据源切换
			if (m != null && m.isAnnotationPresent(DataSource.class)) {
				DataSource annotation = m.getAnnotation(DataSource.class);
				String dataSourceName = annotation.value();
				DynamicDataSourceHolder.putDataSouce(dataSourceName);
				log.debug("-----current thread " + Thread.currentThread().getName() + " add " + dataSourceName + " to ThreadLocal-----");

			} else {
				log.debug("switch datasource fail, use default");
			}
		} catch (NoSuchMethodException e) {
			log.error("current thread " + Thread.currentThread().getName() + " add data to ThreadLocal error", e);
		}
	}


	//执行完切面后,清空线程共享中的数据源名称
	@After("dataSourcePointCut1()")
	public void after(JoinPoint joinPoint){
		DynamicDataSourceHolder.removeDataSource();
	}
}

mapper方法
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值