當數據量比較多時,放在一個表中的時候會影響查詢效率;或者數據的時效性只是當月有效的時候;這時咱們就會涉及到數據庫的分表操做了。固然,你也可使用比較完善的第三方組件:sharding-jdbc來實現;可是你使用後會發現,貌似對oracle的兼容性不是很好。因此最後我仍是決定使用Mybatis攔截器對數據庫進行水平分表。java
以上4個都是Configuration的方法,這些方法在MyBatis的一個操做(新增,刪除,修改,查詢)中都會被執行到,執行的前後順序是Executor,ParameterHandler,ResultSetHandler,StatementHandler。mysql
<dependencies> <!-- SpringBoot啓動依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.0.4.RELEASE</version> </dependency> <!--添加Web依賴:有先後端交互就須要用到,即有controller中的請求 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion><!-- 採用的SLf4J 去除衝突 --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- 添加日誌框架依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <!--添加MySql依賴 --> <!-- <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.42</version> </dependency> --> <!-- 添加Oracle依賴 --> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.3</version> </dependency> <!-- 添加druid依賴: 一個用來鏈接數據庫的連接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!--添加Mybatis依賴 配置mybatis的一些初始化的東西--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <!-- 添加lombok依賴 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-classworlds</artifactId> <version>2.5.1</version> </dependency> <!--生成代碼插件--> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.2</version> <type>jar</type> </dependency> </dependencies>
package com.java.mmzsit.framework.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 分表規則 * @author mmzsit * */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface TableSplitRule { public String tableName(); //暫時只支持單參數 public String paramName(); public String targetName(); }
package com.java.mmzsit.framework.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 分表策略攔截 * @author tianwei * */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface TableSplitTarget { boolean interFale() default true; //分表規則 public TableSplitRule[] rules(); }
package com.java.mmzsit.framework.interceptor; import com.java.mmzsit.framework.annotation.TableSplitRule; import com.java.mmzsit.framework.annotation.TableSplitTarget; import com.java.mmzsit.framework.mybatisStrategy.strategy.Strategy; import com.java.mmzsit.framework.mybatisStrategy.StrategyManager; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.DefaultReflectorFactory; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import org.springframework.beans.factory.annotation.Autowired; import java.lang.reflect.Field; import java.sql.Connection; import java.util.Map; import java.util.Properties; /** * @author :mmzsit * @description: * @date :2019/6/14 10:10 */ @Slf4j(topic="策略分表攔截器【TableSplitInterceptor】") @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class,Integer.class }) }) public class TableSplitInterceptor implements Interceptor { @Autowired StrategyManager strategyManager; @Override public Object intercept(Invocation invocation) throws Throwable { log.info("進入mybatisSql攔截器:===================="); StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); MetaObject metaStatementHandler = MetaObject.forObject(statementHandler,SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory()); Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject"); doSplitTable(metaStatementHandler,parameterObject); // 傳遞給下一個攔截器處理 return invocation.proceed(); } @Override public Object plugin(Object arg0) { //System.err.println(arg0.getClass()); if (arg0 instanceof StatementHandler) { return Plugin.wrap(arg0, this); } else { return arg0; } } @Override public void setProperties(Properties arg0) { } private void doSplitTable(MetaObject metaStatementHandler,Object param) throws ClassNotFoundException{ String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql"); if (originalSql != null && !originalSql.equals("")) { log.info("分表前的SQL:"+originalSql); MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); String id = mappedStatement.getId(); String className = id.substring(0, id.lastIndexOf(".")); Class<?> classObj = Class.forName(className); // 根據配置自動生成分表SQL TableSplitTarget tableSplit = classObj.getAnnotation(TableSplitTarget.class); if(tableSplit==null||!tableSplit.interFale()) { return ; } TableSplitRule[] rules = tableSplit.rules(); if (rules != null && rules.length>0) { String convertedSql= null; // StrategyManager可使用ContextHelper策略幫助類獲取,本次使用注入 for(TableSplitRule rule : rules) { Strategy strategy = null; if(rule.targetName()!=null&&!rule.targetName().isEmpty()) { strategy = strategyManager.getStrategy(rule.targetName()); } if(!rule.paramName().isEmpty()&&!rule.tableName().isEmpty()) { String paramValue = getParamValue(param, rule.paramName()); //System.err.println("paramValue:"+paramValue); //獲取 參數 String newTableName = strategy.returnTableName(rule.tableName(), paramValue); try { convertedSql = originalSql.replaceAll(rule.tableName(),newTableName ); } catch (Exception e) { e.printStackTrace(); } } } log.info("新sql是:" + convertedSql); metaStatementHandler.setValue("delegate.boundSql.sql",convertedSql); } } } public String getParamValue(Object obj,String paramName) { if(obj instanceof Map) { return (String) ((Map) obj).get(paramName); } Field[] fields = obj.getClass().getDeclaredFields(); for(Field field : fields) { field.setAccessible(true); //System.err.println(field.getName()); if(field.getName().equalsIgnoreCase(paramName)) { try { return (String) field.get(obj); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } return null; } }
package com.java.mmzsit.framework.mybatisStrategy.strategy; /** * 分表策略服務接口 * @author mmzsit * */ public interface Strategy { /** * 傳入表名 和分表參數 * @param tableName * @param splitParam * @return */ String returnTableName(String tableName,String splitParam); }
package com.java.mmzsit.framework.mybatisStrategy.strategy.impl; import com.java.mmzsit.framework.mybatisStrategy.framework.util.DateUtil; import com.java.mmzsit.framework.mybatisStrategy.strategy.Strategy; import java.text.ParseException; /** * @author :mmzsit * @description:按月分表策略 * @date :2019/6/13 10:29 */ public class YYYYMM01Strategy implements Strategy { @Override public String returnTableName(String tableName, String param) { try { // 結果相似 20190601 return tableName+"_"+ DateUtil.get_MM01Str(param); } catch (ParseException e) { e.printStackTrace(); return tableName; } } }
package com.java.mmzsit.framework.mybatisStrategy; import com.java.mmzsit.framework.mybatisStrategy.strategy.Strategy; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author :mmzsit * @description: * @date :2019/6/13 10:28 */ public class StrategyManager { public static final String _YYYYMM01 = "YYYYMM01"; //策略名稱 public static final String _YYYYMMDD = "YYYYMMDD"; public static final String _YYYYMM = "YYYYMM"; private Map<String,Strategy> strategies = new ConcurrentHashMap<String,Strategy>(10); /** * 向管理器中添加策略 * @param strategyName * @param strategy */ public void addStrategy(String strategyName,Strategy strategy) { strategies.put(strategyName, strategy); } public Strategy getStrategy(String key){ return strategies.get(key); } public Map<String, Strategy> getStrategies() { return strategies; } public void setStrategies(Map<String, String> strategies) { for(Map.Entry<String, String> entry : strategies.entrySet()){ try { this.strategies.put(entry.getKey(),(Strategy)Class.forName(entry.getValue()).newInstance()); } catch (Exception e) { System.out.println("實例化策略出錯"+e); } } } }
很簡單,在你須要進行分表的dao層添加以下註解便可:git
@TableSplitTarget(rules={@TableSplitRule(tableName="TESTDATAS",paramName="updatedate",targetName=StrategyManager._YYYYMM01)})
0、建表語句github
CREATE TABLE TESTDATAS_20190701 ( ID NUMBER(4) NOT NULL, NAME NVARCHAR2(30), AGE NVARCHAR2(2), INFORMATION NVARCHAR2(30), UPDATEDATE NVARCHAR2(14), PRIMARY KEY (ID) );
一、啓動項目
二、請求地址:http://localhost:8001/add
三、控制打印信息:web
2019-07-12 09:24:45.937 INFO 5548 --- [nio-8001-exec-1] 策略分表攔截器【TableSplitInterceptor】 : 進入mybatisSql攔截器:==================== 2019-07-12 09:24:45.947 INFO 5548 --- [nio-8001-exec-1] 策略分表攔截器【TableSplitInterceptor】 : 分表前的SQL:insert into TESTDATAS (ID, NAME, AGE, INFORMATION, UPDATEDATE) values (?, ?, ?, ?, ?) 2019-07-12 09:24:45.964 INFO 5548 --- [nio-8001-exec-1] 策略分表攔截器【TableSplitInterceptor】 : 新sql是:insert into TESTDATAS_20190501 (ID, NAME, AGE, INFORMATION, UPDATEDATE) values (?, ?, ?, ?, ?) 2019-07-12 09:24:46.140 INFO 5548 --- [nio-8001-exec-1] 數據插入分表【AddDataImpl】 : 插入數據成功 2019-07-12 09:24:46.141 INFO 5548 --- [nio-8001-exec-1] 策略分表攔截器【TableSplitInterceptor】 : 進入mybatisSql攔截器:==================== 2019-07-12 09:24:46.149 INFO 5548 --- [nio-8001-exec-1] 策略分表攔截器【TableSplitInterceptor】 : 分表前的SQL:insert into TESTDATAS (ID, NAME, AGE, INFORMATION, UPDATEDATE) values (?, ?, ?, ?, ?) 2019-07-12 09:24:46.150 INFO 5548 --- [nio-8001-exec-1] 策略分表攔截器【TableSplitInterceptor】 : 新sql是:insert into TESTDATAS_20190601 (ID, NAME, AGE, INFORMATION, UPDATEDATE) values (?, ?, ?, ?, ?) 2019-07-12 09:24:46.190 INFO 5548 --- [nio-8001-exec-1] 數據插入分表【AddDataImpl】 : 插入數據成功 2019-07-12 09:24:46.191 INFO 5548 --- [nio-8001-exec-1] 策略分表攔截器【TableSplitInterceptor】 : 進入mybatisSql攔截器:==================== 2019-07-12 09:24:46.191 INFO 5548 --- [nio-8001-exec-1] 策略分表攔截器【TableSplitInterceptor】 : 分表前的SQL:insert into TESTDATAS (ID, NAME, AGE, INFORMATION, UPDATEDATE) values (?, ?, ?, ?, ?) 2019-07-12 09:24:46.192 INFO 5548 --- [nio-8001-exec-1] 策略分表攔截器【TableSplitInterceptor】 : 新sql是:insert into TESTDATAS_20190701 (ID, NAME, AGE, INFORMATION, UPDATEDATE) values (?, ?, ?, ?, ?) 2019-07-12 09:24:46.204 INFO 5548 --- [nio-8001-exec-1] 數據插入分表【AddDataImpl】 : 插入數據成功
四、查看數據庫信息
spring
其實這也算是對mybatis底層的一種使用了,由於對其須要執行的mysql語句進行了攔截,而後進行從新拼接後才繼續執行操做的。
代碼已經提交github:springboot-mybatisInterceptor
sql