In-depth analysis of Mybatis Plus dynamic SQL statement implementation mechanism
Mybatis-Plus (MP for short) is a Mybatis enhancement tool, so how is it enhanced? The fact is that it already encapsulates some CRUD methods, so development doesn’t need to write XML, just call these methods, similar to JPA. This article will look at the following implementation of MP to see how these enhancements are implemented.
Entrance class: MybatisSqlSessionFactoryBuilder
Through the entrance class MybatisSqlSessionFactoryBuilder# build method, the application starts up, the mybatis plus (MP) custom dynamic configuration XML file into mybatis.
public class MybatisSqlSessionFactoryBuilder extends SqlSessionFactoryBuilder {
public SqlSessionFactory build(Configuration configuration) {
/ /... Omit several lines
if (globalConfig.isEnableSqlRunner()) {
new SqlRunnerInjector().inject(configuration);
}
/ /... Omit several lines
returnsqlSessionFactory; }}Copy the code
There are two MP2 function classes involved
- MybatisConfiguration extends MybatisConfiguration class inherited from Mybatis: MP dynamic script building, registration, and other logical judgments.
- SqlRunnerInjector: MP XML script method that inserts some dynamic methods by default.
MybatisConfiguration class
Here we focus on the analysis of MybatisConfiguration class, in MybatisConfiguration, MP initializes its own MybatisMapperRegistry, MybatisMapperRegistry is the registry for MP to load custom SQL methods.
Many of the methods in MybatisConfiguration are implemented using the MybatisMapperRegistry rewrite
Three overloaded methods, addMapper, implement the function of registering MP dynamic scripts.
public class MybatisConfiguration extends Configuration {
/**
* Mapper 注册
*/
protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
/ /...
/** * initializes the call */
public MybatisConfiguration(a) {
super(a);this.mapUnderscoreToCamelCase = true;
languageRegistry.setDefaultDriverClass(MybatisXMLLanguageDriver.class);
}
/** * MybatisPlus; SQL * 2. SQL
* 3. XmlSql and SqlProvider cannot contain the same SQL
* after adjustment SQL priority: XmlSql > sqlProvider > CurdSql
*/
@Override
public void addMappedStatement(MappedStatement ms) {
// ...
}
/ /... Omit several lines
/** * Use your own MybatisMapperRegistry */
@Override
public <T> void addMapper(Class<T> type) {
mybatisMapperRegistry.addMapper(type);
}
/ /... Omit several lines
}
Copy the code
In MybatisMapperRegistry, MP will mybatis MapperAnnotationBuilder replaced with MP own MybatisMapperAnnotationBuilder
public class MybatisMapperRegistry extends MapperRegistry {
@Override
public <T> void addMapper(Class<T> type) {
/ /... Omit several lines
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
/ /... Omit several lines}}Copy the code
In MybatisMapperRegistry addMapper method of a class, real MybatisMapperAnnotationBuilder into MP at the core of the class, MybatisMapperAnnotationBuilder this class is the key to achieve dynamic script MP classes.
MybatisMapperAnnotationBuilder dynamically constructed
Core classes in MP MybatisMapperAnnotationBuilder parser method, MP one traversal Mapper classes to be loaded, loaded method includes the following
public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
@Override
public void parse(a) {
/ /... Omit several lines
for (Method method : type.getMethods()) {
/** for loop code, MP check method is not@Select @InsertEtc mybatis annotation method **/
parseStatement(method);
InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);
SqlParserHelper.initSqlParserInfoCache(mapperName, method);
}
/** These two lines of code, MP injects the default method list **/
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
/ /... Omit several lines
}
@Override
public void inspectInject(MapperBuilderAssistant builderAssistant, Class
mapperClass) { Class<? > modelClass = extractModelClass(mapperClass);/ /... Omit several lines
List<AbstractMethod> methodList = this.getMethodList(mapperClass);
TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
// Loop to inject custom methodsmethodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); mapperRegistryCache.add(className); }}public class DefaultSqlInjector extends AbstractSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class
mapperClass) {
return Stream.of(
new Insert(),
/ /... Omit several lines
newSelectPage() ).collect(toList()); }}Copy the code
In MybatisMapperAnnotationBuilder, MP real frame custom dynamic SQL statements to registration to Mybatis engine. AbstractMethod, on the other hand, implements SQL statement construction for concrete methods.
Concrete AbstractMethod instance classes that construct concrete method SQL statements
Take the SelectById class as an example
/** * Query data by ID */
public class SelectById extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class
mapperClass, Class
modelClass, TableInfo tableInfo) {
/** define mybatis XML method id as
**/
="xyz">
SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
/** Construct the specific XML fragment corresponding to the id **/
SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(),
sqlSelectColumns(tableInfo, false),
tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
tableInfo.getLogicDeleteSql(true.true)), Object.class);
/** Add the XML method method to mybatis MappedStatement **/
return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo); }}Copy the code
Mybatis ${variable} #{variable} dynamic replacement and precompilation, has entered mybatis own function.
To summarize
MP rewrites and replaces more than ten classes of Mybatis in total, as shown in the following figure:
The increase of the overall, MP implementation mybatis means slightly complicated and not intuitive, but according to MybatisMapperAnnotationBuilder structural approach from the definition of the XML file, transform it into mybatis Resource resources, Mybatis class: SqlSessionFactoryBean
public class YourSqlSessionFactoryBean extends SqlSessionFactoryBean implements ApplicationContextAware {
private Resource[] mapperLocations;
@Override
public void setMapperLocations(Resource... mapperLocations) {
super.setMapperLocations(mapperLocations);
/** Mapper XML file path **/
this.mapperLocations = mapperLocations;
}
/ * * * {@inheritDoc} * /
@Override
public void afterPropertiesSet(a) throws Exception {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
/** Mybatis can implement the symbiotic relationship between MP's custom dynamic SQL and native SQL **/
this.setMapperLocations(InjectMapper.getMapperResource(this.dbType, beanFactory, this.mapperLocations));
super.afterPropertiesSet(); }}Copy the code
In this article, the realization process of MP dynamic statement is briefly introduced, and a possible more convenient method is given. In the next article, we will introduce fluent Mybatis, another myBatis enhancement framework, how to achieve dynamic statements without rewriting any system methods of Mybatis.
Fluent Mybatis introduction and source code
Using FluentMybatis to achieve MyBatis dynamic SQL assembly and Fluent API syntax
Complex nested queries using Fluent Mybatis zero XML configuration
Fluent Mybatis source code, Github