Using mybatis plug-in to develop dynamic change sql

Posted by frih on Sun, 26 Sep 2021 11:43:29 +0200

1. Business background requirements

At present, the company where the landlord is located uses the same set of database with the advance environment. The purpose of this is to check the real situation on the acceptance line of the advance environment. However, several configuration tables need to be changed separately in the advance environment. If they are changed, they will affect the use of online users. Therefore, how to isolate the configuration table from the environment without changing the existing code; The method is to use the plug-in development mechanism provided by mybatis to intercept the four built-in objects at the bottom; So what are the four built-in objects?

2. 4 built-in objects

  1. Executor: represents the executor, which schedules StatementHandler, ParameterHandler, ResultSetHandler, etc. to execute the corresponding SQL, of which StatementHandler is the most important.
  2. StatementHandler: it is used to perform operations using the Statement (PreparedStatement) of the database. It is the core of the four objects and plays a connecting role. Many important plug-ins are implemented by intercepting it.
  3. ParameterHandler: used to process SQL parameters.
  4. ResultSetHandler: it is used to encapsulate and return a dataset.

The four built-in objects are closely related to the life cycle of mybatis executing an sql, Refer to another blog for the specific implementation process

3. Specific implementation

Our solution is to add a new set of pre shipment configuration tables (production environment). For example, the previous configuration table is called table_config, and the newly added table is table_config_pre, of course, the data should also be copied, and then judged according to the system operation environment variables. If it is a pre issuance environment, intercept the StatementHandler in the four built-in objects, and the prepare method of StatementHandler will precompile the sql. Here we can get the sql. If it is the configuration table we want to change, replace the table; The specific codes are as follows:

@Slf4j
@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class ReRoutePlugin implements Interceptor, ApplicationContextAware {

    private static final String PRE = "pre";

    private ApplicationContext context;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // Effective only in advance environment
        if(context.getEnvironment().getActiveProfiles()[0].equals(PRE)){
            return invocation.proceed();
        }

        try {
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            BoundSql boundSql = statementHandler.getBoundSql();
            String sqlBefore = boundSql.getSql();

            boolean containsChannel = sqlBefore.contains("channel_config");

            if(StringUtils.isNotBlank(sqlBefore) && containsChannel){
                String sqlAfter = sqlBefore.replaceAll("channel_config", "channel_config_pre");
               

                Field sqlField = boundSql.getClass().getDeclaredField("sql");
                ReflectionUtils.setField(sqlField, boundSql, sqlAfter);
                log.info("before sql:{},after sql:{}", sqlBefore, sqlAfter);
            }
        }catch (Exception e){
            log.error("ChannelConfigReRoutePlugin error.", e);
        }
        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {}

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
}

Analysis of mybatis principle_ Promise SH's blog - CSDN blog   In this way, you can steal the beam and change the column at the bottom without changing the business code

4. Plug in development principle

public interface Interceptor {
    Object intercept(Invocation var1) throws Throwable;

    default Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    default void setProperties(Properties properties) {
    }
}

Before the execution of the four built-in objects, the plugin method in the Interceptor will be executed. Take a look at the plugin method

 public static Object wrap(Object target, Interceptor interceptor) {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }

The wrap method will read all the current plug-ins and generate a proxy class to execute the methods in the plug-in

Topics: Database Mybatis SQL