Mybatis 플러그 인 시스템 에 정통 합 니 다 (중간 부품 과 의 사고)

머리말
이것 은 my batis 시리즈 에 정통 한 마지막 글 일 것 입 니 다. 플러그 인 시스템 이 설명 한 후에 여러분 이 모 르 는 부분 이 있 으 면 같이 토론 할 수 있 습 니 다. 지난 블 로그 에서 Mybatis 의 Configuration 설정 체계 에 정통 하여 차단기 체인 을 이야기 할 때 작은 편집 자 는 주로 플러그 인 체 계 를 맞 추고 네 곳 에서 해당 하 는 강 화 를 했다 고 말 한 적 이 있 습 니 다.그 는 아직 op 과 조금 다 르 고 더욱 간결 하고 명료 하 죠? 그래서 오늘 은 마 이 바 티 스 의 플러그 인 시스템 을 보 여 드 리 겠 습 니 다.
my batis 플러그 인의 4 대 구성 요소 입구
플러그 인 메커니즘 은 MyBatis 기 존 시스템 을 확장 하기 위해 제 공 된 입구 입 니 다.밑바닥 은 동적 대 리 를 통 해 실현된다.프 록 시 차단 이 가능 한 인 터 페 이 스 는 네 개 입 니 다.
  • Executor: 실행 기
  • StatementHandler: JDBC 프로세서
  • 매개 변수 핸들 러: 매개 변수 프로세서
  • ResultSetHandler: 결과 집합 프로세서
  • 이 네 개의 인 터 페 이 스 는 시작 인터페이스 에서 SQL 성명, 파라미터 처리, 결과 집합 처리 까지 의 모든 절 차 를 포함한다.인터페이스 에서 어떤 방법 이 든 차단 하고 방법 이 원래 의 속성 과 행 위 를 바 꿀 수 있다.하지만 이 는 자칫 마 이 배 티 스 핵심 논 리 를 훼손 할 수 있다 는 점 을 모 르 는 매우 위험한 행동 이다.그래서 플러그 인 을 사용 하기 전에 MyBatis 내부 메커니즘 을 분명히 해 야 합 니 다.
    소스 코드 읽 기 가 왜 상기 네 개의 입구 인지 우 리 는 눈 으로 확인 할 수 있 습 니 다. Configuration 에서 방법:
    //Executor 
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
         
    	//              
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
         
          executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
         
          executor = new ReuseExecutor(this, transaction);
        } else {
         
          executor = new SimpleExecutor(this, transaction);
        }
        if (cacheEnabled) {
         
          executor = new CachingExecutor(executor);
        }
        //       interceptorChain          plugin  
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }
      //ParameterHandler 
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
         
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
      }
    //ResultSetHandler 
      public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
          ResultHandler resultHandler, BoundSql boundSql) {
         
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
      }
    //StatementHandler 
      public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
         
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
    

    중간물 실현 사고.
    하나의 프레임 워 크 를 확장 하려 면 좋 은 디자인 사고방식 과 디자인 모델 이 필요 하 다. 이것 은 업무 코드 를 쓰 는 것 보다 더욱 도전 적 이 고 경험 이 없 으 면 상세 하고 전면적으로 생각 하기 어렵다.여기 서 편집장 은 자신의 사고 와 총 결 된 경험 을 설명 한다.
  • 용이 성: 원래 의 구조 나 원래 의 업무 논 리 를 확장 할 때 우 리 는 제3자 호출 이 충분 하고 간단 하 다 는 것 을 확보 해 야 한다. 주 해 를 추가 하거나 가능 한 한 간단 한 파 라 메 터 를 가 져 가면 비교적 어 려 운 논 리 를 실현 할 수 있 고 어 려 운 논 리 를 호출 자 에 게 폐 를 끼 치지 않 는 것 이 좋다.또 하 나 는 설정 을 수정 할 필요 가 없다 는 것 이다.
  • 원 업무 의 모든 장면 에 대한 지원: 원 업무 나 원 코드 의 논리 에 영향 을 주어 서 는 안 된다. 즉, 이전의 버 전 을 호 환 하고 확장 할 수 있다 는 점 은 매우 어렵다. 그 전에 어떤 장면 이 든 지 지원 해 야 한다. 특정한 몇 장면 만 지원 할 수 있다 는 것 이 아니 라 지원 해 야 한다.
  • 사용자 에 게 친절 합 니 다. 이것 은 바로 코드 의 제약 과 규범 입 니 다. 가능 하 다 면 사용자 의 습관 을 유도 할 수 있 습 니 다.중간 에 실 수 를 했 을 때 는 반드시 잘못 을 보고 해 야 하 며, 세 사람 이 무슨 잘못 인지 알 수 있 도록 해 야 한다.
  • 코드 0 침입: 이것 은 매우 어 려 운 것 입 니 다. 설정 을 수정 하거나 3 자 jar 가방 만 도입 하면 쥐 도 새 도 모 르 게 강력 한 기능 을 실현 할 수 있 습 니 다. 예 를 들 어 분포 식 호출 체인 의 실현 입 니 다.

  • 이상 은 작은 편집 사고의 몇 가지 점 입 니 다. 사실은 my batis 의 플러그 인 실현 도 중간 부품 의 실현 에 해당 합 니 다. 여러분 들 이 얻 기 를 바 랍 니 다.
    페이지 별 구현
    먼저 마 이 바 티 스 의 plugin 류 를 테스트 합 니 다.
    public class PluginTest {
         
        @Test
        public void testMyPlugin(){
         
            MyPlugin myPlugin = msg -> msg + " MyPlugin";
            Interceptor interceptor = new MyPluginInterceptor();
            MyPlugin wrap = (MyPlugin)Plugin.wrap(myPlugin, interceptor);
            System.out.println(wrap.wrappedString("hello"));
        }
    
        public interface MyPlugin{
         
    
            String wrappedString(String msg);
        }
    
        @Intercepts({
         
                @Signature(type = MyPlugin.class, method = "wrappedString", args = {
         String.class})})
        public static  class MyPluginInterceptor implements Interceptor {
         
    
    
            @Override
            public Object intercept(Invocation invocation) throws Throwable {
         
                System.out.println("intercept");
                return invocation.proceed();
            }
    
            @Override
            public Object plugin(Object target) {
         
                return null;
            }
    
            @Override
            public void setProperties(Properties properties) {
         
    
            }
        }
    }
    

    테스트 결과:
    intercept
    hello MyPlugin
    

    여기 서 plugin 의 소스 코드 를 먼저 보 세 요.
    public class Plugin implements InvocationHandler {
         
    
      private final Object target;
      private final Interceptor interceptor;
      private final Map<Class<?>, Set<Method>> signatureMap;
    
      private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
         
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
      }
      //  
      public static Object wrap(Object target, Interceptor interceptor) {
         
      	//      map  
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        //          
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
         
        	//    
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
        }
        return target;
      }
     //    
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         
        try {
         
          Set<Method> methods = signatureMap.get(method.getDeclaringClass());
          if (methods != null && methods.contains(method)) {
         
          	//            
            return interceptor.intercept(new Invocation(target, method, args));
          }
          //               
          return method.invoke(target, args);
        } catch (Exception e) {
         
          throw ExceptionUtil.unwrapThrowable(e);
        }
      }
     //    @Intercepts({
         
     //          @Signature(type = MyPlugin.class, method = "wrappedString", args = {String.class})})
     //  
      private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
         
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        // issue #251
        if (interceptsAnnotation == null) {
         
          throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
        }
        //        @Signature,     map,key     ,values        set   
        Signature[] sigs = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
        for (Signature sig : sigs) {
         
          Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
          try {
         
            Method method = sig.type().getMethod(sig.method(), sig.args());
            methods.add(method);
          } catch (NoSuchMethodException e) {
         
            throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
          }
        }
        return signatureMap;
      }
     //       
      private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
         
        Set<Class<?>> interfaces = new HashSet<>();
        while (type != null) {
         
          for (Class<?> c : type.getInterfaces()) {
         
            if (signatureMap.containsKey(c)) {
         
              interfaces.add(c);
            }
          }
          type = type.getSuperclass();
        }
        return interfaces.toArray(new Class<?>[interfaces.size()]);
      }
    
    }
    

    Interceptor 의 소스 코드
    public interface Interceptor {
         
    
      Object intercept(Invocation invocation) throws Throwable;
    
      default Object plugin(Object target) {
         
        return Plugin.wrap(target, this);
      }
    
      default void setProperties(Properties properties) {
         
        // NOP
      }
    
    }
    

    위 에서 보 셨 듯 이 Plugin, Interceptor 의 역할 을 알 고 계 시 죠? 저 희 는 사실 페이지 플러그 인 을 만 드 는 것 은 Interceptor 인 터 페 이 스 를 실현 하고 설정 하 는 것 입 니 다.
    간이 판 페이지 플러그 인 원리
    페이지 플러그 인 원 리 는 먼저 페이지 클래스 를 설정 합 니 다. total, size, index 3 개의 속성 을 포함 하고 Mapper 인터페이스 에서 이 매개 변 수 는 자동 페이지 논 리 를 실행 해 야 한 다 는 것 을 표시 합 니 다.전체 실현 절 차 는 3 개 를 포함한다.
  • 페이지 조건 만족 여 부 를 검사 합 니 다
  • 현재 조회 의 총 줄 수 를 자동 으로 구 합 니 다
  • 기 존의 SQL 문 구 를 수정 하고 limit 키 워드 를 추가 합 니 다.

  • 페이지 종류:
    public class Page {
         
    
        private Integer pageNum;
    
        private Integer pageSize;
    
        private Integer total;
    
        public Integer getPageNum() {
         
            return pageNum;
        }
    
        public void setPageNum(Integer pageNum) {
         
            this.pageNum = pageNum;
        }
    
        public Integer getPageSize() {
         
            return pageSize;
        }
    
        public void setPageSize(Integer pageSize) {
         
            this.pageSize = pageSize;
        }
    
        public Integer getTotal() {
         
            return total;
        }
    
        public void setTotal(Integer total) {
         
            this.total = total;
        }
    }
    

    PageInterceptor 클래스
    @Intercepts(@Signature(type = StatementHandler.class,
            method = "prepare", args = {
         Connection.class, Integer.class}))
    public class PageInterceptor implements Interceptor {
         
        @Override
        @SuppressWarnings("unchecked")
        public Object intercept(Invocation invocation) throws Throwable {
         
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            BoundSql boundSql = statementHandler.getBoundSql();
            Object parameterObject = boundSql.getParameterObject();
            Page page = null;
            if (parameterObject instanceof Page) {
         
                page = (Page) parameterObject;
            } else if (parameterObject instanceof Map) {
         
                page = (Page) ((Map) parameterObject).values().stream().
                        filter(item -> item instanceof Page).findFirst().orElse(null);
            }
            if (page != null) {
         
                int total = getTotalSize(invocation,statementHandler);
                page.setTotal(total);
                String limitSql = String.format("%s limit %s , %s",boundSql.getSql(),page.getPageNum(),page.getPageSize());
                SystemMetaObject.forObject(boundSql).setValue("sql",limitSql);
            }
            return invocation.proceed();
        }
    
        private int getTotalSize(Invocation invocation, StatementHandler statementHandler) throws SQLException {
         
            int count = 0;
            BoundSql boundSql = statementHandler.getBoundSql();
            String countSql = String.format("select count(*) from (%s) as _page",boundSql.getSql());
            Connection connection = (Connection) invocation.getArgs()[0];
            PreparedStatement preparedStatement = connection.prepareStatement(countSql);
            statementHandler.getParameterHandler().setParameters(preparedStatement);
            ResultSet resultSet = preparedStatement.executeQuery();
            if(resultSet.next()){
         
                 count = resultSet.getInt(1);
            }
            resultSet.close();
            preparedStatement.close();;
    
            return count;
        }
    
        @Override
        public Object plugin(Object target) {
         
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
         
    
        }
    }
    

    테스트 클래스:
    @Before
        public void init() throws SQLException {
         
            //      
            SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
            //   XML        
            factory = factoryBuilder.build(ExecutorTest.class.getResourceAsStream("/mybatis-config.xml"));
            factory.getConfiguration().addInterceptor(new PageInterceptor());
            sqlSession = factory.openSession();
    
        }
    @Test
        public void selectByPage() {
         
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            Page page = new Page();
            page.setPageNum(1);
            page.setPageSize(10);
            List<User> users = mapper.selectByPage(page);
            System.out.println("   :" + page.getTotal() + ",      :" + users.size());
        }
    

    그리고 그 sql 은 사실 select * from users 입 니 다.
    <select id="selectByPage" resultMap="result_user">
            select * from users
        select>
    

    결과:
    22:39:40,562 DEBUG selectByPage:54 - ==>  Preparing: select count(*) from (select * from users) as _page 
    22:39:40,601 DEBUG selectByPage:54 - ==> Parameters: 
    22:39:40,631 DEBUG selectByPage:54 - ==>  Preparing: select * from users limit 1 , 10 
    22:39:40,631 DEBUG selectByPage:54 - ==> Parameters: 
    22:39:40,641 DEBUG selectByPage:54 - <==      Total: 1012,      :10
    

    이것 은 분명 완선 되 지 않 을 것 입 니 다. 여러분 은 my batis - plus 의 페이지 별 실현 이나 PageHelper 의 실현 을 볼 수 있 습 니 다. 물론 원 리 를 파악 하 는 것 이 가장 중요 합 니 다.
    다른 플러그 인의 확장
    sql 모니터링: sql 소모 시간 통계, sql 호출 횟수 통계, sql 로그 등.
    작은 매듭
    여러분 은 인터넷 에서 많은 플러그 인 을 볼 수 있 습 니 다. 사실은 위 에서 말 한 원 리 를 바탕 으로 각 구성 요소 에 대한 확장 입 니 다. 여러분 이 관심 이 있 으 면 주석 연 구 를 할 수 있 습 니 다.
    총결산
    한 달 여 동안 의 학습 총화 에 들 어가 면 작은 편 의 my batis 여행 이 일 단락 되 었 고 끝 이 났 습 니 다. 그리고 시간 이 있 으 면 my batis - plus 가 어떻게 확장 되 었 는 지 설명 하려 고 합 니 다. 그리고 my batis 의 간단 한 sql 조회 패키지 도 my batis 의 플러그 인 시스템 입 니 다.마지막 으로 소 편 과 소 편 문장 을 읽 는 여러분 께 감 사 드 립 니 다. 더욱 분발 하 세 요. 우 리 는 다음 기 에 새로운 내용 을 이어서 이야기 하 겠 습 니 다.

    좋은 웹페이지 즐겨찾기