1、前言
上篇文章大概讲了下shardingSphere中的sharding-jdbc的实现原理(https://www.cnblogs.com/smileIce/p/11131053.html),接下来我们想正对大家使用场景来分析下。
大家可以先看看shardingSphere的配置方式https://shardingsphere.apache.org/document/current/cn/manual/sharding-jdbc/configuration/config-spring-boot/。
我们可以发现shardingSphere是支持默认的 default-database-strategy或者针对表上的default-database-strategy。
对于规模不太大的公司来说,针对每个表配置default-database-strategy工作量还是比较大的,一般情况会针对不同的业务使用多个库,一个工程可能会使用多个库的数据(可能有人会说,用微服务不应该有一个项目使用多个库的情况,但是在真实的业务场景确实可能一个项目需要调用多个库,可能为了性能考虑,可能有其它业务方面的考虑)。
针对该场景可能通过注解的方式更好。用spring AbstractRoutingDataSource 方式线下如下 https://juejin.im/post/5a927d23f265da4e7e10d740,但是该方式针对事务就无能为力(针对分布式事务后面会讲到)。那么sharding-sphere是否能实现通过配置一个注解就能简单的切换数据源昵,当然可以了,接下来看看怎么实现。
2、实现annotation的数据源原理。
上一节提到HintManager,但没有细讲,这节可以讲讲HintManager
public final class HintManager implements AutoCloseable { private static final ThreadLocalHINT_MANAGER_HOLDER = new ThreadLocal<>(); private final Multimap > databaseShardingValues = HashMultimap.create(); private final Multimap > tableShardingValues = HashMultimap.create(); private boolean databaseShardingOnly; private boolean masterRouteOnly; /** * Get a new instance for {@code HintManager}. * * @return {@code HintManager} instance */ public static HintManager getInstance() { Preconditions.checkState(null == HINT_MANAGER_HOLDER.get(), "Hint has previous value, please clear first."); HintManager result = new HintManager(); HINT_MANAGER_HOLDER.set(result); return result; } /** * Set sharding value for database sharding only. * * The sharding operator is {
@code =} * * @param value sharding value */ public void setDatabaseShardingValue(final Comparable> value) { databaseShardingValues.clear(); databaseShardingValues.put("", value); databaseShardingOnly = true; } /** * Add sharding value for database. * *The sharding operator is {
@code =} * * @param logicTable logic table name * @param value sharding value */ public void addDatabaseShardingValue(final String logicTable, final Comparable> value) { databaseShardingValues.put(logicTable, value); databaseShardingOnly = false; } /** * Add sharding value for table. * *The sharding operator is {
@code =} * * @param logicTable logic table name * @param value sharding value */ public void addTableShardingValue(final String logicTable, final Comparable> value) { tableShardingValues.put(logicTable, value); databaseShardingOnly = false; } /** * Get database sharding values. * * @return database sharding values */ public static Collection> getDatabaseShardingValues() { return getDatabaseShardingValues(""); } /** * Get database sharding values. * * @param logicTable logic table * @return database sharding values */ public static Collection > getDatabaseShardingValues(final String logicTable) { return null == HINT_MANAGER_HOLDER.get() ? Collections. >emptyList() : HINT_MANAGER_HOLDER.get().databaseShardingValues.get(logicTable); } /** * Get table sharding values. * * @param logicTable logic table name * @return table sharding values */ public static Collection > getTableShardingValues(final String logicTable) { return null == HINT_MANAGER_HOLDER.get() ? Collections. >emptyList() : HINT_MANAGER_HOLDER.get().tableShardingValues.get(logicTable); } /** * Judge whether database sharding only. * * @return database sharding or not */ public static boolean isDatabaseShardingOnly() { return null != HINT_MANAGER_HOLDER.get() && HINT_MANAGER_HOLDER.get().databaseShardingOnly; } /** * Set database operation force route to master database only. */ public void setMasterRouteOnly() { masterRouteOnly = true; } /** * Judge whether route to master database only or not. * * @return route to master database only or not */ public static boolean isMasterRouteOnly() { return null != HINT_MANAGER_HOLDER.get() && HINT_MANAGER_HOLDER.get().masterRouteOnly; } /** * Clear threadlocal for hint manager. */ public static void clear() { HINT_MANAGER_HOLDER.remove(); } @Override public void close() { HintManager.clear(); } }
通过源码我可以发现HintManager 维护了一个线程变量 HINT_MANAGER_HOLDER,我们这里就是通过该变量实现的注解分库。
还记得databaseShardingOnly这个变量吗?这个变量决定了是使用DatabaseHintSQLRouter还是使用ParsingSQLRouter
public final class ShardingRouterFactory { /** * Create new instance of sharding router. * * @param shardingRule sharding rule * @param shardingMetaData sharding meta data * @param databaseType database type * @param showSQL show SQL or not * @return sharding router instance */ public static ShardingRouter newInstance(final ShardingRule shardingRule, final ShardingMetaData shardingMetaData, final DatabaseType databaseType, final boolean showSQL) { return HintManagerHolder.isDatabaseShardingOnly() ? new DatabaseHintSQLRouter(shardingRule, showSQL) : new ParsingSQLRouter(shardingRule, shardingMetaData, databaseType, showSQL); } }
我们只要通过调用HintManager 的setDatabaseShardingValue,我们就可以设置为只进行分库并把分库的数据Id 填入其中
public void setDatabaseShardingValue(final Comparable> value) { databaseShardingValues.clear(); databaseShardingValues.put("", value); databaseShardingOnly = true; }
3、实现逻辑
定义数据库类型
public enum DataSourceType { DEFAULT("default", "default"),
DATASOURCE_1("datasource1", "datasource1"),
DATASOURCE_2("datasource1", "datasource2"),
; private String name; private String identity; DataSourceType(String name, String identity) { this.name = name; this.identity = identity; } public String getName() { return name; } public String getIdentity() { return identity; } public static DataSourceType getDataSourceTypeByName(String name) { for (DataSourceType dataSourceType : DataSourceType.values()) { if (dataSourceType.name.equals(name)) { return dataSourceType; } } throw new RuntimeException("db is not exist." + name); } }
定义注解类
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { DataSourceType value(); boolean isDatabaseShardingOnly() default true; }
定义AOP拦截器
@Aspect @Order(-10) @Component public class DynamicDataSourceAspect { private static final Logger LOGGER = LogManager.getLogger(DynamicDataSourceAspect.class); @Before("within(@com.newretail.datasource.TargetDataSource *) || @annotation(com.newretail.datasource.TargetDataSource)") public void changeDataSource(JoinPoint point) { MethodSignature joinPointObject = (MethodSignature) point.getSignature(); TargetDataSource targetDataSource = null; if (joinPointObject.getDeclaringType().isAnnotationPresent(TargetDataSource.class)) { targetDataSource = (TargetDataSource) joinPointObject.getDeclaringType().getAnnotation(TargetDataSource.class); } Method method = joinPointObject.getMethod(); if (method.isAnnotationPresent(TargetDataSource.class)) { targetDataSource = method.getAnnotation(TargetDataSource.class); } if (targetDataSource.isDatabaseShardingOnly()) { //获取当前的指定的数据源; DataSourceType dsId = targetDataSource.value(); HintManager.getInstance().setDatabaseShardingValue(dsId.getIdentity()); } } @After(value = "@annotation(com.newretail.datasource.TargetDataSource)") public void restoreDataSource(JoinPoint point) { //方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。 HintManagerHolder.clear(); } }
实现HintShardingAlgorithm算法。
public class AnnotationHintShardingAlgorithm implements HintShardingAlgorithm { private final static Logger logger = LoggerFactory.getLogger(AnnotationHintShardingAlgorithm.class); @Override public CollectiondoSharding(Collection availableTargetNames, ShardingValue shardingValue) { if (shardingValue instanceof ListShardingValue && !CollectionUtils.isEmpty(((ListShardingValue) shardingValue).getValues())) { logger.info("---------------------"+((ListShardingValue) shardingValue).getValues()); return availableTargetNames.stream().filter(availableTargetName -> ((ListShardingValue) shardingValue).getValues().contains(availableTargetName)).collect(Collectors.toList()); } return availableTargetNames; } }
配置如下
# 数据源 load.datasources 可以指定需要加载的数据源,没配置加载所有的 sharding: jdbc: datasource: names: datasource1,datasource2
datasource1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl:
username:
password: datasource2: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbcUrl: username: password: config: sharding: defaultDatabaseStrategy: hint: algorithmClassName: com.newretail.datasource.AnnotationHintShardingAlgorithm props: sql: show: false
public interface DeliveryTargetMapper { @TargetDataSource(value = DataSourceType.DATASOURCE_1) ListgetCityList(); }