MyBatis 차단기 의 원리 와 사용

25602 단어 MyBatis차단기
1.차단 대상 과 인터페이스 구현 예시
        마 이 배 티 스 차단 기 는 다 오 가 DB 중간 까지 추가 처리 하 는 역할 을 한다.대부분의 경우 my batis 의 xml 설정 sql 을 통 해 원 하 는 DB 작업 효 과 를 얻 을 수 있 지만 유사 하거나 같은 조회 조건 이나 조회 요구 가 존재 합 니 다.이런 것들 은 차단기 의 실현 을 통 해 개발 효율 을 향상 시 킬 수 있 습 니 다.예 를 들 어 페이지,삽입 과 업데이트 시간/사람,데이터 권한,SQL 모니터링 로그 등 입 니 다.
Mybatis 는 네 가지 대상 이 Executor,StatementHandler,PameterHandler 와 ResultSetHandler 를 차단 하 는 것 을 지원 합 니 다
  • Executor:실행 기 를 차단 하 는 방법
  • StatementHandler:Sql 문법 구축 을 차단 하 는 처리.
  • ParameterHandler:차단 매개 변수 처리.
  • ResultHandler:결과 집합 차단 처리.
  • 
    public interface Executor {
        ResultHandler NO_RESULT_HANDLER = null;
        int update(MappedStatement var1, Object var2) throws SQLException;
        <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;
        <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
        <E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;
        List<BatchResult> flushStatements() throws SQLException;
        void commit(boolean var1) throws SQLException;
        void rollback(boolean var1) throws SQLException;
        CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);
        boolean isCached(MappedStatement var1, CacheKey var2);
        void clearLocalCache();
        void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);
        Transaction getTransaction();
        void close(boolean var1);
        boolean isClosed();
        void setExecutorWrapper(Executor var1);
    }
    public interface StatementHandler {
        Statement prepare(Connection var1, Integer var2) throws SQLException;
        void parameterize(Statement var1) throws SQLException;
        void batch(Statement var1) throws SQLException;
        int update(Statement var1) throws SQLException;
        <E> List<E> query(Statement var1, ResultHandler var2) throws SQLException;
        <E> Cursor<E> queryCursor(Statement var1) throws SQLException;
        BoundSql getBoundSql();
        ParameterHandler getParameterHandler();
    }
    public interface ParameterHandler {
        Object getParameterObject();
        void setParameters(PreparedStatement var1) throws SQLException;
    }
    public interface ResultHandler<T> {
        void handleResult(ResultContext<? extends T> var1);
    }
       차단 실행 순 서 는 Executor->StatementHandler->ParameterHandler->ResultHandler
    MyBatis 가 제공 하 는 차단기 인터페이스:
    
    public interface Interceptor {
        Object intercept(Invocation var1) throws Throwable;
        default Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
        default void setProperties(Properties properties) {}
    }
    Object intercept 방법 은 차단기 의 실현 에 사 용 됩 니 다.
    Object plugin 방법 은 실행 차단기 의 유형 을 판단 하 는 데 사 용 됩 니 다.
    void setProperties 방법 은 설정 항목 의 속성 을 가 져 오 는 데 사 용 됩 니 다.
    4.567917.차단 대상 과 차단기 인터페이스의 결합,사용자 정의 차단기 류 는 차단기 인 터 페 이 스 를 실현 하고@Intercepts 와 파라미터@Signature 를 통 해 차단 대상 을 설명 합 니 다        @Signature 매개 변수 type 은 차단 대상 이 고 method 는 차단 하 는 방법 입 니 다.즉,위의 네 가지 대응 하 는 방법 입 니 다.args 는 차단 방법 에 대응 하 는 매개 변수 입 니 다.
             @Intercepts 는 여러 개의@Signature,즉 하나의 차단기 실현 클래스 가 여러 개의 대상 과 방법 을 동시에 차단 할 수 있 습 니 다.예 를 들 어 다음 과 같 습 니 다.
  • Executor->intercept
  • StatementHandler->intercept
  • ParameterHandler->intercept
  • ResultHandler->intercept
  • 
    @Intercepts({
            @Signature(
                    type = Executor.class,
                    method = "query",
                    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
            )
    })
    public class SelectPlugin implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            if (invocation.getTarget() instanceof Executor) {
                System.out.println("SelectPlugin");
            }
            return invocation.proceed();
        }
        @Override
        public Object plugin(Object target) {
            if (target instanceof Executor) {
                return Plugin.wrap(target, this);
            }
            return target;
        }
        @Override
        public void setProperties(Properties properties) {}
    }
    @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
    public class StatementPlugin implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            if (invocation.getTarget() instanceof StatementHandler) {
                System.out.println("StatementPlugin");
            }
            return invocation.proceed();
        }
        @Override
        public Object plugin(Object target) {
            if (target instanceof StatementHandler) {
                return Plugin.wrap(target, this);
            }
            return target;
        }
        @Override
        public void setProperties(Properties properties) {}
    }
    @Intercepts({@Signature(type = ParameterHandler.class,method = "setParameters",args = {PreparedStatement.class})})
    public class ParameterPlugin implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            if (invocation.getTarget() instanceof ParameterHandler) {
                System.out.println("ParameterPlugin");
            }
            return invocation.proceed();
        }
        @Override
        public Object plugin(Object target) {
            if (target instanceof ParameterHandler) {
                return Plugin.wrap(target, this);
            }
            return target;
        }
        @Override
        public void setProperties(Properties properties) {}
    }
    @Intercepts({@Signature(type = ResultHandler.class,method = "handleResult",args = {ResultContext.class})})
    public class ResultPlugin implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            if (invocation.getTarget() instanceof ResultHandler) {
                System.out.println("ResultPlugin");
            }
            return invocation.proceed();
        }
        @Override
        public Object plugin(Object target) {
            if (target instanceof ResultHandler) {
                return Plugin.wrap(target, this);
            }
            return target;
        }
        @Override
        public void setProperties(Properties properties) {}
    }
    2.차단기 등록 의 세 가지 방식
            앞에서 Mybatis 의 차단 대상 과 인터페이스의 실현 방식 을 소 개 했 는데 프로젝트 에 차단 기 를 어떻게 등록 합 니까?본문 에서 세 가지 등록 방식 을 제시 하 다.
            1.XML 등록
            xml 등록 은 가장 기본 적 인 방식 으로 Mybatis 설정 파일 에서 plugins 요 소 를 통 해 등록 합 니 다.하나의 plugin 은 하나의 차단기 에 대응 합 니 다.plugin 요 소 는 property 서브 요 소 를 지정 할 수 있 습 니 다.정의 차단기 에 등록 할 때 해당 하 는 모든 property 를 Interceptor 의 setProperties 방법 으로 차단기 에 주입 합 니 다.따라서 차단기 등록 xml 방식 은 다음 과 같 습 니 다.
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <!-- ...... -->
        <plugins>
           <plugin interceptor="com.tiantian.mybatis.interceptor.MyInterceptor">
               <property name="prop1" value="prop1"/>
               <property name="prop2" value="prop2"/>
           </plugin>
        </plugins>
        <!-- ...... -->
    </configuration>
            2.클래스 등록 설정
           설정 클래스 등록 이란 Mybatis 의 설정 클래스 를 통 해 등록 차단 기 를 설명 하고,설정 클래스 등록 도 Properties 클래스 를 통 해 Interceptor 의 setProperties 방법 에 파 라미 터 를 주입 할 수 있 습 니 다.구체 적 인 참 고 는 다음 과 같다.
    
    @Configuration
    public class MyBatisConfig {
        @Bean
        public String MyBatisInterceptor(SqlSessionFactory sqlSessionFactory) {
            UpdatePlugin executorInterceptor = new UpdatePlugin();
            Properties properties = new Properties();
            properties.setProperty("prop1", "value1");
            //            
            executorInterceptor.setProperties(properties);
            sqlSessionFactory.getConfiguration().addInterceptor(executorInterceptor);
            sqlSessionFactory.getConfiguration().addInterceptor(new StatementPlugin());
            sqlSessionFactory.getConfiguration().addInterceptor(new ResultPlugin());
            sqlSessionFactory.getConfiguration().addInterceptor(new ParameterPlugin());
            // sqlSessionFactory.getConfiguration().addInterceptor(new SelectPlugin());
            return "interceptor";
        }
    
        //  sqlSessionFactory.getConfiguration().addInterceptor(new SelectPlugin());    
        @Bean
        public SelectPlugin SelectInterceptor() {
            SelectPlugin interceptor = new SelectPlugin();
            Properties properties = new Properties();
            //   properties.setProperty             
            interceptor.setProperties(properties);
            return interceptor;
        }
    }
            3.주해 방식
              @Component 주해 방식 을 통 해 가장 간단 한 방법 으로 사용자 정의 파 라 메 터 를 전달 하지 않 아 도 사용 할 수 있 고 빠 르 고 편리 합 니 다.
    
    @Component
    @Intercepts({
            @Signature(
                    type = Executor.class,
                    method = "query",
                    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
            )
    })
    public class SelectPlugin implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            if (invocation.getTarget() instanceof Executor) {
                System.out.println("SelectPlugin");
            }
            return invocation.proceed();
        }
    
        @Override
        public Object plugin(Object target) {
            if (target instanceof Executor) {
                return Plugin.wrap(target, this);
            }
            return target;
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    }
    3.ParameterHandler 매개 변수 재 작성-수정 시간 과 수정 자 를 통일 적 으로 삽입 합 니 다.
            구체 적 인 차단기 실현 에 대해 설명 하 다.일상적인 인 코딩 수요 에서 수정 할 때 수 정 된 시간 과 인원 을 삽입 해 야 합 니 다.xml 방식 으로 쓰 려 면 매우 번 거 롭 고 차단 기 를 통 해 전체적인 삽입 수정 시간 과 인원 을 신속하게 실현 할 수 있 습 니 다.코드 먼저 보기:
    
    @Component
    @Intercepts({
            @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}),
    })
    public class MyBatisInterceptor implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            //     
            if (invocation.getTarget() instanceof ParameterHandler) {
                System.out.println("ParameterHandler");
                //          
                autoAddOperatorInfo(invocation);
            }
            return invocation.proceed();
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    
        /**
         *          
         *
         * @param invocation     
         * @throws Throwable   
         */
        private void autoAddOperatorInfo(Invocation invocation) throws Throwable {
            System.out.println("autoInsertCreatorInfo");
            //          ParameterHandler
            ParameterHandler ph = (ParameterHandler) invocation.getTarget();
            //   MetaObject  ParameterHandler     
            MetaObject metaObject = MetaObject.forObject(ph,
                    SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                    SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                    new DefaultReflectorFactory());
            //   MetaObject       MappedStatement
            MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("mappedStatement");
            //  sql   INSERT UPDATE ,         
            if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT ||
                    mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
                //       
                Object obj = ph.getParameterObject();
                if (null != obj) {
                    //              
                    Field[] fields = obj.getClass().getDeclaredFields();
                    //          
                    for (Field f : fields) {
                        //   sql INSERT,   createdAt  
                        if ("createdAt".equals(f.getName()) && mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
                            //           
                            f.setAccessible(true);
                            //       createdAt  ,    createdAt         
                            if (null == f.get(obj)) {
                                //   createdAt       
                                f.set(obj, LocalDateTime.now());
                            }
                        }
                        //   sql INSERT,   createdBy  
                        if ("createdBy".equals(f.getName()) && mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
                            //           
                            f.setAccessible(true);
                            //       createdBy  ,    createdBy           
                            if (null == f.get(obj)) {
                                //   createdBy          
                                f.set(obj, 0);
                            }
                        }
                        // sql INSERT UPDATE      updatedAt  
                        if ("updatedAt".equals(f.getName())) {
                            f.setAccessible(true);
                            if (null == f.get(obj)) {
                                f.set(obj, LocalDateTime.now());
                            }
                        }
                        // sql INSERT UPDATE      updatedBy  
                        if ("updatedBy".equals(f.getName())) {
                            f.setAccessible(true);
                            if (null == f.get(obj)) {
                                f.set(obj, 0);
                            }
                        }
                    }
    
                    //       ParameterHandler parameterObject  
                    Field parameterObject = ph.getClass().getDeclaredField("parameterObject");
                    //       parameterObject  
                    parameterObject.setAccessible(true);
                    //               ParameterHandler parameterObject  
                    parameterObject.set(ph, obj);
                }
            }
        }
    }
            차단기 의 인 터 페 이 스 는 전문 을 참고 하여 autoAddOperator Info 방법 과 관련 된 종 류 를 소개 합 니 다.
            1.ParameterHandler
            인터페이스 원본:
    
     public interface ParameterHandler {
         Object getParameterObject();
         void setParameters(PreparedStatement var1) throws SQLException;
     }
            두 가지 방법 을 제공 합 니 다.
            getParameterObject 는 매개 변 수 를 가 져 오 는 대상 입 니 다.null 이 존재 할 수 있 으 므 로 null 지침 에 주의해 야 합 니 다.
            setParameters 는 sql 구문 에 설 정 된 자바 대상 과 jdbc 형식 에 대응 하 는 관 계 를 어떻게 설정 하 는 지 제어 합 니 다.예 를 들 어\#{id,jdbc Type=INTEGER},id 기본 형식 은 자바 Type=class 자바.lang.Integer 입 니 다.
            이 인 터 페 이 스 는 기본 구현 클래스 가 있 습 니 다.원본 코드 는 다음 과 같 습 니 다.
    
    public class DefaultParameterHandler implements ParameterHandler {
        private final TypeHandlerRegistry typeHandlerRegistry;
        private final MappedStatement mappedStatement;
        private final Object parameterObject;
        private final BoundSql boundSql;
        private final Configuration configuration;
    
        public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
            this.mappedStatement = mappedStatement;
            this.configuration = mappedStatement.getConfiguration();
            this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
            this.parameterObject = parameterObject;
            this.boundSql = boundSql;
        }
    
        public Object getParameterObject() {
            return this.parameterObject;
        }
    
        public void setParameters(PreparedStatement ps) {
            ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
            List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
            if (parameterMappings != null) {
                for(int i = 0; i < parameterMappings.size(); ++i) {
                    ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
                    if (parameterMapping.getMode() != ParameterMode.OUT) {
                        String propertyName = parameterMapping.getProperty();
                        Object value;
                        if (this.boundSql.hasAdditionalParameter(propertyName)) {
                            value = this.boundSql.getAdditionalParameter(propertyName);
                        } else if (this.parameterObject == null) {
                            value = null;
                        } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
                            value = this.parameterObject;
                        } else {
                            MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                            value = metaObject.getValue(propertyName);
                        }
    
                        TypeHandler typeHandler = parameterMapping.getTypeHandler();
                        JdbcType jdbcType = parameterMapping.getJdbcType();
                        if (value == null && jdbcType == null) {
                            jdbcType = this.configuration.getJdbcTypeForNull();
                        }
    
                        try {
                            typeHandler.setParameter(ps, i + 1, value, jdbcType);
                        } catch (SQLException | TypeException var10) {
                            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
                        }
                    }
                }
            }
    
        }
    }
            Default ParameterHandler 구현 클래스 를 통 해 우 리 는 ParameterHandler 를 통 해 다음 중요 한 클래스 인 Mapped Statement 을 포함 하여 어떤 속성 과 방법 을 얻 을 수 있 는 지 알 고 있 습 니 다.
            2.MappedStatement
            MyBatis 의 mapper 파일 에 있 는 모든 select/update/insert/delete 탭 은 해상도 기 에서 대응 하 는 Mapped Statement 대상,즉 Mapped Statement 대상 이 SQL 문 구 를 설명 하 는 것 으로 분 석 됩 니 다.MappedStatement 대상 속성 은 다음 과 같 습 니 다.
    
    // mapper     
        private String resource;
        // mybatis     , jdbc
        private Configuration configuration;
        //    id       , :com.example.mybatis.dao.UserMapper.selectByExample
        private String id;
        private Integer fetchSize;
        private Integer timeout;
        private StatementType statementType;
        private ResultSetType resultSetType;
        private SqlSource sqlSource;
        private Cache cache;
        private ParameterMap parameterMap;
        private List<ResultMap> resultMaps;
        private boolean flushCacheRequired;
        private boolean useCache;
        private boolean resultOrdered;
        // sql     :select、update、delete、insert
        private SqlCommandType sqlCommandType;
        private KeyGenerator keyGenerator;
        private String[] keyProperties;
        private String[] keyColumns;
        private boolean hasNestedResultMaps;
        private String databaseId;
        private Log statementLog;
        private LanguageDriver lang;
        private String[] resultSets;
            이 예 에서 Mapped Statement 대상 의 sqlCommandType 을 통 해 현재 sql 유형 이 insert,update 라 고 판단 하여 다음 작업 을 진행 합 니 다.
    4.StatementHandler 를 통 해 SQL 을 고 칩 니 다.
            StatementHandler 는 매개 변 수 를 설정 하고 Statement 결과 집합 을 List 집합 으로 변환 하 는 등 JDBC Statement 작업 을 봉인 하 는 데 사 용 됩 니 다.
            구현 코드 는 다음 과 같 습 니 다:
            주석 태그 삭제
    
    @Target({ElementType.METHOD})  //         
    @Retention(RetentionPolicy.RUNTIME) //       
    @Documented    //    
    public @interface DeletedAt {
        boolean has() default true;
    }
            Dao 층 에 삭제 주 해 를 추가 합 니 다.false 일 때 삭제 플래그 를 추가 하지 않 습 니 다.
    
     @Mapper
     public interface AdminProjectDao {
         @DeletedAt(has = false)
         List<AdminProjectPo> selectProjects(AdminProjectPo po);
     }
            차단 기 는 주석 표 시 를 삭제 하여 삭제 표 지 를 추가 할 지 여 부 를 판단 합 니 다.
    
    @Component
    @Intercepts({
            @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
    })
    public class MyBatisInterceptor implements Interceptor {
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            if (invocation.getTarget() instanceof StatementHandler) {
                System.out.println("StatementHandler");
                checkHasDeletedAtField(invocation);
            }
            return invocation.proceed();
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    
        /**
         *                 
         *
         * @param invocation     
         * @throws Throwable   
         */
        private void checkHasDeletedAtField(Invocation invocation) throws Throwable {
            System.out.println("checkHasDeletedAtField");
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            //   MetaObject       
            MetaObject metaObject = MetaObject.forObject(
                    statementHandler,
                    SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                    SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                    new DefaultReflectorFactory());
            //       mappedStatement
            MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
            //   sql     
            if (mappedStatement.getSqlCommandType() == SqlCommandType.SELECT) {
                //         
                DeletedAt annotation = null;
                String id = mappedStatement.getId();
                String className = id.substring(0, id.lastIndexOf("."));
                String methodName = id.substring(id.lastIndexOf(".") + 1);
                Class<?> aClass = Class.forName(className);
                Method[] declaredMethods = aClass.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    declaredMethod.setAccessible(true);
                    //     ,     DeletedAt
                    if (methodName.equals(declaredMethod.getName()) && declaredMethod.isAnnotationPresent(DeletedAt.class)) {
                        annotation = declaredMethod.getAnnotation(DeletedAt.class);
                    }
                }
                //             true(   true)   mysql        
                if (annotation == null || annotation.has()) {
                    BoundSql boundSql = statementHandler.getBoundSql();
                    //     sql  
                    String sql = boundSql.getSql();
                    //      sql  
                    Field field = boundSql.getClass().getDeclaredField("sql");
                    field.setAccessible(true);
                    String newSql = sql.replaceAll("9=9", "9=9 and deleted_at is null ");
                    field.set(boundSql, newSql);
                }
            }
        }
    }
            SQL 문장 교체 에 있어 서 교체 할 내용 을 식별 할 수 있어 야 하기 때문에 xml 의 sql 문장 에 특수 표지 인'9=9'를 추가 합 니 다.이 표 지 는 원래 SQL 의 실행 결과 에 영향 을 주지 않 고 서로 다른 여과 조건 에 서로 다른 표 지 를 설정 할 수 있 으 며 비교적 교묘 한 교체 방식 입 니 다.
    이상 은 MyBatis 차단기 의 원리 와 사용 에 대한 상세 한 내용 입 니 다.MyBatis 차단기 에 관 한 자 료 는 다른 관련 글 을 주목 하 세 요!

    좋은 웹페이지 즐겨찾기