【Mybatis多数据源一】简单,无误,看这篇就够了!StringBoot配置mybatis多数据源,读写分离的配置

前言:多数据源跟读写分离,没啥区别的,只是数据库连接不一样。。

一、配置原理

1.认识AbstractRoutingDataSource类

说实话,mybatis配置多数据源,AbstractRoutingDataSource类起到一个决定性的作用,读者可以直接在项目中找到这个类,打开后,找到最后这个方法:

	protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		Object lookupKey = determineCurrentLookupKey();
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
			dataSource = this.resolvedDefaultDataSource;
		}
		if (dataSource == null) {
			throw new IllegalStateException("Cannot determine target DataSource for 
			lookup key [" + lookupKey + "]");
		}
		return dataSource;
	}


	protected abstract Object determineCurrentLookupKey();

该方法字面意思就是:决定目标数据源!看,多么清晰的名字,再看最后的一个方法,determineCurrentLookupKey 这个方法就是决定当前的数据源的key, this.resolvedDataSources.get(lookupKey)(determineTargetDataSource()方法里面的代码),这个resolvedDataSources就是个Map,所以你就可以知道,determineCurrentLookupKey就是获取一个Map的key名称。
注意留意这个方法:determineCurrentLookupKey 是一个抽象类,在spring的框架中,凡是抽象类又没有实现的方法,一般都是给使用者自定义的扩展方法,切记切记!!!
所以,我们配置多数据源,就应该从这个扩展方法开始!!!

2.实现AbstractRoutingDataSource

实现AbstractRoutingDataSource的目的很简单,就是为了扩展determineCurrentLookupKey方法,只要实现了这个方法,配置多数据源,就成功了一半!
具体方法如下:

/**
 * 动态数据源
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDb();
    }
}
/** 当前线程数据源 */
public class DataSourceContextHolder {
	private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

	// 设置数据源名
	public static void setDb(String dbName) {
		CONTEXT_HOLDER.set(dbName);
	}

	// 获取数据源名
	public static String getDb() {
		return CONTEXT_HOLDER.get();
	}

	// 清除数据源名
	public static void clearDb() {
		CONTEXT_HOLDER.remove();
	}
}

该步骤,我们使用了ThreadLocal 保证当前线程的数据源具有线程独占性,与其他线程无关;

读者如果进行到这里,恭喜,你已经成功了一半。接下来,我们做点业务上的代码。

3.业务关联代码

3.1 创建个注解,用来方便指定数据源名称的

/** 数据源注解 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface TargetDataSource {
	//默认是主库
	String value() default "master_db";
}

3.2 创建切面

切面的目的是为了获取类或者方法上的TargetDataSource 注解的数据源名称:


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;


@Aspect
@Order(1)
@Component
public class DynamicDataSourceAspect {
    /**
     * 数据源优先级
     * 1.方法上的TargetDataSource注解
     * 2.类上的TargetDataSource注解
     * 3.默认数据源
     * */
    @Around("execution(* com.xxx.data.xxx.xxx.service..*.*Impl.*(..))")//扫描路径:你需要切换数据源的包名,通常是实现类所在的包
    public Object switchDs(ProceedingJoinPoint point) throws Throwable {
        Class<?> className = point.getTarget().getClass();
		
		//默认的数据源,其实可以把写死的放到一个枚举中
        String dataSource = "master_db";

        // 得到访问的方法对象
        String methodName = point.getSignature().getName();
        Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();
        Method method = className.getMethod(methodName, argClass);

        // 判断是否存在@TargetDataSource注解
        if (method.isAnnotationPresent(TargetDataSource.class)) {
            TargetDataSource annotation = method.getAnnotation(TargetDataSource.class);
            // 取出注解中的数据源名
            dataSource = annotation.value();
        } else if (className.isAnnotationPresent(TargetDataSource.class)) {
            TargetDataSource ds = className.getAnnotation(TargetDataSource.class);
            dataSource = ds.value();
        }

        // 切换数据源
        DataSourceContextHolder.setDb(dataSource);

        try {
            return point.proceed();
        } finally {
            DataSourceContextHolder.clearDb();
        }
    }
}

仔细看下,其目的就是为了走: DataSourceContextHolder.setDb(dataSource);–把当前线程持有的数据源名称替换成指定的名称!

3.3 创建多个数据源DataSource

之前我们提到,determineCurrentLookupKey 这个方法,就是得到一个key,这个key,就是指定的数据源的名称,而这个名称,其实,就可以从DataSourceContextHolder.getDb()中获取!
假如,创建多个数据源,放到一个Map<String,DataSource>,然后用这个key,是不是就可以轻松获取到一个DataSource了? 正好,前面讲过的 AbstractRoutingDataSource 这个类,就有一个对象,专门存放多个数据源的Map,那就是:
private Map<Object, Object> targetDataSources;

所以,可以参考如下类,创建多个数据源以及SqlSessionFactoryBean:


@MapperScan(basePackages = { "com.xx.data.xx.xx.datamodel.dao" })//你们自己的dao
@Configuration
public class DataModelConfig {
	@Value("${druid.masterdb.url}")
	private String fmasterdbUrl;
	@Value("${druid.masterdb.username}")
	private String masterdbserName;
	@Value("${druid.masterdb.password}")
	private String masterdbPwd;

	@Value("${druid.slave1.url}")
	private String slave1Url;
	@Value("${druid.slave1.username}")
	private String slave1serName;
	@Value("${druid.slave1.password}")
	private String slave1Pwd;


	@Value("${druid.slave2.url}")
	private String slave2Url;
	@Value("${druid.slave2.username}")
	private String slave2UserName;
	@Value("${druid.slave2.password}")
	private String slave2Pwd;


	@Autowired
	private DateSourceConfigBean dateSourceConfigBean;

	//很关键:创建的数据源,一定是自己建的那个动态数据源
	@Bean(name = "dynamicDataSource")
	public DataSource dynamicDataSource() {

		DynamicDataSource dynamicDataSource = new DynamicDataSource();

		DataSource defaultdb= defaultDb();

		// 默认数据源
		dynamicDataSource.setDefaultTargetDataSource(defaultdb);

		// 配置多数据源
		Map<Object, Object> dsMap = new HashMap<>(6);
		dsMap.put("master_db",defaultdb);
		dsMap.put("slave2",slave2Db());
		dsMap.put("slave1", slave1Db());
		//你看,这里把N个数据源放到了一个上面说的Map
        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
	}


	private DataSource defaultDb() {
		return createDruidDataSource(masterdbUrl, masterdbUserName, masterdbPwd);
	}


	private DataSource slave1Db() {
	return createDruidDataSource(slave1Url, slave1UserName, slave1Pwd);
	}


	private DataSource slave2Db() {
		return  createDruidDataSource(slave2Url, slave2UserName, slave2Pwd);
	}


	private  DruidDataSource createDruidDataSource(String url,String  dbUserName,String dbUserPwd ){
		try{
			DruidDataSource dataSource = new DruidDataSource();
			dataSource.setUrl(url);
			dataSource.setUsername(username);
			dataSource.setPassword(password);
			return dataSource;

		}catch (Exception e){
			System.exit(-1);
		}
		return  null;
	}

	@Bean
	public SqlSessionFactoryBean sqlSessionFactory(DataSource dynamicDataSource) {

		SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
		sqlSessionFactory.setDataSource(dynamicDataSource);
		try {
			// spring获取classpath下通配的文件列表解析器
			ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
			Resource[] resources = resourceLoader.getResources("com/xx/data/xxx/xxx/xxx/mapper/**/*.xml");//你们mapper的xml文件地址
			sqlSessionFactory.setMapperLocations(resources);
			return sqlSessionFactory;

		} catch (IOException e) {
			logger.error("Load mybatis mapper xml exception, ", e);
		}
		return null;
	}

3.4 集成业务

//连接从库1

import java.util.List;

@TargetDataSource("master_db")
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> selectList(Map<String,String> condition) {
        return userMapper.selectList(condition);
    }
}

//连接从库2

import java.util.List;

@TargetDataSource("slave1")
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> selectList(Map<String,String> condition) {
        return userMapper.selectList(condition);
    }
}

//连接主库

import java.util.List;

@TargetDataSource("slave2")
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> selectList(Map<String,String> condition) {
        return userMapper.selectList(condition);
    }
    
	@TargetDataSource("slave1")//方法上也可以直接加注解
    @Override
    public List<User> selectList1(Map<String,String> condition) {
        return userMapper.selectList1(condition);
    }
}

以上,所有配置动态数据源的方法!读写分离一模一样,只是数据库连接不同而已!!

二、实战代码

篇幅多长,可以看下一篇。


版权声明:本文为shenhaiyushitiaoyu原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。