Parcourir la source

增加动态表接换策略

zhouhao il y a 6 ans
Parent
commit
cfb138a625

+ 4 - 2
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/MybatisEntityFactory.java

@@ -6,13 +6,15 @@ import org.hswebframework.web.commons.entity.factory.EntityFactory;
 import java.util.*;
 
 /**
- * TODO 完成注释
+ * 使用EntityFactory来拓展mybatis实体
  *
  * @author zhouhao
  */
 public class MybatisEntityFactory extends DefaultObjectFactory {
 
-    private EntityFactory entityFactory;
+    private static final long serialVersionUID = -7388760632000329910L;
+
+    private transient EntityFactory entityFactory;
 
     public MybatisEntityFactory(EntityFactory entityFactory) {
         this.entityFactory = entityFactory;

+ 17 - 27
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/main/java/org/hswebframework/web/dao/mybatis/builder/EasyOrmSqlBuilder.java

@@ -18,6 +18,7 @@
 
 package org.hswebframework.web.dao.mybatis.builder;
 
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.beanutils.BeanUtilsBean;
 import org.apache.commons.beanutils.PropertyUtilsBean;
 import org.apache.ibatis.mapping.ResultMap;
@@ -60,6 +61,7 @@ import java.util.concurrent.ConcurrentMap;
  * @author zhouhao
  * @since 2.0
  */
+@Slf4j
 public class EasyOrmSqlBuilder {
 
     public volatile boolean useJpa = false;
@@ -96,31 +98,6 @@ public class EasyOrmSqlBuilder {
         simpleName.put(short.class, "short");
         simpleName.put(char.class, "char");
         simpleName.put(byte.class, "byte");
-//
-//        Dialect.MYSQL.setTermTypeMapper(TermType.eq, supportArray(new EnumDicTermTypeMapper(Dialect.MYSQL, false)));
-//        Dialect.MYSQL.setTermTypeMapper(TermType.in, supportArray(new MysqlEnumDicInTermTypeMapper(false)));
-//        Dialect.MYSQL.setTermTypeMapper(TermType.not, supportArray(new EnumDicTermTypeMapper(Dialect.MYSQL, true)));
-//        Dialect.MYSQL.setTermTypeMapper(TermType.nin, supportArray(new MysqlEnumDicInTermTypeMapper(true)));
-//
-//        Dialect.MYSQL.setTermTypeMapper("ain", supportArray(new MysqlEnumDicInTermTypeMapper(true, true)));
-//        Dialect.MYSQL.setTermTypeMapper("anin", supportArray(new MysqlEnumDicInTermTypeMapper(false, true)));
-//
-//
-//        Dialect.H2.setTermTypeMapper(TermType.eq, supportArray(new EnumDicTermTypeMapper(Dialect.H2, false)));
-//        Dialect.H2.setTermTypeMapper(TermType.in, supportArray(new H2EnumDicInTermTypeMapper(false)));
-//        Dialect.H2.setTermTypeMapper(TermType.not, supportArray(new EnumDicTermTypeMapper(Dialect.H2, true)));
-//        Dialect.H2.setTermTypeMapper(TermType.nin, supportArray(new H2EnumDicInTermTypeMapper(true)));
-//        Dialect.H2.setTermTypeMapper("ain", supportArray(new H2EnumDicInTermTypeMapper(true, true)));
-//        Dialect.H2.setTermTypeMapper("anin", supportArray(new H2EnumDicInTermTypeMapper(false, true)));
-//
-//
-//        Dialect.ORACLE.setTermTypeMapper(TermType.eq, supportArray(new EnumDicTermTypeMapper(Dialect.ORACLE, false)));
-//        Dialect.ORACLE.setTermTypeMapper(TermType.in, supportArray(new OracleEnumDicInTermTypeMapper(false)));
-//        Dialect.ORACLE.setTermTypeMapper(TermType.not, supportArray(new EnumDicTermTypeMapper(Dialect.ORACLE, true)));
-//        Dialect.ORACLE.setTermTypeMapper(TermType.nin, supportArray(new OracleEnumDicInTermTypeMapper(true)));
-//        Dialect.ORACLE.setTermTypeMapper("ain", supportArray(new OracleEnumDicInTermTypeMapper(true, true)));
-//        Dialect.ORACLE.setTermTypeMapper("anin", supportArray(new OracleEnumDicInTermTypeMapper(false, true)));
-
     }
 
     public static String getJavaType(Class type) {
@@ -161,7 +138,19 @@ public class EasyOrmSqlBuilder {
         }
     }
 
+    private String getRealTableName(String tableName) {
+
+        String newTable = DataSourceHolder.tableSwitcher().getTable(tableName);
+
+        if (!tableName.equals(newTable)) {
+            log.debug("use new table [{}] for [{}]", newTable, tableName);
+        }
+        return newTable;
+
+    }
+
     protected RDBTableMetaData createMeta(String tableName, String resultMapId) {
+        tableName = getRealTableName(tableName);
         RDBDatabaseMetaData active = getActiveDatabase();
         String cacheKey = tableName.concat("-").concat(resultMapId);
         Map<String, RDBTableMetaData> cache = metaCache.get(active);
@@ -296,6 +285,7 @@ public class EasyOrmSqlBuilder {
         SqlRender<UpdateParam> render = tableMetaData.getDatabaseMetaData().getRenderer(SqlRender.TYPE.UPDATE);
         return render.render(tableMetaData, param).getSql();
     }
+
     public String buildSelectFields(String resultMapId, String tableName, Object arg) {
         QueryParam param = null;
         if (arg instanceof QueryParam) {
@@ -322,7 +312,7 @@ public class EasyOrmSqlBuilder {
             }
             String cname = columnMetaData.getName();
             if (!cname.contains(".")) {
-                cname = tableName.concat(".").concat(cname);
+                cname = tableMetaData.getName().concat(".").concat(cname);
             }
             boolean isJpa = columnMetaData.getProperty("fromJpa", false).isTrue();
 
@@ -362,7 +352,7 @@ public class EasyOrmSqlBuilder {
                     }
                     String cname = column.getName();
                     if (!cname.contains(".")) {
-                        cname = tableName.concat(".").concat(cname);
+                        cname = tableMetaData.getName().concat(".").concat(cname);
                     }
                     appender.add(encodeColumn(tableMetaData.getDatabaseMetaData().getDialect(), cname), " ", sort.getOrder(), ",");
                 });

+ 4 - 1
hsweb-commons/hsweb-commons-dao/hsweb-commons-dao-mybatis/src/test/java/org/hswebframework/web/dao/crud/TestCrud.java

@@ -4,6 +4,7 @@ import org.apache.ibatis.session.SqlSessionFactory;
 import org.hswebframework.ezorm.core.param.QueryParam;
 import org.hswebframework.ezorm.rdb.executor.SqlExecutor;
 import org.hswebframework.web.commons.entity.param.QueryParamEntity;
+import org.hswebframework.web.datasource.DataSourceHolder;
 import org.hswebframework.web.dict.EnumDict;
 import org.junit.Assert;
 import org.junit.Before;
@@ -67,8 +68,10 @@ public class TestCrud extends AbstractTransactionalJUnit4SpringContextTests {
 
         QueryParamEntity query = new QueryParamEntity();
         //any in
-        query.where("dataTypes$in$any", Arrays.asList(DataType.TYPE1,DataType.TYPE2));
+        query.where("dataTypes$in$any", Arrays.asList(DataType.TYPE1, DataType.TYPE2));
         query.includes("nest.name", "*");
+
+        //  DataSourceHolder.tableSwitcher().use("h_test", "h_test2");
         List<TestEntity> entities = testDao.queryNest(query);
 
 //        testDao.query(entity);

+ 96 - 31
hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/AopDataSourceSwitcherAutoConfiguration.java

@@ -2,11 +2,10 @@ package org.hswebframework.web.datasource;
 
 import org.aopalliance.intercept.MethodInterceptor;
 import org.hswebframework.web.ExpressionUtils;
+import org.hswebframework.web.boost.aop.context.MethodInterceptorContext;
 import org.hswebframework.web.boost.aop.context.MethodInterceptorHolder;
 import org.hswebframework.web.datasource.exception.DataSourceNotFoundException;
-import org.hswebframework.web.datasource.strategy.AnnotationDataSourceSwitchStrategyMatcher;
-import org.hswebframework.web.datasource.strategy.DataSourceSwitchStrategyMatcher;
-import org.hswebframework.web.datasource.strategy.ExpressionDataSourceSwitchStrategyMatcher;
+import org.hswebframework.web.datasource.strategy.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
@@ -19,6 +18,8 @@ import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 import static org.hswebframework.web.datasource.strategy.AnnotationDataSourceSwitchStrategyMatcher.*;
 
@@ -43,8 +44,25 @@ public class AopDataSourceSwitcherAutoConfiguration {
     }
 
     @Bean
-    public SwitcherMethodMatcherPointcutAdvisor switcherMethodMatcherPointcutAdvisor(List<DataSourceSwitchStrategyMatcher> matchers) {
-        return new SwitcherMethodMatcherPointcutAdvisor(matchers);
+    public TableSwitchStrategyMatcher alwaysNoMatchStrategyMatcher() {
+        return new TableSwitchStrategyMatcher() {
+            @Override
+            public boolean match(Class target, Method method) {
+                return false;
+            }
+
+            @Override
+            public Strategy getStrategy(MethodInterceptorContext context) {
+                return null;
+            }
+        };
+    }
+
+    @Bean
+    public SwitcherMethodMatcherPointcutAdvisor switcherMethodMatcherPointcutAdvisor(
+            List<DataSourceSwitchStrategyMatcher> matchers,
+            List<TableSwitchStrategyMatcher> tableSwitcher) {
+        return new SwitcherMethodMatcherPointcutAdvisor(matchers, tableSwitcher);
     }
 
     public static class SwitcherMethodMatcherPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {
@@ -53,45 +71,79 @@ public class AopDataSourceSwitcherAutoConfiguration {
 
         private List<DataSourceSwitchStrategyMatcher> matchers;
 
-        private Map<AnnotationDataSourceSwitchStrategyMatcher.CacheKey, DataSourceSwitchStrategyMatcher> cache = new ConcurrentHashMap<>();
+        private List<TableSwitchStrategyMatcher> tableSwitcher;
+
+        private Map<CachedDataSourceSwitchStrategyMatcher.CacheKey, DataSourceSwitchStrategyMatcher> cache
+                = new ConcurrentHashMap<>();
+        private Map<CachedTableSwitchStrategyMatcher.CacheKey, TableSwitchStrategyMatcher>           tableCache
+                = new ConcurrentHashMap<>();
 
-        public SwitcherMethodMatcherPointcutAdvisor(List<DataSourceSwitchStrategyMatcher> matchers) {
+        public SwitcherMethodMatcherPointcutAdvisor(List<DataSourceSwitchStrategyMatcher> matchers,
+                                                    List<TableSwitchStrategyMatcher> tableSwitcher) {
             this.matchers = matchers;
+            this.tableSwitcher = tableSwitcher;
             setAdvice((MethodInterceptor) methodInvocation -> {
                 CacheKey key = new CacheKey(ClassUtils.getUserClass(methodInvocation.getThis()), methodInvocation.getMethod());
+                CachedTableSwitchStrategyMatcher.CacheKey tableKey = new CachedTableSwitchStrategyMatcher.CacheKey(ClassUtils.getUserClass(methodInvocation.getThis()), methodInvocation.getMethod());
+
                 DataSourceSwitchStrategyMatcher matcher = cache.get(key);
-                if (matcher == null) {
-                    logger.warn("method:{} not support switch datasource", methodInvocation.getMethod());
-                } else {
-                    MethodInterceptorHolder holder = MethodInterceptorHolder.create(methodInvocation);
-                    Strategy strategy = matcher.getStrategy(holder.createParamContext());
-                    if (strategy == null) {
-                        logger.warn("strategy matcher found:{}, but strategy is null!", matcher);
-                    } else {
-                        logger.debug("switch datasource.use strategy:{}", strategy);
-                        if (strategy.isUseDefaultDataSource()) {
-                            DataSourceHolder.switcher().useDefault();
+                TableSwitchStrategyMatcher tableMatcher = tableCache.get(tableKey);
+
+                Consumer<MethodInterceptorContext> before = context -> {
+                };
+
+                if (matcher != null) {
+                    before = before.andThen(context -> {
+                        Strategy strategy = matcher.getStrategy(context);
+                        if (strategy == null) {
+                            logger.warn("strategy matcher found:{}, but strategy is null!", matcher);
                         } else {
-                            String id = strategy.getDataSourceId();
-                            if (id.contains("${")) {
-                                id = ExpressionUtils.analytical(id, holder.getArgs(), "spel");
-                            }
-                            if (!DataSourceHolder.existing(id)) {
-                                if (strategy.isFallbackDefault()) {
-                                    DataSourceHolder.switcher().useDefault();
-                                } else {
-                                    throw new DataSourceNotFoundException(id);
-                                }
+                            logger.debug("switch datasource.use strategy:{}", strategy);
+                            if (strategy.isUseDefaultDataSource()) {
+                                DataSourceHolder.switcher().useDefault();
                             } else {
-                                DataSourceHolder.switcher().use(id);
+                                try {
+                                    String id = strategy.getDataSourceId();
+                                    if (id.contains("${")) {
+                                        id = ExpressionUtils.analytical(id, context.getParams(), "spel");
+                                    }
+                                    if (!DataSourceHolder.existing(id)) {
+                                        if (strategy.isFallbackDefault()) {
+                                            DataSourceHolder.switcher().useDefault();
+                                        } else {
+                                            throw new DataSourceNotFoundException(id);
+                                        }
+                                    } else {
+                                        DataSourceHolder.switcher().use(id);
+                                    }
+                                } catch (RuntimeException e) {
+                                    throw e;
+                                } catch (Exception e) {
+                                    throw new RuntimeException(e.getMessage(), e);
+                                }
                             }
                         }
-                    }
+                    });
+                }
+                if (tableMatcher != null) {
+                    before = before.andThen(context -> {
+                        TableSwitchStrategyMatcher.Strategy strategy = tableMatcher.getStrategy(context);
+                        if (null != strategy) {
+                            logger.debug("switch table. use strategy:{}", strategy);
+                            strategy.getMapping().forEach(DataSourceHolder.tableSwitcher()::use);
+                        } else {
+                            logger.warn("table strategy matcher found:{}, but strategy is null!", matcher);
+                        }
+                    });
                 }
+
+                MethodInterceptorHolder holder = MethodInterceptorHolder.create(methodInvocation);
+                before.accept(holder.createParamContext());
                 try {
                     return methodInvocation.proceed();
                 } finally {
                     DataSourceHolder.switcher().useLast();
+                    DataSourceHolder.tableSwitcher().reset();
                 }
             });
         }
@@ -103,7 +155,20 @@ public class AopDataSourceSwitcherAutoConfiguration {
                     .filter(matcher -> matcher.match(aClass, method))
                     .findFirst()
                     .ifPresent((matcher) -> cache.put(key, matcher));
-            return cache.containsKey(key);
+
+
+            boolean datasourceMatched = cache.containsKey(key);
+            boolean tableMatched = false;
+            if (null != tableSwitcher) {
+                CachedTableSwitchStrategyMatcher.CacheKey tableCacheKey = new CachedTableSwitchStrategyMatcher.CacheKey(aClass, method);
+                tableSwitcher.stream()
+                        .filter(matcher -> matcher.match(aClass, method))
+                        .findFirst()
+                        .ifPresent((matcher) -> tableCache.put(tableCacheKey, matcher));
+                tableMatched = tableCache.containsKey(tableCacheKey);
+            }
+
+            return datasourceMatched || tableMatched;
         }
     }
 }

+ 65 - 0
hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/strategy/CachedTableSwitchStrategyMatcher.java

@@ -0,0 +1,65 @@
+package org.hswebframework.web.datasource.strategy;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.web.boost.aop.context.MethodInterceptorContext;
+import org.springframework.util.ClassUtils;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+@Slf4j
+public abstract class CachedTableSwitchStrategyMatcher implements TableSwitchStrategyMatcher {
+    static Map<CacheKey, Strategy> cache = new ConcurrentHashMap<>();
+
+    public abstract Strategy createStrategyIfMatch(Class target, Method method);
+
+    @Override
+    public boolean match(Class target, Method method) {
+        Strategy strategy = createStrategyIfMatch(target, method);
+        if (null != strategy) {
+            if (log.isDebugEnabled()) {
+                log.debug("create table switcher strategy:{} for method:{}", strategy, method);
+            }
+            CacheKey cacheKey = new CacheKey(target, method);
+            cache.put(cacheKey, strategy);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public Strategy getStrategy(MethodInterceptorContext context) {
+        Method method = context.getMethod();
+        Class target = ClassUtils.getUserClass(context.getTarget());
+        return cache.get(new CacheKey(target, method));
+    }
+
+    @AllArgsConstructor
+    public static class CacheKey {
+
+        private Class target;
+
+        private Method method;
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof CacheKey)) {
+                return false;
+            }
+            CacheKey target = ((CacheKey) obj);
+            return target.target == this.target && target.method == method;
+        }
+
+        public int hashCode() {
+            int result = this.target != null ? this.target.hashCode() : 0;
+            result = 31 * result + (this.method != null ? this.method.hashCode() : 0);
+            return result;
+        }
+    }
+}

+ 8 - 5
hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/strategy/ExpressionDataSourceSwitchStrategyMatcher.java

@@ -13,11 +13,14 @@ import java.util.Map;
 /**
  * 表达式方式切换数据源,在配置文件中设置:
  * <pre>
- *     hsweb:
- *        datasource:
- *           switcher:
- *              org.hswebframework.**.*Service.select*:
- *                  data-source-id: test1
+ *    hsweb:
+ *      datasource:
+ *          switcher:
+ *              test: # 只是一个标识
+ *              # 拦截类和方法的表达式
+ *              expression: org.hswebframework.**.*Service.find*
+ *              # 使用数据源
+ *              data-source-id: read_db
  * </pre>
  *
  * @author zhouhao

+ 60 - 0
hsweb-datasource/hsweb-datasource-api/src/main/java/org/hswebframework/web/datasource/strategy/TableSwitchStrategyMatcher.java

@@ -0,0 +1,60 @@
+package org.hswebframework.web.datasource.strategy;
+
+import org.hswebframework.web.boost.aop.context.MethodInterceptorContext;
+import org.hswebframework.web.datasource.DynamicDataSource;
+import org.hswebframework.web.datasource.exception.DataSourceNotFoundException;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * 数据库表切换策略,可通过此接口来自定义表切换的方式
+ *
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+public interface TableSwitchStrategyMatcher {
+
+    /**
+     * 匹配类和方法,返回是否需要进行表切换
+     *
+     * @param target 类
+     * @param method 方法
+     * @return 是否需要进行数据源切换
+     */
+    boolean match(Class target, Method method);
+
+    /**
+     * 获取表切换策略
+     *
+     * @param context aop上下文
+     * @return 切换策略
+     */
+    Strategy getStrategy(MethodInterceptorContext context);
+
+    /**
+     * 表切换策略
+     */
+    interface Strategy {
+        /**
+         * @return 表映射关系
+         * @see org.hswebframework.web.datasource.switcher.TableSwitcher#getTable(String)
+         */
+        Map<String, String> getMapping();
+
+        static Strategy of(Map<String, String> mapping) {
+            return () -> mapping;
+        }
+
+        static Strategy of(Supplier<Map<String, String>> supplier) {
+            return of(supplier.get());
+        }
+
+        static Strategy single(String source, String target) {
+            return of(() -> Collections.singletonMap(source, target));
+        }
+    }
+
+}