通常业务开发中,我们会使用到多个数据源,比如,部分数据存在mysql实例中,部分数据是在oracle数据库中,那这时候,项目基于springboot和mybatis,其实只需要配置两个数据源即可,只需要按照
dataSource -SqlSessionFactory - SqlSessionTemplate配置好就可以了。
如下代码,首先我们配置一个主数据源,通过@Primary注解标识为一个默认数据源,通过配置文件中的spring.datasource作为数据源配置,生成SqlSessionFactoryBean,最终,配置一个SqlSessionTemplate。
@Configuration
@MapperScan(basePackages = "com.xxx.mysql.mapper", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Bean(name = "primaryDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druid() {
return new DruidDataSource();
}
@Bean(name = "primarySqlSessionFactory")
@Primary
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper
public void innerBefore(JoinPoint point, boolean isClass) {
String methodName = point.getSignature().getName();
Class<?> clazz = getClass(point, isClass);
//使用默认数据源
String dbKey = DBKey.DEFAULT;
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
Method method = null;
try {
method = clazz.getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
throw new RuntimeException("can't find " + methodName + " in " + clazz.toString());
}
//方法上存在注解,使用方法定义的datasource
if (method.isAnnotationPresent(DBKey.class)) {
DBKey key = method.getAnnotation(DBKey.class);
dbKey = key.value();
} else {
//方法上不存在注解,使用类上定义的注解
clazz = method.getDeclaringClass();
if (clazz.isAnnotationPresent(DBKey.class)) {
DBKey key = clazz.getAnnotation(DBKey.class);
dbKey = key.value();
}
}
DBContextHolder.setDBKey(dbKey);
}
private Class<?> getClass(JoinPoint point, boolean isClass) {
Object target = point.getTarget();
String methodName = point.getSignature().getName();
Class<?> clazz = target.getClass();
if (!isClass) {
Class<?>[] clazzList = target.getClass().getInterfaces();
if (clazzList == null || clazzList.length == 0) {
throw new MutiDBException("找不到mapper class,methodName =" + methodName);
}
clazz = clazzList[0];
}
return clazz;
}
}
既然在执行mapper之前,该mapper接口最终使用的数据源已经被放入threadLocal中,那么,只需要重写新的路由数据源接口逻辑即可:
public class RoutingDatasource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String dbKey = DBContextHolder.getDBKey();
return dbKey;
}
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
for (Object key : targetDataSources.keySet()) {
DBContextHolder.addDBKey(String.valueOf(key));
}
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
}
另外,我们在服务启动,配置mybatis的时候,将所有的db配置加载:
@Bean
@ConditionalOnMissingBean(DataSource.class)
@Autowired
public DataSource dataSource(MybatisProperties mybatisProperties) {
Map<Object, Object> dsMap = new HashMap<>(mybatisProperties.getNodes().size());
for (String nodeName : mybatisProperties.getNodes().keySet()) {
dsMap.put(nodeName, buildDataSource(nodeName, mybatisProperties));
DBContextHolder.addDBKey(nodeName);
}
RoutingDatasource dataSource = new RoutingDatasource();
dataSource.setTargetDataSources(dsMap);
if (null == dsMap.get(DBKey.DEFAULT)) {
throw new RuntimeException(
String.format("Default DataSource [%s] not exists", DBKey.DEFAULT));
}
dataSource.setDefaultTargetDataSource(dsMap.get(DBKey.DEFAULT));
return dataSource;
}
@ConfigurationProperties(prefix = "mybatis")
@Data
public class MybatisProperties {
private Map<String, String> params;
private Map<String, Object> nodes;
private String mapperLocations = "classpath*:com/iqiyi/xiumapper
private String basePackage = "com.iqiyi.xiu.**.repository";
private String configLocation = "classpath:mybatis-config.xml";
}
那threadLocal中的key什么时候进行销毁呢,其实可以自定义一个基于mybatis的拦截器,在拦截器中主动调DBContextHolder.clear()方法销毁这个key。具体代码就不贴了。这样一来,我们就完成了一个基于注解的支持多数据源切换的中间件。
那有没有可以优化的点呢?其实,可以发现,在获取mapper接口/所在类的注解的时候,使用了反射来获取的,那我们知道一般反射调用是比较耗性能的,所以可以考虑在这里加个本地缓存来优化下性能:
private final static Map<String, String> METHOD_CACHE = new ConcurrentHashMap<>();
//....
public void innerBefore(JoinPoint point, boolean isClass) {
String methodName = point.getSignature().getName();
Class<?> clazz = getClass(point, isClass);
//key为类名+方法名
String keyString = clazz.toString() + methodName;
//使用默认数据源
String dbKey = DBKey.DEFAULT;
//如果缓存中已经有这个mapper方法对应的数据源的key,那直接设置
if (METHOD_CACHE.containsKey(keyString)) {
dbKey = METHOD_CACHE.get(keyString);
} else {
Class<?>[] parameterTypes =
((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
Method method = null;
try {
method = clazz.getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
throw new RuntimeException("can't find " + methodName + " in " + clazz.toString());
}
//方法上存在注解,使用方法定义的datasource
if (method.isAnnotationPresent(DBKey.class)) {
DBKey key = method.getAnnotation(DBKey.class);
dbKey = key.value();
} else {
clazz = method.getDeclaringClass();
//使用类上定义的注解
if (clazz.isAnnotationPresent(DBKey.class)) {
DBKey key = clazz.getAnnotation(DBKey.class);
dbKey = key.value();
}
}
//先放本地缓存
METHOD_CACHE.put(keyString, dbKey);
}
DBContextHolder.setDBKey(dbKey);
}
这样一来,只有在第一次调用这个mapper接口的时候,才会走反射调用的逻辑去获取对应的数据源,后续,都会走本地缓存,提升了性能。
到此这篇关于基于注解的springboot+mybatis的多数据源组件的实现代码的文章就介绍到这了,更多相关springboot mybatis多数据源组件内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!