轉自 :https://www.jianshu.com/p/e6d9afd562b2
業務場景
- 公司APP需要將主模塊拆分成多個APP給代理商運營
- 不同代理商代理的APP產生的數據需根據對應公司進行區分
- 公司總數據庫需同步管理所有代理商運營的數據
設計思想
- 設計在最小修改原則保證產品業務無大變動只對表進行新增COMPAN_ID字段進行區分
- 綜合以上需求場景產生的思路決定采用低耦合、高復用進行架構設計
- 通過MyBatis攔截器Sql進行處理,采用類自定義注解+反射的形式
代碼實現
創建自定義注解 HmwCompanyAnnotation
@Target({ElementType.METHOD,ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface HmwCompanyAnnotation { /** * 公司ID(暫未啟用) * @return */ String CompanyId() default ""; /** * 別名(針對復雜查詢備用處理屬性) * @return */ String Alias() default ""; }
MyBatis的Mapper的實現示例
接口方法上添加 自定義注解@HmwCompanyAnnotation()
@Mapper public interface SmsCodeDao { /** * 新增 * * @param smsCode * @return */ @Options(useGeneratedKeys = true, keyProperty = "smsCodeId" , keyColumn = "SMS_CODE_ID") @Insert("insert into hmw_sms_code(SMS_TYPE, ... 省略字段..., COMPANY_ID) " + " values(#{smsType},...省略字段... ,#{companyId})") int save(SmsCode smsCode); /** * 查詢用戶每天發送的驗證碼數量 * * @param sendTel 手機號 * @return */ @HmwCompanyAnnotation() @Select(" select " + " count(0) as count_num " + " from hmw_sms_code" + " where " + " SEND_TEL = #{sendTel} " + " and TO_DAYS(NOW()) = TO_DAYS(SEND_TIME) ") Long findTelTodayCount(String sendTel ); /** * 根據短信類型查詢是否已使用獲取已過期 * * @param sendTel 手機號 * @param sendCode 驗證碼 * @param smsType 短信類型 * @param nowTime 當前服務器時間(請用服務器New出來的時間 不要用NOW()) * @return */ @HmwCompanyAnnotation() @Select(" select " + " count(0) as count_num " + " from " + " hmw_sms_code " + " where " + " SEND_TEL = #{sendTel,jdbcType=VARCHAR} " + " and SEND_CODE = #{sendCode,jdbcType=VARCHAR} " + " and SMS_STATE = 'N' " + " and SMS_TYPE = #{smsType,jdbcType=VARCHAR} " + " and SEND_TIME >= DATE_SUB(#{sendTime},INTERVAL 5 MINUTE) ") Long findSmsCodeByTel(String sendTel , String sendCode , String smsType , Date nowTime); }
MyBatis 攔截器具體實現
import com.hmw.annotation.HmwCompanyAnnotation; import com.hmw.base.Const; import com.hmw.utils.common.ReflectHelper; import org.apache.ibatis.executor.statement.RoutingStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.sql.Connection; import java.util.Properties; /** * 功能:Mybatis 攔截器 * 說明:攔截SQL執行前的語句 根據自定義注解對方法進行公司查詢 * * 開發:Bruce.Liu Create by 2018-03-12 20:15 */ @Component @Intercepts( { @Signature( type = StatementHandler.class, method = "prepare", args = { Connection.class , Integer.class } ) } ) public class CompanySqlInterceptor implements Interceptor { @Value("${base.config.companyId}") private String COMPANY_ID; @Override public Object intercept(Invocation invocation) throws Throwable { try{ if(invocation.getTarget() instanceof RoutingStatementHandler){ RoutingStatementHandler statementHandler = (RoutingStatementHandler)invocation.getTarget(); StatementHandler delegate = (StatementHandler) ReflectHelper.getFieldValue(statementHandler, "delegate"); BoundSql boundSql = delegate.getBoundSql(); MappedStatement mappedStatement = (MappedStatement) ReflectHelper.getFieldValue(delegate, "mappedStatement"); Class<?> classType = Class.forName(mappedStatement.getId().substring(0,mappedStatement.getId().lastIndexOf("."))); String mName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1 ,mappedStatement.getId().length()); for(Method method : classType.getDeclaredMethods()){ if(method.isAnnotationPresent(HmwCompanyAnnotation.class) && mName.equals(method.getName()) ) { HmwCompanyAnnotation companyAnnotation = method.getAnnotation(HmwCompanyAnnotation.class); String sql = boundSql.getSql(); if( mappedStatement.getSqlCommandType().toString().equals(Const.SELECT) || mappedStatement.getSqlCommandType().toString().equals(Const.UPDATE) || mappedStatement.getSqlCommandType().toString().equals(Const.DELETE )){ if(companyAnnotation.Alias().equals("")){ sql = sql + " and COMPANY_ID = '"+ COMPANY_ID +"' "; } else { sql = sql + " and "+ companyAnnotation.Alias()+".COMPANY_ID = '" + COMPANY_ID +"' "; } } else if( mappedStatement.getSqlCommandType().toString().equals(Const.INSERT)){ // System.err.println("Insert 實現已移到Service層處理了"); } //System.err.println("執行后SQL:"+sql); ReflectHelper.setFieldValue(boundSql, "sql", sql); break; } } } } catch (Exception e){ e.printStackTrace(); } return invocation.proceed(); } @Override public Object plugin(Object target) { if (target instanceof StatementHandler) { return Plugin.wrap(target,this); } else { return target; } } @Override public void setProperties(Properties properties) { } }
- 利用MyBatis攔截器@Signature對底層StatementHandler.class的 prepare方法進行攔截處理
- 在執行JDBC的SQL腳本之前對sql進行統一拼接處理
- 自定義注解標示出需要進行攔截處理的方法,通過反射機制獲取方法以及SqlCommonType 然后作出相應的邏輯處理
作者:52HzBoo
鏈接:https://www.jianshu.com/p/e6d9afd562b2