mybatis拦截器

阅读数:124 评论数:0

跳转到新版页面

分类

python/Java

正文

一、概述

1、mybatis执行流程

(1)Configuration

mybatis所有的配置信息都维持在Configuration对象中,比如:插件、映射器、DataSource等。

(2)SqlSession

作为mybatis工作的主要顶层API,表示和数据库交互会话

(3)Executor

mybatis执行器,是mybatis调度的核心,负责SQL语句的生成和查询缓存的维护

(4)StatementHandler

封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换成List集合。

(5)ParameterHandler

负责对用户传递的参数转换成JDBC Statement所需要的参数。

(6)ResultSetHandler

负责将JDBC返回的ResultSet结果集对象转换成List类型的集合。

(7)TypeHandler

负责Java数据类型和jdbc数据类型之间的映射和转换。

(8)MappedStatement

一条SQL对应一个Statement.

2、mybatis拦截器

又称mybatis插件,采用责任链模式+动态代理模式实现拦截的功能,通过这些插件可以改变mybatis的默认行为(诸如SQL重写之类的),可以实现在mybatis的整个运行流程中的某些指定位置进行拦截,mybatis支持对Executor、ParameterHandler、ResultSetHandler和StatementHandler接口进行拦截。

//对执行器类型进行拦截,括号内表示是可以拦截的方法
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
 
//参数处理时进行拦截
ParameterHandler (getParameterObject, setParameters)
 
//处理结果集,封装Java对象时进行拦截
ResultSetHandler (handleResultSets, handleOutputParameters)
 
//编译statement时进行拦截
StatementHandler (prepare, parameterize, batch, update, query)

(1)Executor接口及其方法

可以在这里实现自定义二级缓存。

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

(2)ResultSetHandler

public interface ResultSetHandler {
    <E> List<E> handleResultSets(Statement var1) throws SQLException;
 
    <E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;
 
    void handleOutputParameters(CallableStatement var1) throws SQLException;
}

(3)StatementHandler

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

3、mybatis拦截器链

InterceptorChain对象是拦截器执行链对象,里面维护了Mybatis配置的所有拦截器(Interceptor)对象。启动mybatis时,初始化配置文件的时候就把所有的拦截器添加到拦截器链中。

SqlSessionFactoryBean初始化配置文件时添加拦截器。

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
   
 
    public void afterPropertiesSet() throws Exception {
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }
 
    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
        // 省略
 
        //查看是否注入拦截器,有的话添加到Interceptor集合里面
        if (!ObjectUtils.isEmpty(this.plugins)) {
            Stream.of(this.plugins).forEach((plugin) -> {
                targetConfiguration.addInterceptor(plugin);
                LOGGER.debug(() -> {
                    return "Registered plugin: '" + plugin + "'";
                });
            });
        }
 
        //省略
 
        return this.sqlSessionFactoryBuilder.build(targetConfiguration);
    }

同样在使用配置文件时,XMLConfigBuilder解析mybatis全局配置文件的pluginElement方法时也会将拦截器添加到拦截器链对象中。

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
}

InterceptorChain是Configuration的内部属性。

public class InterceptorChain {
    //所有插件对象
    private final List<Interceptor> interceptors = new ArrayList();
 
    public InterceptorChain() {
    }
 
    public Object pluginAll(Object target) {
        Interceptor interceptor;
        //循环调用每个拦截器的plugin方法,返回的对象调用下一个拦截器的plugin
        for (Interceptor interceptor : interceptors) {
          target = interceptor.plugin(target);
        }
 
        return target;
    }
 
    public void addInterceptor(Interceptor interceptor) {
        this.interceptors.add(interceptor);
    }
 
    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(this.interceptors);
    }
}

拦截器链中的pluginAll方法,就是执行所有拦截器的plugin方法。拦截器的plugin方法,可以在自定义拦截器的时候自我实现,但是Mybatis已经为我们提供了一个Plugin的类实现,通过静态方法wrap(Object target,Interceptor interceptor)可以决定要返回的对象是目标对象还是对应的代理。

//实现InvocationHandler接口,重写invoke方法,代理对象执行时会调用invoke方法
public class Plugin implements InvocationHandler {
    private final Object target;
    private final Interceptor interceptor;
    private final Map<Class<?>, Set<Method>> signatureMap;
 
    //提供warp方法,目标实例target(这个target就是之前所说的MyBatis拦截器可以拦截的类,Executor,ParameterHandler,ResultSetHandler,StatementHandler)和它的父类们
    public static Object wrap(Object target, Interceptor interceptor) {
        //获取插件的Intercepts注解拦截器类名和方法信息
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        //获取要改变行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor)
        Class<?> type = target.getClass();
        //获取signatureMap中含有target实现的接口数组
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        //根据是否有4个接口类型决定返回代理对象还是原目标对象
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }
    
    //如果当前执行的方法是定义好的需要拦截的方法,则把目标对象、要执行的方法以及方法参数封装成一个Invocation对象,
    //再把封装好的Invocation作为参数传递给当前拦截器的intercept方法。如果不需要拦截,则直接调用当前的方法。
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //获取拦截器中配置的所有方法
            Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
            //当拦截器中方法包含当前执行的方法,则调用拦截器的intercept方法,否则执行原方法
            return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
        } catch (Exception var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }
 

 

二、@Interceptor接口

mybatis中使用拦截器非常简单,只要实现Interceptor接口并通过@Intercepts和@Signature注解指定要拦截的接口和方法即可。

public interface Interceptor {  
   //要进行拦截的时候要执行的方法。 
   Object intercept(Invocation invocation) throws Throwable;     
 
   //用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,根据是否要进行拦截决定要返回一个什么样的目标对象,官方提供了示例:return Plugin.wrap(target, this)返回一个代理对象
   default Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
 
   //在Mybatis进行配置插件的时候可以配置自定义相关属性,即:接口实现对象的参数配置。
   void setProperties(Properties properties);
}

1、@Intercepts和@Signature注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Intercepts {
    Signature[] value();
}

它的值是一个@Signature数组,表示当前的对象是一个Interceptor,而@Signature则表明要拦截的接口、方法以及对应的参数类型。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
    Class<?> type();
 
    String method();
 
    Class<?>[] args();
}

 

 




相关推荐

出现这个错误,是因为mybatis默认OGNL解析参数,所以会自动采用对象树形式取String.xxx值。 解决方法: 方法1:在方法中提前定义 <pre c

&lt;if test="extraSql != null and extraSql !=''"&gt; AND ${ext

一、概述 mybatis原来是apache的一个开源项目,叫做ibatis,2010年由apache迁移到了google code,并且改名为mybatis。2013年迁移到github。 mybat

(1)Executor 执行增删改查操作

实际开发中,有时候需要把当前插入的数据id取出来,但又不想再去查一遍. mybatis提供了两种返回insert方法后的主键的方法 : 1、根据useGeneratedKeys获取

一、概述 一个项目使用多个数据库(无论是主从复制--读写分离还是分布式数据库结构)的重要性变得越来越明显,整合的多数据源有两种方式:分包和aop。 1、SqlSessionTemplate SqlSe

背景: 之前用spring boot+mybatis+oracle,现在要改成spring boot_mybatis+postgresql。 为了想让一套代码即可

1、使用selectKey标签 &lt;insert id="addLoginLog" parameterTyp

依靠数据库锁是非常安全的方式,比方说,分布式定时任务,除了使用quartz不让各个机器上同时跑一个定时任务之外,最好在数据库也加一个保险。插入一条数据之前,判断表中有没有这条数据,如果没有才插入

原因很简单,就一句话,是不是resultType 和resultMap 弄混了? &nbsp; 大致的意思就是无法找到返回值对应的r