Mybatis prints the complete SQL and presents it to the front end

Posted by zampu on Wed, 26 Jan 2022 08:28:20 +0100

survey

In the recent report writing function, when the data is inaccurate, you need to check the SQL running on the current interface and compare whether there is a problem with the SQL. Then you need to get the SQL statement running on the interface on the business side. The realization of this function is mainly considered from two aspects.
1.Mybatis framework plug-in
Mybatis can get the complete SQL in the whole request thread by setting the plug-in form, intercepting the executed SQL and setting it into ThreadLocal. However, at present, other blogs are SQL placeholders and can not print the SQL completely.
2.Mybatis Log pluging
The source code of mybatis log plugin is mainly used to assemble SQL from the console. There is zero intrusion for business code, which is not suitable for this demand.
This time, we mainly use the plug-in of Mybatis framework to realize the functions. I won't say much nonsense. The above code:

Implementation steps

Create interceptor

@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})})
public class SqlStatementInterceptor implements Interceptor {

    private final Logger logger = LoggerFactory.getLogger(SqlStatementInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long startTime = System.currentTimeMillis();
        try {
            return invocation.proceed();
        } finally {
            long endTime = System.currentTimeMillis();
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = boundSql.getSql();
            //args contains complete sql
            if (invocation.getArgs().length > 0) {
                try {
                    //getTarget can be obtained. If an error is reported, try invoking getArgs()[0]. Tostring() different SQL connections
                    sql = getTarget(invocation.getArgs()[0]).toString();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            //sql collation
            sql = sql.replace("\n", "").replace("\t", "").replaceAll("\\s+", " ");
            //Collect SQL for this visit
            String threadSql = Constant.THREAD_SQL.get();
            threadSql = threadSql == null ? "" : threadSql;
            threadSql = threadSql + "|" + sql;
            Constant.THREAD_SQL.set(threadSql);

            logger.info("implement SQL: [{}]spend{}ms", sql, (endTime - startTime));
        }
    }

    /**
     * Get the proxied object from the proxy object
     *
     * @param proxy
     * @return
     * @throws Exception
     */
    public Object getTarget(Object proxy) throws Exception {
        Field[] fields = proxy.getClass().getSuperclass().getFields();
        for (Field field : fields) {
            System.out.println(field.getName());
        }
        Field field = proxy.getClass().getSuperclass().getDeclaredField("h");
        field.setAccessible(true);
        //Gets the value of this field in the specified object
        //Gets the value of this field in the Proxy object
        PreparedStatementLogger personProxy = (PreparedStatementLogger) field.get(proxy);
        Field statement = personProxy.getClass().getDeclaredField("statement");
        statement.setAccessible(true);
        return statement.get(personProxy);
    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

Add plug-in:

    @Bean(name = "SqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setPlugins(new Interceptor[]{new SqlStatementInterceptor()});
        bean.setDataSource(dynamicDataSource);
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*.xml"));
        return bean.getObject();
    }

Set response header

Because SQL statements are non business level things, it is not good to put them in the response result body according to the specification.
At first, I used the interceptor to implement it, but the setting of the request header in the postHandler method of the interceptor that stepped on a pit interceptor did not take effect, because the request header returned to the front end after executing the controller, and then executed the postHandler. The following is part of the source code of springMvc

// DispatcherServlet.java class
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	....Omit code
	// #1 carry out pre-processing and the processing before entering the controller method
	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;
	}

	// #2 execute the controller method
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

	if (asyncManager.isConcurrentHandlingStarted()) {
		return;
	}

	applyDefaultViewName(processedRequest, mv);
	// #3 execute post-processing, that is, the processing after the controller method is executed
	mappedHandler.applyPostHandle(processedRequest, response, mv);
	....Omit code
}

When the #2 method is returned after execution, you can make a breakpoint to have a look


So the interceptor doesn't work. Finally, I implemented it in the way of AOP. The code is as follows:

@Aspect
@Order(1)
@Component
public class SQLAspect implements HandlerInterceptor {

    /**
     * Select the pointcut to annotate the DataSource
     */
    @Pointcut("execution(* com.xxxx.xx.xxx.web.controller..*Controller.*(..))")
    public void dsPointCut() {
    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletResponse response = null;
        if (sra != null) {
            response = sra.getResponse();
        }

        try {
            return point.proceed();
        } finally {
            String sqlList = Constant.THREAD_SQL.get();
            if (!StringUtils.isEmpty(sqlList)) {
                if (response != null) {
                    response.addHeader("Query-Sql", sqlList);
                }
            }
            // Destroy the data source after executing the method
            Constant.THREAD_SQL.remove();
        }
    }
}

So far, the whole business requirements have been realized. Let's explain that printing SQL on the front end will expose the structure of the database, so don't use it in the formal environment. This code is only developed on the test branch and will not be submitted to the main branch.

Topics: Java Mybatis Spring AOP