Define an interceptor in Mybatis that simply gets sql

Posted by kaze on Sun, 28 Jul 2019 19:47:56 +0200

1. What is BeanPostProcessor?When does it trigger?What can I do with it?

1. What is it?

First it is an interface that defines two methods:

public interface BeanPostProcessor {
	@Nullable //Trigger this method before all bean s are initialized
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Nullable //Trigger this method after all bean s have been initialized
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}

It defines two methods:

postProcessBeforeInitialization: bean s initialize preprocessing

postProcessAfterInitialization:bean initialization postprocessing

Note: Initialization here refers to the invocation of an instantiated bean to complete some of its initialization methods (essentially through the @PostConstruct preset initialization method). The before and after s of the above two methods are used to distinguish the trigger timing from this state.

We can define a bean that implements this interface to do something before and after initialization for other beans.

2. When does it trigger?

First look at the life cycle of spring beans (pictures from the web):

 

Figure 1

The red dot in the figure above is the trigger point for the BeanPostProcessor methods, and you can see that the triggers for these methods are in the initialization phase.

So how do you define a post processor for the initialization phase of a similar bean?Simply let a bean implement the BeanPostProcessor interface and override its before and after methods. There are many such beans. The trigger process is that any bean in the container triggers the before method of all beans that implement the BeanPostProcessor interface once before it is initialized after instantiation, and then after initializationThe after method of all beans that implement the BeanPostProcessor interface is triggered once, that is, when spring starts, it preloads the object that implements the interface (registers such beans through the registerBeanPostProcessors method), so that any other beans can be initially loaded through beforeGood logic, trigger one by one (of course, if you want to ensure the order of implementation, you can also define the order of triggers by implementing the Order interface).

3. What can I do with it?

Knowing when it triggers, what can it usually do?Generally speaking, you can use it to do some generic bean property injection. Here is an example of how it works and scenarios.

 

2. Use BeanPostProcessor to add an interceptor to all SqlSessionFactory objects

Now define an interceptor in Mybatis that simply gets the sql and prints it out, which takes time to execute:

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

    @Override
    public Object intercept(Invocation invocation) throws Throwable { //Intercept every sql execution
        Object target = invocation.getTarget();
        StatementHandler statementHandler = (StatementHandler) target;
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql(); //Get sql
        long start = System.currentTimeMillis();
        try {
            return invocation.proceed(); //sql run
        } catch (Throwable t) {
            System.out.println(String.format("error SQL=%s", sql));
            throw t;
        } finally {
            System.out.println(String.format("time consuming%s ms, SQL=%s", (System.currentTimeMillis() - start), sql));
        }

    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

Mybatis's interceptor needs to be set up in advance to SqlSessionFactory:

@Bean(name = "sqlSession")
    public SqlSessionFactory sqlSession(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setVfs(SpringBootVFS.class);
        bean.getObject().getConfiguration().addInterceptor(new SqlInterceptor()); //Join manually
        return bean.getObject();
    }

If there are many project modules, but this interceptor also requires that all SqlSessionFactories be valid for all projects. It is too cumbersome to change the SqlSessionFactory type bean s in each project. At this time, you can define a BeanPostProcessor in the public module to do this, for example, you can defineThis is the following:

@Slf4j
public class SqlSessionFactoryBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof SqlSessionFactory) { //All bean s will enter this method after initialization, at which point you need to filter out the type you want, such as this time simply getting an object of type SqlSessionFactory to set an interceptor on it
            SqlSessionFactory nowBean = (SqlSessionFactory) bean;
            nowBean.getConfiguration().addInterceptor(new SqlInterceptor(nowBean //Set Interceptor
                .getConfiguration()
                .getEnvironment()
                .getDataSource()));
        }
        return bean; //Return when finished, either directly into the container or execute another BeanPostProcessor
    }
}

Then define it as a bean, which itself is a bean, so that it can be scanned and loaded by spring, otherwise simply implementing the BeanPostProcessor interface spring cannot be perceived as managed:

@ConditionalOnClass({SqlSessionFactory.class}) //Loading of the following bean s will only be triggered if the SqlSessionFactory type exists
public class MysqlAutoConfiguration {
    @Bean
    public SqlSessionFactoryBeanPostProcessor sqlSessionFactoryBeanPostProcessor() {
        return new SqlSessionFactoryBeanPostProcessor();
    }
}

This way, you don't have to change the SqlSessionFactory objects one by one. As long as the public module is introduced, after the bean s are initialized, you will walk through this logic, filter out the types you want, and modify them, so that all SqlSessionFactories will initialize SqlSessio elsewhere without modifying it.In the case of nFactory code, it takes effect globally.

Topics: Java SQL Spring Mybatis