编程知识 cdmana.com

Back end technology: detailed explanation of mybatis plug-in principle

Focus on “Java Back end technology stack ”

reply “ interview ” Get a full set of interview materials

The last post talked about how to integrate paging plug-ins MyBatis Plug in principle analysis , After reading it, I feel like I am better 了 , Today we're going to talk about mybatis Principle of plug-in .

Plug in principle analysis

mybatis Plug in involves several classes :

I will be Executor For example , analysis MyBatis How is it for Executor Instance is embedded in the plug-in .Executor The instance is opening SqlSession Created when , therefore , We analyze it from the source . Let's take a look first SqlSession Opening process .

public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), nullfalse);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        //  Elliptic logic 
        
        //  establish  Executor
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } 
    catch (Exception e) {...} 
    finally {...}
}

Executor The creation process of is encapsulated in Configuration in , Let's go in and have a look .

// Configuration Class 
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    
    //  according to  executorType  Create the corresponding  Executor  example 
    if (ExecutorType.BATCH == executorType) {...} 
    else if (ExecutorType.REUSE == executorType) {...} 
    else {
        executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    
    //  Plug in 
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

Above ,newExecutor Method in creating a good Executor After the instance , And then through the chain of interceptors interceptorChain by Executor Instance embeds proxy logic . Let's take a look at InterceptorChain What's the code for .

public class InterceptorChain {
    private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
    public Object pluginAll(Object target) {
        //  Traverse the interceptor collection 
        for (Interceptor interceptor : interceptors) {
            //  To invoke the interceptor  plugin  Method to embed the corresponding plug-in logic 
            target = interceptor.plugin(target);
        }
        return target;
    }
    /**  Add plug-in instances to  interceptors  Collection  */
    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }
    /**  Get plug-in list  */
    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }
}

above for Loop represents as long as it's a plug-in , with Responsibility chain To execute one by one ( Don't expect it to skip a node ), So called plug-ins , It's like interceptors .

The chain of responsibility design pattern is used here , The chain of responsibility design pattern is equivalent to that we are in OA Initiate approval in the system , The leaders approve one level at a time .

Above is InterceptorChain Full code , Relatively simple . its pluginAll Method will call the specific plug-in plugin Method to embed the corresponding plug-in logic . If you have multiple plugins , It will call plugin Method , Finally, a nested proxy class is generated . It looks like the following :

When Executor When a method of is called , The plug-in logic will execute first . The order of execution is from the outside to the inside , For example, the execution sequence in the figure above is plugin3 → plugin2 → Plugin1 → Executor.

plugin Methods are implemented by specific plug-in classes , However, the method code is generally fixed , So let's take an example to analyze .

// TianPlugin class 
public Object plugin(Object target) {
    return Plugin.wrap(target, this);
}

//Plugin
public static Object wrap(Object target, Interceptor interceptor) {
    /*
     *  Get plug-in class  @Signature  Note content , And generate the corresponding mapping structure . It looks like the following :
     * {
     *     Executor.class : [query, update, commit],
     *     ParameterHandler.class : [getParameterObject, setParameters]
     * }
     */
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    //  Get the interface implemented by the target class 
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
        //  adopt  JDK  Dynamic proxy generates proxy classes for target classes 
        return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            new Plugin(target, interceptor, signatureMap));
    }
    return target;
}

Above ,plugin Method is called internally Plugin Class wrap Method , Used to generate a proxy for the target object .Plugin Class implements the InvocationHandler Interface , So it can be passed as a parameter to Proxy Of newProxyInstance Method .

Come here , The logic of plug-in placement is analyzed . Next , Let's take a look at how the plug-in logic works .

Execute plug-in logic

Plugin Realized InvocationHandler Interface , So it's invoke Method intercepts all method calls .invoke Method will detect the intercepted method , To decide whether to execute plug-in logic . The logic of this method is as follows :

// stay Plugin Class 
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        /*
         *  Get the list of blocked methods , such as :
         *    signatureMap.get(Executor.class), May return  [query, update, commit]
         */
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        //  Check whether the method list contains intercepted methods 
        if (methods != null && methods.contains(method)) {
            //  Execute plug-in logic 
            return interceptor.intercept(new Invocation(target, method, args));
        }
        //  Execute the intercepted method 
        return method.invoke(target, args);
    } catch (Exception e) {
        throw ExceptionUtil.unwrapThrowable(e);
    }
}

invoke Method has less code , Logic is not hard to understand . First ,invoke Method will detect whether the intercepted method is configured in the plug-in's @Signature In the annotations , if , The plug-in logic is executed , Otherwise, the intercepted method is executed . Plug in logic is encapsulated in intercept in , The parameter type of this method is Invocation.Invocation It is mainly used to store the target class , Method and method parameter list . Let's take a brief look at the definition of this class .

public class Invocation {

    private final Object target;
    private final Method method;
    private final Object[] args;

    public Invocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }
    //  Omitted code 
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        // Reflection calls the intercepted method 
        return method.invoke(target, args);
    }
}

The execution logic of plug-ins is analyzed here , The whole process is not hard to understand , Let's have a simple look .

Custom plug in

Let's make a better understanding of Mybatis Plug in mechanism of , Let's simulate a slow sql Monitoring plug-ins .

/**
 *  The slow query sql  plug-in unit 
 */
@Intercepts({@Signature(type = StatementHandler.classmethod "prepare", args = {Connection.classInteger.class})})
public class SlowSqlPlugin implements Interceptor {

    private long slowTime;

    // The business that needs to be handled after interception 
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // adopt StatementHandler Get the executed sql
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();

        long start = System.currentTimeMillis();
        // End the interception 
        Object proceed = invocation.proceed();
        long end = System.currentTimeMillis();
        long f = end - start;
        System.out.println(sql);
        System.out.println(" Time consuming =" + f);
        if (f > slowTime) {
            System.out.println(" This database operation is a slow query ,sql yes :");
            System.out.println(sql);
        }
        return proceed;
    }

    // Get intercepted object , The bottom layer is also implemented by proxy , It's actually getting a target proxy object 
    @Override
    public Object plugin(Object target) {
        // Trigger intercept Method 
        return Plugin.wrap(target, this);
    }

    // Set properties 
    @Override
    public void setProperties(Properties properties) {
        // Get our definition of slow sql Time threshold for slowTime
        this.slowTime = Long.parseLong(properties.getProperty("slowTime"));
    }
}

Then inject the plug-in class into the container .

Then we'll execute the query method .

Time consuming 28 Of a second , It's bigger than what we've defined 10 millisecond , So this one SQL It's what we think is slow SQL.

Through this plug-in , We can easily understand setProperties() What does the method do .

Review paging plug-ins

Also realize mybatis Interface Interceptor.

@SuppressWarnings({"rawtypes""unchecked"})
@Intercepts(
    {
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    }
)
public class PageInterceptor implements Interceptor {
        @Override
    public Object intercept(Invocation invocation) throws Throwable {
        ...
    }

intercept In the method

//AbstractHelperDialect Class 
@Override
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
        String sql = boundSql.getSql();
        Page page = getLocalPage();
        // Support  order by
        String orderBy = page.getOrderBy();
        if (StringUtil.isNotEmpty(orderBy)) {
            pageKey.update(orderBy);
            sql = OrderByParser.converToOrderBySql(sql, orderBy);
        }
        if (page.isOrderByOnly()) {
            return sql;
        }
        // Get paging sql
        return getPageSql(sql, page, pageKey);
 }
// Hook method in template method pattern 
 public abstract String getPageSql(String sql, Page page, CacheKey pageKey);

AbstractHelperDialect Class implementation classes are as follows ( This page plug-in supports the following databases ):

We're going to use MySQL. There is also a corresponding .

    @Override
    public String getPageSql(String sql, Page page, CacheKey pageKey) {
        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
        sqlBuilder.append(sql);
        if (page.getStartRow() == 0) {
            sqlBuilder.append(" LIMIT ? ");
        } else {
            sqlBuilder.append(" LIMIT ?, ? ");
        }
        pageKey.update(page.getPageSize());
        return sqlBuilder.toString();
    }

We'll know when we get here , It's just what we do SQL It's been stitched together again Limit only . Empathy ,Oracle That is to use rownum To handle paging . Here is Oracle Process paging

    @Override
    public String getPageSql(String sql, Page page, CacheKey pageKey) {
        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 120);
        if (page.getStartRow() > 0) {
            sqlBuilder.append("SELECT * FROM ( ");
        }
        if (page.getEndRow() > 0) {
            sqlBuilder.append(" SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM ( ");
        }
        sqlBuilder.append(sql);
        if (page.getEndRow() > 0) {
            sqlBuilder.append(" ) TMP_PAGE WHERE ROWNUM <= ? ");
        }
        if (page.getStartRow() > 0) {
            sqlBuilder.append(" ) WHERE ROW_ID > ? ");
        }
        return sqlBuilder.toString();
    }

Other database paging operations are similar to . About the concrete principle analysis , There is no need to repeat it here , Because the comments in the pagination plug-in source code are basically Chinese .

Mybatis Plug in application scenarios

  • Horizontal sub table

  • Access control

  • Data encryption and decryption

summary

Spring-Boot+Mybatis Inherited the paging plug-in , And use cases 、 Plug in principle analysis 、 Source code analysis 、 How to customize plug-ins .

When it comes to technical points :JDK A dynamic proxy 、 Responsibility chain design pattern 、 Template method pattern .

Mybatis Plug in key object summary :

  • Inteceptor Interface : The class that the custom intercept must implement .

  • InterceptorChain: The container for the plug-in .

  • Plugin:h object , Provides methods for creating proxy classes .

  • Invocation: Encapsulation of the surrogate object .

IT Technology sharing community

Personal blog site :https://programmerblog.xyz

Article recommendation Programmer efficiency : Common tools for drawing flowcharts Programmer efficiency : Organize common online note taking software Telecommuting : Commonly used remote assistance software , Do you know ?51 MCU download program 、ISP And basic knowledge of serial port Hardware : Circuit breaker 、 Contactor 、 Basic knowledge of relay

版权声明
本文为[osc_ 7oc4d1en]所创,转载请带上原文链接,感谢
https://cdmana.com/2020/12/20201224130630627c.html

Scroll to Top