Mybatis plug-in development
1. Perform process analysis
1. Parse the configuration file
Mybatis configuration file structure before understanding the parsing process:
mybatis-config.xml
<configuration>
<properties/>
<settting/>
<typeHandlers/> <.... /> <mappers/> </configuration>Copy the code
mybatis-mapper.xml
<mapper >
<cache/>
<resultMap/>
<select/>
<update/>
<delete/>
<insert/>
</mapper>
Copy the code
The parsing process of the configuration file is to convert the above XML description elements into corresponding Java objects. The final converted objects and their relationship are shown as follows:
Configure the element resolution builder
>org.apache.ibatis.builder.xml.XMLConfigBuilder
>org.apache.ibatis.builder.xml.XMLMapperBuilder
>org.apache.ibatis.builder.xml.XMLStatementBuilder
>org.apache.ibatis.builder.SqlSourceBuilder
>org.apache.ibatis.scripting.xmltags.XMLScriptBuilder
>org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
Copy the code
SQL Statement build process source code
>org.apache.ibatis.session.SqlSessionFactoryBuilder#build()/ / 1. Config. The XML document parsing > org. Apache. Ibatis. Builder. XML. XMLConfigBuilder#parse
>org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
>org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement/ / 2. Mapper. The XML document parsing > org. Apache. Ibatis. Builder. XML. XMLMapperBuilder#parse
>org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
>org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)/ / 3. Statemen SQL block parsing > org. Apache. Ibatis. Builder. XML. XMLStatementBuilder#parseStatementNode
>org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement/ / 4. Dynamic SQL script > org. Apache. Ibatis. Scripting. Xmltags. XMLLanguageDriver#createSqlSource()
>org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode()
>org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags()
Copy the code
2. Create an SqlSession
First of all, let’s take a look at the composition structure of the session object as shown below:
Session build source code parsing
>org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession(boolean)
>org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
>org.apache.ibatis.transaction.TransactionFactory#newTransaction
>org.apache.ibatis.session.Configuration#newExecutor> org. Apache. Ibatis. Executor. SimpleExecutor > org. Apache. Ibatis. Executor. CachingExecutor / / actuator plug-in packaging >org.apache.ibatis.plugin.InterceptorChain#pluginAll(executor)
>org.apache.ibatis.session.defaults.DefaultSqlSession#DefaultSqlSession()
Copy the code
3. Run the StatementHandler method
StatementHandler source code parsing
>org.apache.ibatis.session.defaults.DefaultSqlSession#selectList()
>org.apache.ibatis.executor.CachingExecutor#query()
>org.apache.ibatis.executor.BaseExecutor#query()
>org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
>org.apache.ibatis.session.Configuration#newStatementHandler
org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler
org.apache.ibatis.session.Configuration#newParameterHandler
org.apache.ibatis.plugin.InterceptorChain#pluginAll(parameterHandler)
org.apache.ibatis.session.Configuration#newResultSetHandler
org.apache.ibatis.plugin.InterceptorChain#pluginAll(resultSetHandler)
>org.apache.ibatis.plugin.InterceptorChain#pluginAll(statementHandler)
>org.apache.ibatis.executor.BaseExecutor#getConnection
>org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement
>org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize
>org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
org.apache.ibatis.type.BaseTypeHandler#setParameter
org.apache.ibatis.type.UnknownTypeHandler#setNonNullParameter
org.apache.ibatis.type.IntegerTypeHandler#setNonNullParameter
Copy the code
Second, myBatis plug-in development
Four extension points for plug-ins
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
Paging plug-in implementation: after the user declares the Page object implementation in the interface, the plug-in realizes automatic paging. The following is an example:
public class Page implements java.io.Serializable { private int szie; // private int number; // current page number}Copy the code
Page parameter declaration
@Select("select * from user")
List<User> selectByPage(String name, Page page);
Copy the code
Client call
mapper.selectByPage("Xiao Ming", new Page(3, 2))
select * from user limit10, 20Copy the code
Achieve goal decomposition
- Modify Modify the SQL and add the limit statement
- Determines if there is a Page object in the method argument
- Take the Page object and generate the limit statement
- The above operations must be completed before the PreparedStatement object is generated
The complete code is as follows:
package com.niuh.mybatis.dao; import com.sun.deploy.util.ReflectionUtil; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.statement.BaseStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Map; import java.util.Optional; import java.util.Properties; /** * @author: hejianhui * @create: The 2019-07-13 he * @ see PagePlugin * @ since JDK1.8 * / public class PagePlugin implements Interceptor {@ Override public Object intercept(Invocation invocation) throws Throwable {return null;
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Proxy.newProxyInstance(PagePlugin.class.getClassLoader(),
new Class[]{StatementHandler.class},
new PageHandler((StatementHandler) target));
}
return target;
}
@Override
public void setProperties(Properties properties) { } private class PageHandler implements InvocationHandler { StatementHandler handler; public PageHandler(StatementHandler handler) { this.handler = handler; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (method.getName().equalsIgnoreCase("prepare")) {
setParametersProxy();
}
return method.invoke(handler, args);
}
private void setParametersProxy() {
if(handler.getBoundSql().getParameterObject() instanceof Map) { ((Map) handler.getBoundSql().getParameterObject()).values().stream() .filter(a -> a instanceof Page) .findFirst() .ifPresent( page -> { appendPageSql((Page) page); }); } } private void appendPageSql(Page page) { try { BoundSql sql = handler.getBoundSql(); sql.getSql(); Stringlimit = String.format(" limit %s,%s", page.getBegin(), page.getSzie());
setFileValue("sql", sql, sql.getSql() + limit);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private Object getFileValue(String fileName, Object target) throws NoSuchFieldException, IllegalAccessException {
Field sqlField = target.getClass().getDeclaredField(fileName);
sqlField.setAccessible(true);
return sqlField.get(target);
}
private Object setFileValue(String fileName, Object target, Object value) throws NoSuchFieldException, IllegalAccessException {
Field sqlField = target.getClass().getDeclaredField(fileName);
sqlField.setAccessible(true);
sqlField.set(target, value);
returnsqlField.get(target); }}Copy the code