欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MyBatis攔截器的原理與使用

 更新時(shí)間:2021年06月17日 10:17:00   作者:飄梧  
本文全面的講解了MyBatis攔截器的作用原理及使用方法,攔截器的使用可以提升開(kāi)發(fā)效率,學(xué)習(xí)MyBatis的朋友不妨了解下本文

一、攔截對(duì)象和接口實(shí)現(xiàn)示例

        MyBatis攔截器的作用是在于Dao到DB中間進(jìn)行額外的處理。大部分情況下通過(guò)mybatis的xml配置sql都可以達(dá)到想要的DB操作效果,然而存在一些類似或者相同的查詢條件或者查詢要求,這些可以通過(guò)攔截器的實(shí)現(xiàn)可以提升開(kāi)發(fā)效率,比如:分頁(yè)、插入和更新時(shí)間/人、數(shù)據(jù)權(quán)限、SQL監(jiān)控日志等。

  • Mybatis支持四種對(duì)象攔截Executor、StatementHandler、PameterHandler和ResultSetHandler
  1. Executor:攔截執(zhí)行器的方法。
  2. StatementHandler:攔截Sql語(yǔ)法構(gòu)建的處理。
  3. ParameterHandler:攔截參數(shù)的處理。
  4. ResultHandler:攔截結(jié)果集的處理。
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);
}

     攔截的執(zhí)行順序是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方法用于攔截器的實(shí)現(xiàn);

  Object plugin方法用于判斷執(zhí)行攔截器的類型;

  void setProperties方法用于獲取配置項(xiàng)的屬性。

  • 攔截對(duì)象和攔截器接口的結(jié)合,自定義的攔截器類需要實(shí)現(xiàn)攔截器接口,并通過(guò)注解@Intercepts和參數(shù)@Signature來(lái)聲明要攔截的對(duì)象。

        @Signature參數(shù)type是攔截對(duì)象,method是攔截的方法,即上面的四個(gè)類對(duì)應(yīng)的方法,args是攔截方法對(duì)應(yīng)的參數(shù)(方法存在重載因此需要指明參數(shù)個(gè)數(shù)和類型)

         @Intercepts可以有多個(gè)@Signature,即一個(gè)攔截器實(shí)現(xiàn)類可以同時(shí)攔截多個(gè)對(duì)象及方法,示例如下:

  1. Executor->intercept
  2. StatementHandler->intercept
  3. ParameterHandler->intercept
  4. 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) {}
}

二、攔截器注冊(cè)的三種方式

        前面介紹了Mybatis的攔截對(duì)象及其接口的實(shí)現(xiàn)方式,那么在項(xiàng)目中如何注冊(cè)攔截器呢?本文中給出三種注冊(cè)方式。

        1.XML注冊(cè)

        xml注冊(cè)是最基本的方式,是通過(guò)在Mybatis配置文件中plugins元素來(lái)進(jìn)行注冊(cè)的。一個(gè)plugin對(duì)應(yīng)著一個(gè)攔截器,在plugin元素可以指定property子元素,在注冊(cè)定義攔截器時(shí)把對(duì)應(yīng)攔截器的所有property通過(guò)Interceptor的setProperties方法注入給攔截器。因此攔截器注冊(cè)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.配置類注冊(cè)

       配置類注冊(cè)是指通過(guò)Mybatis的配置類中聲明注冊(cè)攔截器,配置類注冊(cè)也可以通過(guò)Properties類給Interceptor的setProperties方法注入?yún)?shù)。具體參考如下:

@Configuration
public class MyBatisConfig {
    @Bean
    public String MyBatisInterceptor(SqlSessionFactory sqlSessionFactory) {
        UpdatePlugin executorInterceptor = new UpdatePlugin();
        Properties properties = new Properties();
        properties.setProperty("prop1", "value1");
        // 給攔截器添加自定義參數(shù)
        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();
        // 調(diào)用properties.setProperty方法給攔截器設(shè)置自定義參數(shù)
        interceptor.setProperties(properties);
        return interceptor;
    }
}

        3.注解方式

          通過(guò)@Component注解方式是最簡(jiǎn)單的方式,在不需要轉(zhuǎn)遞自定義參數(shù)時(shí)可以使用,方便快捷。

@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) {

    }
}

三、ParameterHandler參數(shù)改寫(xiě)-修改時(shí)間和修改人統(tǒng)一插入

        針對(duì)具體的攔截器實(shí)現(xiàn)進(jìn)行描述。日常編碼需求中會(huì)碰到修改時(shí)需要插入修改的時(shí)間和人員,如果要用xml的方式去寫(xiě)非常麻煩,而通過(guò)攔截器的方式可以快速實(shí)現(xiàn)全局的插入修改時(shí)間和人員。先看代碼:

@Component
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}),
})
public class MyBatisInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 參數(shù)代理
        if (invocation.getTarget() instanceof ParameterHandler) {
            System.out.println("ParameterHandler");
            // 自動(dòng)添加操作員信息
            autoAddOperatorInfo(invocation);
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * 自動(dòng)添加操作員信息
     *
     * @param invocation 代理對(duì)象
     * @throws Throwable 異常
     */
    private void autoAddOperatorInfo(Invocation invocation) throws Throwable {
        System.out.println("autoInsertCreatorInfo");
        // 獲取代理的參數(shù)對(duì)象ParameterHandler
        ParameterHandler ph = (ParameterHandler) invocation.getTarget();
        // 通過(guò)MetaObject獲取ParameterHandler的反射內(nèi)容
        MetaObject metaObject = MetaObject.forObject(ph,
                SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                new DefaultReflectorFactory());
        // 通過(guò)MetaObject反射的內(nèi)容獲取MappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("mappedStatement");
        // 當(dāng)sql類型為INSERT或UPDATE時(shí),自動(dòng)插入操作員信息
        if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT ||
                mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
            // 獲取參數(shù)對(duì)象
            Object obj = ph.getParameterObject();
            if (null != obj) {
                // 通過(guò)反射獲取參數(shù)對(duì)象的屬性
                Field[] fields = obj.getClass().getDeclaredFields();
                // 遍歷參數(shù)對(duì)象的屬性
                for (Field f : fields) {
                    // 如果sql是INSERT,且存在createdAt屬性
                    if ("createdAt".equals(f.getName()) && mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
                        // 設(shè)置允許訪問(wèn)反射屬性
                        f.setAccessible(true);
                        // 如果沒(méi)有設(shè)置createdAt屬性,則自動(dòng)為createdAt屬性添加當(dāng)前的時(shí)間
                        if (null == f.get(obj)) {
                            // 設(shè)置createdAt屬性為當(dāng)前時(shí)間
                            f.set(obj, LocalDateTime.now());
                        }
                    }
                    // 如果sql是INSERT,且存在createdBy屬性
                    if ("createdBy".equals(f.getName()) && mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
                        // 設(shè)置允許訪問(wèn)反射屬性
                        f.setAccessible(true);
                        // 如果沒(méi)有設(shè)置createdBy屬性,則自動(dòng)為createdBy屬性添加當(dāng)前登錄的人員
                        if (null == f.get(obj)) {
                            // 設(shè)置createdBy屬性為當(dāng)前登錄的人員
                            f.set(obj, 0);
                        }
                    }
                    // sql為INSERT或UPDATE時(shí)均需要設(shè)置updatedAt屬性
                    if ("updatedAt".equals(f.getName())) {
                        f.setAccessible(true);
                        if (null == f.get(obj)) {
                            f.set(obj, LocalDateTime.now());
                        }
                    }
                    // sql為INSERT或UPDATE時(shí)均需要設(shè)置updatedBy屬性
                    if ("updatedBy".equals(f.getName())) {
                        f.setAccessible(true);
                        if (null == f.get(obj)) {
                            f.set(obj, 0);
                        }
                    }
                }

                // 通過(guò)反射獲取ParameterHandler的parameterObject屬性
                Field parameterObject = ph.getClass().getDeclaredField("parameterObject");
                // 設(shè)置允許訪問(wèn)parameterObject屬性
                parameterObject.setAccessible(true);
                // 將上面設(shè)置的新參數(shù)對(duì)象設(shè)置到ParameterHandler的parameterObject屬性
                parameterObject.set(ph, obj);
            }
        }
    }
}

        攔截器的接口實(shí)現(xiàn)參考前文,這里著重介紹autoAddOperatorInfo方法里的相關(guān)類。

        1.ParameterHandler

        接口源碼:

 public interface ParameterHandler {
     Object getParameterObject();
     void setParameters(PreparedStatement var1) throws SQLException;
 }

        提供兩個(gè)方法:

        getParameterObject是獲取參數(shù)對(duì)象,可能存在null,需要注意null指針。

        setParameters是控制如何設(shè)置SQL參數(shù),即sql語(yǔ)句中配置的java對(duì)象和jdbc類型對(duì)應(yīng)的關(guān)系,例如#{id,jdbcType=INTEGER},id默認(rèn)類型是javaType=class java.lang.Integer。

        該接口有一個(gè)默認(rèn)的實(shí)現(xiàn)類,源碼如下:

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);
                    }
                }
            }
        }

    }
}

        通過(guò)DefaultParameterHandler實(shí)現(xiàn)類我們知道通過(guò)ParameterHandler可以獲取到哪些屬性和方法,其中包括我們下面一個(gè)重要的類MappedStatement。

        2.MappedStatement

        MyBatis的mapper文件中的每個(gè)select/update/insert/delete標(biāo)簽會(huì)被解析器解析成一個(gè)對(duì)應(yīng)的MappedStatement對(duì)象,也就是一個(gè)MappedStatement對(duì)象描述一條SQL語(yǔ)句。MappedStatement對(duì)象屬性如下:

// mapper配置文件名
    private String resource;
    // mybatis的全局信息,如jdbc
    private Configuration configuration;
    // 節(jié)點(diǎn)的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語(yǔ)句的類型: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;

        在本例中通過(guò)MappedStatement對(duì)象的sqlCommandType來(lái)判斷當(dāng)前的sql類型是insert、update來(lái)進(jìn)行下一步的操作。

四、通過(guò)StatementHandler改寫(xiě)SQL

        StatementHandler是用于封裝JDBC Statement操作,負(fù)責(zé)對(duì)JDBC Statement的操作,如設(shè)置參數(shù),并將Statement結(jié)果集轉(zhuǎn)換成List集合。

        實(shí)現(xiàn)代碼如下:

        刪除注解標(biāo)記

@Target({ElementType.METHOD})  //表示注解的使用范圍
@Retention(RetentionPolicy.RUNTIME) //注解的保存時(shí)間
@Documented    //文檔顯示
public @interface DeletedAt {
    boolean has() default true;
}

        Dao層添加刪除注解,為false時(shí)不添加刪除標(biāo)志

 @Mapper
 public interface AdminProjectDao {
     @DeletedAt(has = false)
     List<AdminProjectPo> selectProjects(AdminProjectPo po);
 }

        攔截器通過(guò)刪除注解標(biāo)記判斷是否添加刪除標(biāo)志

@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) {

    }

    /**
     * 檢查查詢是否需要添加刪除標(biāo)志字段
     *
     * @param invocation 代理對(duì)象
     * @throws Throwable 異常
     */
    private void checkHasDeletedAtField(Invocation invocation) throws Throwable {
        System.out.println("checkHasDeletedAtField");
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // 通過(guò)MetaObject訪問(wèn)對(duì)象的屬性
        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) {
            // 獲取刪除注解標(biāo)志
            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(默認(rèn)為true) 則為mysql語(yǔ)句增加刪除標(biāo)志
            if (annotation == null || annotation.has()) {
                BoundSql boundSql = statementHandler.getBoundSql();
                //獲取到原始sql語(yǔ)句
                String sql = boundSql.getSql();
                //通過(guò)反射修改sql語(yǔ)句
                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語(yǔ)句替換上需要能識(shí)別到要被替換的內(nèi)容,因此在xml的sql語(yǔ)句中加入特殊標(biāo)志"9=9",該標(biāo)志不影響原來(lái)SQL的執(zhí)行結(jié)果,不同的過(guò)濾條件可以設(shè)置不同的標(biāo)志,是一個(gè)比較巧妙的替換方式。

以上就是MyBatis攔截器的原理與使用的詳細(xì)內(nèi)容,更多關(guān)于MyBatis攔截器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論