MyBatis Plus插件機制與執(zhí)行流程原理分析詳解
MyBatis Plus插件
MyBatis Plus提供了分頁插件PaginationInterceptor、執(zhí)行分析插件SqlExplainInterceptor、性能分析插件PerformanceInterceptor以及樂觀鎖插件OptimisticLockerInterceptor。
Mybatis 通過插件 (Interceptor) 可以做到攔截四大對象相關(guān)方法的執(zhí)行 ,根據(jù)需求完成相關(guān)數(shù)據(jù)的動態(tài)改變。
四大對象是:
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
四大對象的每個對象在創(chuàng)建時,都會執(zhí)行interceptorChain.pluginAll(),會經(jīng)過每個插件對象的 plugin()方法,目的是為當前的四大對象創(chuàng)建代理。代理對象就可以攔截到四大對象相關(guān)方法的執(zhí)行,因為要執(zhí)行四大對象的方法需要經(jīng)過代理 。
① xml下插件的配置
如下所示:
<bean id="sqlSessionFactoryBean" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean"> <!-- 數(shù)據(jù)源 --> <property name="dataSource" ref="dataSource"> </property> <property name="configLocation" value="classpath:mybatis-config.xml"> </property> <!-- 別名處理 --><property name="typeAliasesPackage" value="com.jane.mp.beans"> </property> <!-- 注入全局MP策略配置 --><property name="globalConfig" ref="globalConfiguration"> </property> <!-- 插件注冊 --><property name="plugins"><list><!-- 注冊分頁插件 --> <bean class="com.baomidou.mybatisplus.plugins.PaginationInterceptor"> </bean> <!-- 注冊執(zhí)行分析插件 --><bean class="com.baomidou.mybatisplus.plugins.SqlExplainInterceptor"> <property name="stopProceed" value="true"></property></bean> <!-- 注冊性能分析插件 --> <bean class="com.baomidou.mybatisplus.plugins.PerformanceInterceptor"><property name="format" value="true"> </property> <!-- <property name="maxTime" value="5"></property> --></bean> <!-- 注冊樂觀鎖插件 --> <bean class="com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor"></bean> </list></property></bean>
② springboot下注冊插件
這里以分頁插件為例:
@Bean public PaginationInterceptor paginationInterceptor()
{
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 設(shè)置請求的頁面大于最大頁后操作, true調(diào)回到首頁,false 繼續(xù)請求 默認false
// paginationInterceptor.setOverflow(false);
// 設(shè)置最大單頁限制數(shù)量,默認 500 條,-1 不受限制
// paginationInterceptor.setLimit(500);
// 開啟 count 的 join 優(yōu)化,只針對部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
③ SqlExplainInterceptor
SQL執(zhí)行分析攔截器,全類名是com.baomidou.mybatisplus.plugins.SqlExplainInterceptor,只支持 MySQL5.6.3以上版本。
該插件的作用是分析 DELETE UPDATE語句 ,防止小白或者惡意進行DELETE UPDATE全表操作,不建議在生產(chǎn)環(huán)境中使用會造成性能下降,
在插件的底層通過SQL語句分析命令 Explain 分析當前的 SQL語句,根據(jù)結(jié)果集中的 Extra列來斷定當前是否全表操作。
④ 性能分析插件
性能分析攔截器,全類名是com.baomidou.mybatisplus.plugins.PerformanceInterceptor,用于輸出每條 SQL 語句及其執(zhí)行時間。SQL性能執(zhí)行分析 ,開發(fā)環(huán)境使用 超過指定時間,停止運行。
⑤ 樂觀鎖插件
全類名是com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor。如果想實現(xiàn)如下需求 : 當要更新一條記錄的時候,希望這條記錄沒有被別人更新,就可以使用該插件進行判斷。
樂觀鎖的實現(xiàn)原理(@Version 用于注解實體字段,必須要有) :
- 取出記錄時,獲取當前 version
- 更新時,帶上這個version
- 執(zhí)行更新時,set version = yourVersion+1 where version = yourVersion
- 如果 version不對,就更新失敗
【2】獲取sqlSessionFactoryBean
如下圖所示,在系統(tǒng)啟動時會初始化定義的bean。DefaultListableBeanFactory.preInstantiateSingletons方法中會從beanDefinitionNames中獲取bean name然后依次創(chuàng)建。
這里可以看到RootBeanDefinition是com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean。

① 獲取bean的過程中bean屬性
如下所示,在getBean過程中可以看到bean的屬性:

② createBean
第一次獲取bean的時候會走到AbstractAutowireCapableBeanFactory.createBean進行bean的創(chuàng)建。
/** 創(chuàng)建一個bean實例,為bean實例設(shè)置屬性值,調(diào)用post-processors-bean后置處理器 */@Overrideprotected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args)
throws BeanCreationException
{//...暫時忽略其他代碼
try {// 這里會首先觸發(fā)BeanPostProcessors ,如果這里能獲取到bean則直接返回,不再走doCreateBean
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null)
{
return bean;
} }//...暫時忽略其他代碼
在AbstractAutowireCapableBeanFactory.resolveBeforeInstantiation中就會分別執(zhí)行bean后置處理器的前置和后置方法。
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd)
{ Object bean = null;
if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved))
{
// Make sure bean class is actually resolved at this point.
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors())
{
Class<?> targetType = determineTargetType(beanName, mbd);
if (targetType != null) {
bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
if (bean != null)
{
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
}
}
mbd.beforeInstantiationResolved = (bean != null);
}
return bean;
}
執(zhí)行后置處理器的前置方法如下所示:

③ doCreateBean
AbstractAutowireCapableBeanFactory.doCreateBean是創(chuàng)建bean的核心方法,這會為bean屬性賦值并會觸發(fā)bean后置處理器、InitializingBean以及自定init方法等。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException
{
// Instantiate the bean.--實例化beanBeanWrapper instanceWrapper = null;if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null)
{//獲取bean的包裝對象-這里很重要 instanceWrapper = createBeanInstance(beanName, mbd, args);}final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);mbd.resolvedTargetType = beanType;
// Allow post-processors to modify the merged bean definition.synchronized (mbd.postProcessingLock)
{ if (!mbd.postProcessed)
{ try {//調(diào)用MergedBeanDefinitionPostProcessors的postProcessMergedBeanDefinition方法 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
} catch (Throwable ex)
{
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex);
} mbd.postProcessed = true; }}//...//這里暫時忽略其他代碼
// Initialize the bean instance.--初始化bean實例Object exposedObject = bean;try {//如下方法很重要 populateBean(beanName, mbd, instanceWrapper); if (exposedObject != null)
{
exposedObject = initializeBean(beanName, exposedObject, mbd);
}}//...
④ populateBean
顧名思義,為bean實例屬性賦值。
AbstractAutowireCapableBeanFactory.populateBean
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {//獲取屬性值列表PropertyValues pvs = mbd.getPropertyValues();
//...該種符號表示暫時忽略其他代碼
//在為bean屬性賦值前,給InstantiationAwareBeanPostProcessors 機會修改bean的狀態(tài)//應(yīng)用場景如支持字段注入boolean continueWithPropertyPopulation = true;
//循環(huán)調(diào)用InstantiationAwareBeanPostProcessors 的postProcessAfterInstantiation方法if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { continueWithPropertyPopulation = false; break; } } }}
if (!continueWithPropertyPopulation) { return;}//解析autowire注解字段,進行主動注入if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
// Add property values based on autowire by name if applicable. if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); }
// Add property values based on autowire by type if applicable. if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); }
pvs = newPvs;}boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);//循環(huán)調(diào)用InstantiationAwareBeanPostProcessors 的postProcessPropertyValues方法if (hasInstAwareBpps || needsDepCheck) { PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); if (hasInstAwareBpps) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvs == null) { return; } } } } if (needsDepCheck) { checkDependencies(beanName, mbd, filteredPds, pvs); }}//在這里為屬性賦值,會進行類型轉(zhuǎn)換,這里注意關(guān)鍵詞deep copy//如果是引用類型且bean沒有存在,則會進行bean的創(chuàng)建過程applyPropertyValues(beanName, mbd, bw, pvs);}
如下圖所示在創(chuàng)建sqlSessionFactoryBean過程中會創(chuàng)建其屬性globalConfiguration對象:

如下圖所示在創(chuàng)建sqlSessionFactoryBean過程中(從左側(cè)的方法順序就可以看出來)會創(chuàng)建其屬性PaginationInterceptor對象:

如下圖所示在創(chuàng)建sqlSessionFactoryBean過程中(從左側(cè)的方法順序就可以看出來)會創(chuàng)建其屬性SqlExplainInterceptor對象:

如下圖所示在創(chuàng)建sqlSessionFactoryBean過程中(從左側(cè)的方法順序就可以看出來)會創(chuàng)建其屬性PerformanceInterceptor對象:

如下圖所示在創(chuàng)建sqlSessionFactoryBean過程中(從左側(cè)的方法順序就可以看出來)會創(chuàng)建其屬性O(shè)ptimisticLockerInterceptor對象:

⑤ initializeBean
AbstractAutowireCapableBeanFactory.initializeBean源碼如下:
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd)
{
if (System.getSecurityManager() != null)
{
AccessController.doPrivileged(new PrivilegedAction<Object>()
{
@Override public Object run()
{
invokeAwareMethods(beanName, bean);
return null;
}
}, getAccessControlContext()); } else { //調(diào)用意識/通知方法 invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic())
{ //調(diào)用bean后置處理器的前置方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
} //調(diào)用初始化方法
try {
invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable ex)
{
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
AbstractAutowireCapableBeanFactory.invokeInitMethods方法源碼如下:
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
throws Throwable {
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet")))
{
if (logger.isDebugEnabled())
{
logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
//調(diào)用InitializingBean.afterPropertiesSet
if (System.getSecurityManager() != null)
{
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>()
{
@Override public Object run() throws Exception
{
((InitializingBean) bean).afterPropertiesSet();
return null;
} }, getAccessControlContext());
}
catch (PrivilegedActionException pae)
{
throw pae.getException();
} } else { ((InitializingBean) bean).afterPropertiesSet();
} }//調(diào)用自定義初始化方法 if (mbd != null)
{
String initMethodName = mbd.getInitMethodName();
if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName))
{
invokeCustomInitMethod(beanName, bean, mbd);
}
}}
如下圖所示,MybatisSqlSessionFactoryBean同樣實現(xiàn)了InitializingBean接口。那么我們就需要注意其afterPropertiesSet方法了。

⑥ MybatisSqlSessionFactoryBean.afterPropertiesSet
如下所示,代碼很簡短只是創(chuàng)建了sqlSessionFactory。
@Overridepublic void afterPropertiesSet() throws Exception
{
notNull(dataSource, "Property 'dataSource' is required"); //sqlSessionFactoryBuilder在populateBean的applyPropertyValues過程中已經(jīng)存在!
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
進入afterPropertiesSet()方法前MybatisSqlSessionFactoryBean如下所示:

我們看一下sqlSessionFactory,這是一段很長的過程:
protected SqlSessionFactory buildSqlSessionFactory() throws Exception
{
Configuration configuration; // TODO 加載自定義 MybatisXmlConfigBuilder
MybatisXMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null)
{
configuration = this.configuration;
if (configuration.getVariables() == null)
{
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null)
{
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) { //通常如果配置了configLocation會從這里創(chuàng)建MybatisXMLConfigBuilder, //其構(gòu)造方法又創(chuàng)建了MybatisConfiguration
xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration(); } else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
// TODO 使用自定義配置
configuration = new MybatisConfiguration();
if (this.configurationProperties != null)
{
configuration.setVariables(this.configurationProperties);
} }
if (this.objectFactory != null)
{
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null)
{
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) { configuration.setVfsImpl(this.vfs);
}
if (hasLength(this.typeAliasesPackage))
{
// TODO 支持自定義通配符
String[] typeAliasPackageArray;
if (typeAliasesPackage.contains("*") && !typeAliasesPackage.contains(",")
&& !typeAliasesPackage.contains(";"))
{
typeAliasPackageArray = PackageHelper.convertTypeAliasesPackage(typeAliasesPackage);
} else { typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
}
if (typeAliasPackageArray == null) {
throw new MybatisPlusException("not find typeAliasesPackage:" + typeAliasesPackage);
} for (String packageToScan : typeAliasPackageArray)
{
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
// TODO 自定義枚舉類掃描處理
if (hasLength(this.typeEnumsPackage))
{
Set<Class> classes = null;
if (typeEnumsPackage.contains("*") && !typeEnumsPackage.contains(",")
&& !typeEnumsPackage.contains(";"))
{
classes = PackageHelper.scanTypePackage(typeEnumsPackage);
} else { String[] typeEnumsPackageArray = tokenizeToStringArray(this.typeEnumsPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
if (typeEnumsPackageArray == null)
{
throw new MybatisPlusException("not find typeEnumsPackage:" + typeEnumsPackage);
}
classes = new HashSet<Class>();
for (String typePackage : typeEnumsPackageArray)
{
classes.addAll(PackageHelper.scanTypePackage(typePackage));
}
} // 取得類型轉(zhuǎn)換注冊器
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
for (Class cls : classes) { if (cls.isEnum()) { if (IEnum.class.isAssignableFrom(cls)) {
typeHandlerRegistry.register(cls.getName(), com.baomidou.mybatisplus.handlers.EnumTypeHandler.class.getCanonicalName()); } else {
// 使用原生 EnumOrdinalTypeHandler typeHandlerRegistry.register(cls.getName(), org.apache.ibatis.type.EnumOrdinalTypeHandler.class.getCanonicalName());
}
} } }
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases)
{
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Registered type alias: '" + typeAlias + "'");
}
} }
if (!isEmpty(this.plugins))
{
for (Interceptor plugin : this.plugins)
{
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
if (hasLength(this.typeHandlersPackage))
{
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray)
{
configuration.getTypeHandlerRegistry().register(packageToScan);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers))
{
for (TypeHandler<?> typeHandler : this.typeHandlers)
{
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Registered type handler: '" + typeHandler + "'");
} } }
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (this.cache != null)
{
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null)
{
try
{
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
} } catch (Exception ex)
{
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null)
{ this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
// 設(shè)置元數(shù)據(jù)相關(guān)
GlobalConfigUtils.setMetaData(dataSource, globalConfig); SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(configuration);
// TODO SqlRunner
SqlRunner.FACTORY = sqlSessionFactory; // TODO 緩存 sqlSessionFactory globalConfig.setSqlSessionFactory(sqlSessionFactory); // TODO 設(shè)置全局參數(shù)屬性
globalConfig.signGlobalConfig(sqlSessionFactory);
if (!isEmpty(this.mapperLocations)) {
if (globalConfig.isRefresh()) {
//TODO 設(shè)置自動刷新配置 減少配置
new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2,
2, true); }
for (Resource mapperLocation : this.mapperLocations)
{
if (mapperLocation == null) {
continue;
}
try {
// TODO 這里也換了噢噢噢噢
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
}
else {
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return sqlSessionFactory;
}
上面代碼主要做了如下事情:
- 獲取MybatisXMLConfigBuilder對象
- 獲取Configuration對象-MybatisConfiguration
- 配置對象Configuration添加插件configuration.addInterceptor(plugin);
- xmlConfigBuilder.parse()對configuration做進一步處理
- 獲取SpringManagedTransactionFactory用來創(chuàng)建SpringManagedTransaction
- 獲取一個DefaultSqlSessionFactory實例對象
- globalConfig.setSqlSessionFactory(sqlSessionFactory)中會創(chuàng)建MybatisSqlSessionTemplate
- 解析mapperLocation對應(yīng)的一個個mapper配置文件,使用助手builderAssistant的addMappedStatement方法將一個個結(jié)點添加配置對象中
- 其他屬性設(shè)置等等
也就是說,在MybatisSqlSessionFactoryBean.afterPropertiesSet方法執(zhí)行結(jié)束后,SqlSessionFactory、SqlSessionTemplate、Configuration等都已存在!
【3】查詢執(zhí)行流程分析
示例代碼如下:
List<Employee > emps = employeeMapper.selectPage(page, null);
如下圖所示,此時我們獲取到的employeeMapper其實是個代理對象:


總結(jié)
到此這篇關(guān)于MyBatis Plus插件機制與執(zhí)行流程原理分析的文章就介紹到這了,更多相關(guān)MyBatis Plus插件機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring擴展之基于HandlerMapping實現(xiàn)接口灰度發(fā)布實例
這篇文章主要介紹了Spring擴展之基于HandlerMapping實現(xiàn)接口灰度發(fā)布實例,灰度發(fā)布是指在黑與白之間,能夠平滑過渡的一種發(fā)布方式,灰度發(fā)布可以保證整體系統(tǒng)的穩(wěn)定,在初始灰度的時候就可以發(fā)現(xiàn)、調(diào)整問題,以保證其影響度,需要的朋友可以參考下2023-08-08
關(guān)于mybatis-plus插件使用時的一些問題小結(jié)
這篇文章主要給大家介紹了關(guān)于mybatis-plus插件使用時的一些問題的相關(guān)資料,文中通過實例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2022-03-03
Java中使用StackWalker和Stream API進行堆棧遍歷
StackWalking API是添加到Java中最酷的(并且對大多數(shù)開發(fā)人員來說完全不切實際,一般不會用,除非深層跟蹤調(diào)優(yōu))的功能之一。在這篇簡短的文章中,我們將看到它是什么以及使用它有多么容易,很快的認識它2018-09-09
在Java中動態(tài)執(zhí)行字符串代碼的方法小結(jié)
在Java編程中,靜態(tài)編譯的特性通常不允許我們直接執(zhí)行運行時生成的代碼,然而,有時我們需要動態(tài)地生成并執(zhí)行代碼片段,本文將詳細介紹如何在Java中運行一段字符串代碼,并提供詳細的代碼案例和運行結(jié)果,需要的朋友可以參考下2024-08-08
Java設(shè)置PDF跨頁表格重復(fù)顯示表頭行的步驟詳解
這篇文章主要給大家介紹了關(guān)于Java設(shè)置PDF跨頁表格重復(fù)顯示表頭行的相關(guān)資料,這里使用的是Free Spire.PDF for Java的jar包,Spire.PDF for Java 是一款專門對 PDF 文檔進行操作的 Java 類庫,需要的朋友可以參考下2021-07-07
非maven項目快速轉(zhuǎn)換為maven項目的方法步驟
時候我們導(dǎo)入的項目并不是有maven來管理依賴的,而是要手動添加jar包,比較麻煩,本文主要介紹了非maven項目快速轉(zhuǎn)換為maven項目的方法步驟,具有一定的參考價值,感興趣的可以了解一下2024-01-01

