Mybatis-Plus BaseMapper自動生成SQL及MapperProxy


Spring+Mybatis + Mybatis-Plus 自定義無XML的sql生成及MapperProxy代理生成

問題產生背景

現在新服務ORM框架是使用mybatis3.4.6mybatis-plus2.2.0

最近在項目中偶然發現CouponRecord實體類中增加了這樣一行代碼如下,導致在Service中調用this.selectCount出現NPE。當然出現NPE很好解決,直接判斷下是否為null就OK了。

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("coupon_record")
public class CouponRecord {
    ...
    @TableField(value = "product_quantity")
    private BigDecimal productQuantity;
    public BigDecimal getProductQuantity() {
        // 提交上的代碼
        return this.productQuantity.setScale(2, RoundingMode.HALF_DOWN);
        // 解決方式如下
        //return this.productQuantity == null ? null : this.productQuantity.setScale(2, RoundingMode.HALF_DOWN);
    }
    ...
}

調用鏈:CouponRecordServiceImpl#count->ServiceImpl#selectCount->BaseMapper#selectCount,主要代碼如下:

ServiceImpl的部分代碼如下:

public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
    @Autowired
    protected M baseMapper;
    ...
    @Override
    public int selectCount(Wrapper<T> wrapper) {
        return SqlHelper.retCount(baseMapper.selectCount(wrapper));
    }
    ...
}

BaseMapper所有接口如下:

public interface BaseMapper<T> {
    Integer insert(T entity);
    Integer insertAllColumn(T entity);
    Integer deleteById(Serializable id);
    Integer deleteByMap(@Param("cm") Map<String, Object> columnMap);
    Integer delete(@Param("ew") Wrapper<T> wrapper);
    Integer deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
    Integer updateById(@Param("et") T entity);
    Integer updateAllColumnById(@Param("et") T entity);
    Integer update(@Param("et") T entity, @Param("ew") Wrapper<T> wrapper);
    T selectById(Serializable id);
    List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
    List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
    T selectOne(@Param("ew") T entity);
    Integer selectCount(@Param("ew") Wrapper<T> wrapper);
    List<T> selectList(@Param("ew") Wrapper<T> wrapper);
    List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> wrapper);
    List<Object> selectObjs(@Param("ew") Wrapper<T> wrapper);
    List<T> selectPage(RowBounds rowBounds, @Param("ew") Wrapper<T> wrapper);
    List<Map<String, Object>> selectMapsPage(RowBounds rowBounds, @Param("ew") Wrapper<T> wrapper);
}

我們在業務代碼CouponRecordServiceImpl#count中直接調用,可能會產生如下疑問?

  • 我們沒有配置XML為什么調用selectCount可以查詢?既然可以查詢那么生成的SQL長成什么樣子?
  • 通過看ServiceImpl中的代碼,會發現是直接注入baseMapper,baseMapper明明是接口咋個就可以使用了呢?

對於工作了這么多年的老司機,猜也猜的出百分之八九十吧。在整理這篇文章之前,以前瀏覽過,我確實忘記的差不多了。感謝公司能提供給大家不管是組內分享還是部門分享機會,分享總會給自己和他人的很大進步。不扯淡這些了。下面將對此這些疑問來逐一解決。但是這里要說明下,這里只看我們關心的內容,其他比如在與spring整合后有些為什么要這樣寫,可以找學習spring組來做分享或者后面整理好文章后在分享。

框架是如何使用

任何框架學習,首先要會用,不然就是扯淡。框架都是在實際的應用中逐漸抽象出來的,簡化我們工作。

Service主要代碼如下:

@Service
public class CouponRecordService extends ServiceImpl<CouponRecordDao, CouponRecord> {
    public int count(Date endTime) {
        CouponRecord conditionCouponRecord = CouponRecord.builder().status(CouponStatus.USED).isDelete(YesNo.NO.getValue()).build();
        return selectCount(new EntityWrapper<>(conditionCouponRecord).le("create_time", endTime).isNotNull("order_no"));
    }
}

Dao(或者叫Mapper)

public interface CouponRecordDao extends BaseMapper<CouponRecord> {
}

spring的相關配置如下:

<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>

    <!-- 自動掃描entity目錄, 省掉Configuration.xml里的手工配置 -->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath*:mapper/**/*.xml"/>
    <property name="plugins">
        <array>
            <!-- 分頁插件配置 -->
            <bean id="paginationInterceptor" class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">
                <property name="dialectType" value="mysql"/>
            </bean>
            <bean id="limitInterceptor" class="com.common.mybatis.LimitInterceptor"/>
        </array>
    </property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.merchant.activity.**.dao"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<context:component-scan base-package="com.common.**,com.merchant.activity.**">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

用法+大致配置就是這樣的。接下來看看這些無Xml的SQL是怎么生成的以及生成出來的SQL長成什么樣?

無Xml的SQL是如何生成生成及SQL長成什么樣

在如何使用中,可以看到XML中有如下一段配置:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.merchant.activity.**.dao"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

這段的配置作用就是掃描我們的Mapper或者Dao的入口。

大概類圖如下:

接下來對源碼做分析

BeanDefinition解析階段

MapperScannerConfigurer

MapperScannerConfigurer得繼承關系如下圖:

從圖中看出MapperScannerConfigurer實現了我們關注的BeanDefinitionRegistryPostProcessor、InitializingBean接口,Spring在初始化Bean的時候會執行對應的方法。

ClassPathMapperScanner構造

構造ClassPathMapperScanner掃描類,掃描basePackage包下的Mapper或者Dao並注冊我們的Mapper Bean到容器中.

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  ...
  @Override
  public void afterPropertiesSet() throws Exception {
      // 驗證是否配置了basePackage
    notNull(this.basePackage, "Property 'basePackage' is required");
  }

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // left intentionally blank
  }

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    // 是否有占位符,處理之
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    // 掃描
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    // 注冊一些過濾器,包括和不包括。有部分可以在xml中配置,比如:annotationClass、markerInterface
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
  ...
}
ClassPathMapperScanner#scan

掃描類並生成BeanDefinition注入到Spring容器中,注意這里的ClassPathMapperScanner繼承ClassPathBeanDefinitionScanner,在ClassPathMapperScanner中未實現scan,所以直接調用父類的scan方法。為了便於閱讀這里將源碼中的日志刪除了。大致源碼如下:

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
    ...
    public int scan(String... basePackages) {
        // 獲取之前容器中bean的數量
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
		// 真正干事的---掃描, 調用子類ClassPathMapperScanner#doScan(basePackages)方法
		doScan(basePackages);
		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}
		// 返回注冊bean的數量
		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}
    // 真正干事的掃描 生成BeanDefinition集合
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
        // BeanDefinitionHolder 的集合
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
		for (String basePackage : basePackages) {
            // 通過查找候選bean定義
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            // 遍歷進行部分邏輯處理
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                // 設置作用域
				candidate.setScope(scopeMetadata.getScopeName());
                // 生成beanName
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
                    // 增加默認值,autowireCandidate
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
                // 注冊BeanDefinition到容器中。
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
    ...
}
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
      // 調用父類的ClassPathBeanDefinitionScanner#doScaner(basePackages)方法,掃描生產BeanDefinitionHolder集合
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      // MapperBean 需要一些額外的處理,查看這個方法
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
  
    //對每個Mapper的BeanDefinition定義處理, 
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      // 構造器參數,下一行代碼將Bean設置為MapperFactoryBean,MapperFactoryBean的構造器中有個參數是mapperInterface
  	  definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 
      // 這一步非常重要,把我們的Bean設置為MapperFactoryBean,接下來會看到MapperFactoryBean的繼承關系
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      // 在bean中增加sqlSessionFactory
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }
	  // 在bean中增加sqlSessionTemplate
      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }
      // 設置自動注入模式
      if (!explicitFactoryUsed) {
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }
}

寫到代碼的注釋可能都不怎么關注,這里再次強調下重點,如果不注意后續可能有些會懵逼的。這是怎么來的。

  1. BeanDefinition的class設置為MapperFactoryBean
  2. 將原始mapper的接口類型以MapperFactoryBean構造器的參數傳入,也就是后面你將看到參數是mapperInterface.

BeanDefinition初始化階段

MapperFactoryBean

經過上面的掃描並注冊,現在容器中已經存在了我們的Mapper Bean了,在上面的說構建Mapper BeanDefinition的時候注意這些BeanDefinition的class類型設置為了MapperFactoryBean,先看看MapperFactoryBean的繼承關系如下:

從圖中,看出MapperFactoryBean是實現了InitializingBean接口。DaoSupport對afterPropertiesSet()實現了。我們都知道Spring在初始化會Bean的時候將會調用afterPropertiesSet()方法。那么看看這個方法干了什么事

public abstract class DaoSupport implements InitializingBean {
	protected final Log logger = LogFactory.getLog(getClass());
	@Override
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// 檢查Dao配置
		checkDaoConfig();
		// Let concrete implementations initialize themselves.
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}
	protected abstract void checkDaoConfig() throws IllegalArgumentException;
	protected void initDao() throws Exception {
	}
}

一看典型的模板設計模式,真正處理在子類中。這里我們關心的是checkDaoConfig(),看看子類MapperFactoryBean#checkDaoConfig實現干了些什么事

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  ...
  protected void checkDaoConfig() {
    super.checkDaoConfig();//調用父類的方法,父類就是檢查sqlSession是否為null。null的話拋出異常
    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    // 通過sqlSession獲取MybatisConfiguration,相當於我們每一個MapperBean都是由SqlSession的,否則你想咋個查詢呢
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        // 將mapperInterface注冊到configuration中。
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }
  ...
}

MybatisConfiguration#addMapper干的就是將類型注冊到我們Mapper容器中,便於后續取

public class MybatisConfiguration extends Configuration {
    ...
    public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
    public <T> void addMapper(Class<T> type) {
        mybatisMapperRegistry.addMapper(type);
    }
    ...
}

接下來就要看看MybatisMapperRegistry#addMapper注冊到底干了何事。猜猜應該就是自定義無XML的sql生產注入。哪些是自定義?就是我們BaseMapper中的那一堆方法。

XXXRegistry 類的名字起的真好,看名字就是一個注冊器。這里的注冊器有一箭雙雕的作用

  1. 定義了一個Map,緩存所知道的Mapper,后面初始化MapperProxy代理用的着,不然后面不好取哦
  2. 將解析出來的SQL,注冊到Configuration中
public class MybatisMapperRegistry extends MapperRegistry {
    ...
    // 這個knownMappers之前以為起的不夠好。。當再次看的時候發現還真不錯,known翻譯就是眾所周知,那么在這里就是我們已經掃描並且已經注冊了的Mapper了,在內部來說當然是都知道的。
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            // 注入過就不再執行了。
            if (hasMapper(type)) {
                return;
            }
            boolean loadCompleted = false;
            try {
                // 這里先記着,后面查看我們MapperProxy代理用的着哦
                knownMappers.put(type, new MapperProxyFactory<>(type));
                // mybatisMapper注解構建器
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                // 解析
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
    ...
}
MybatisMapperAnnotationBuilder#parse

接下來將是生成無xml對應的SQL了。😄😄

public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
    ...
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            // 加載xml
            loadXmlResource();
            configuration.addLoadedResource(resource);
            assistant.setCurrentNamespace(type.getName());
            parseCache();
            parseCacheRef();
            Method[] methods = type.getMethods();
            // 類型是否是BaseMapper
            if (BaseMapper.class.isAssignableFrom(type)) {
                GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
            }
            for (Method method : methods) {
                try {
                    // issue #237
                    if (!method.isBridge()) {
                        parseStatement(method);
                    }
                } catch (IncompleteElementException e) {
                    configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
            }
        }
        parsePendingMethods();
    }
    ...
}

在上面的parse方法中,我們重點關心GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);獲取SQL注入器,再根據類型type生成sql注入

public class AutoSqlInjector implements ISqlInjector {
    // 注入到builderAssistant
     public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        String className = mapperClass.toString();
        Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
         // 判斷之前是否注入過
        if (!mapperRegistryCache.contains(className)) {
            // 注入
            inject(builderAssistant, mapperClass);
            // 加入到緩存中
            mapperRegistryCache.add(className);
        }
    }
    
    public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        this.configuration = builderAssistant.getConfiguration();
        this.builderAssistant = builderAssistant;
        this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
        /**
         * 駝峰設置 PLUS 配置 > 原始配置
		 */
        GlobalConfiguration globalCache = this.getGlobalConfig();
        if (!globalCache.isDbColumnUnderline()) {
            globalCache.setDbColumnUnderline(configuration.isMapUnderscoreToCamelCase());
        }
        Class<?> modelClass = extractModelClass(mapperClass);
        if (null != modelClass) {
            /**
             * 初始化 SQL 解析
             */
            if (globalCache.isSqlParserCache()) {
                PluginUtils.initSqlParserInfoCache(mapperClass);
            }
            // 這里獲取tableInfo. 這里你會看到我們@TableName了。。
            TableInfo table = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
            //生成sql注入sql
            injectSql(builderAssistant, mapperClass, modelClass, table);
        }
    }
    
    // 看到這個方法里面的injectXXXX是不是和我們BaseMapper里的一樣呢。對這里挨着一個個的去實現。
    protected void injectSql(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
        /**
         * #148 表信息包含主鍵,注入主鍵相關方法
         */
        if (StringUtils.isNotEmpty(table.getKeyProperty())) {
            /** 刪除 */
            this.injectDeleteByIdSql(false, mapperClass, modelClass, table);
            this.injectDeleteByIdSql(true, mapperClass, modelClass, table);
            /** 修改 */
            this.injectUpdateByIdSql(true, mapperClass, modelClass, table);
            this.injectUpdateByIdSql(false, mapperClass, modelClass, table);
            /** 查詢 */
            this.injectSelectByIdSql(false, mapperClass, modelClass, table);
            this.injectSelectByIdSql(true, mapperClass, modelClass, table);
        } else {
            // 表不包含主鍵時 給予警告
            logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.",
                    modelClass.toString()));
        }
        /**
         * 正常注入無需主鍵方法
         */
        /** 插入 */
        this.injectInsertOneSql(true, mapperClass, modelClass, table);
        this.injectInsertOneSql(false, mapperClass, modelClass, table);
        /** 刪除 */
        this.injectDeleteSql(mapperClass, modelClass, table);
        this.injectDeleteByMapSql(mapperClass, table);
        /** 修改 */
        this.injectUpdateSql(mapperClass, modelClass, table);
        /** 查詢 */
        this.injectSelectByMapSql(mapperClass, modelClass, table);
        this.injectSelectOneSql(mapperClass, modelClass, table);
        this.injectSelectCountSql(mapperClass, modelClass, table);
        this.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table);
        this.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table);
        this.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table);
        this.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table);
        this.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table);
        /** 自定義方法 */
        this.inject(configuration, builderAssistant, mapperClass, modelClass, table);
    }
}

看到上面的AutoSqlInjector#injectSql這個方法,你會發覺到就和BaseMapper中一樣了。這里就是將那些方法解析生成並注入。下面將以AutoSqlInjector#injectSelectCountSql為例,看看他到底咋個搞得。

protected void injectSelectCountSql(Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
    // 從枚舉中獲取到sqlMethod
        SqlMethod sqlMethod = SqlMethod.SELECT_COUNT;
    // 將sqlMethod.getSql() 格式化
        String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlWhereEntityWrapper(table));
    // 得到SqlSource
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
    // 注入
        this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, Integer.class, null);
    }

// 這個方法將是根據實體類,構造一堆條件。構造出來得條件,后續執行我們得sql后會根據OGNL,也將會通過反射機制調用我們得get方法,慘了,所以最上面我們出現得NPE就問題來了。為null當然會NPE出現了。
protected String sqlWhereEntityWrapper(TableInfo table) {
        StringBuilder where = new StringBuilder(128);
        where.append("\n<where>");
        where.append("\n<if test=\"ew!=null\">");
        where.append("\n<if test=\"ew.entity!=null\">");
        if (StringUtils.isNotEmpty(table.getKeyProperty())) {
            where.append("\n<if test=\"ew.entity.").append(table.getKeyProperty()).append("!=null\">\n");
            where.append(table.getKeyColumn()).append("=#{ew.entity.").append(table.getKeyProperty()).append("}");
            where.append("\n</if>");
        }
        List<TableFieldInfo> fieldList = table.getFieldList();
        for (TableFieldInfo fieldInfo : fieldList) {
            where.append(convertIfTag(fieldInfo, "ew.entity.", false));
            where.append(" AND ").append(this.sqlCondition(fieldInfo.getCondition(),
                fieldInfo.getColumn(), "ew.entity." + fieldInfo.getEl()));
            where.append(convertIfTag(fieldInfo, true));
        }
        where.append("\n</if>");
        where.append("\n<if test=\"ew!=null and ew.sqlSegment!=null and ew.notEmptyOfWhere\">\n${ew.sqlSegment}\n</if>");
        where.append("\n</if>");
        where.append("\n</where>");
        where.append("\n<if test=\"ew!=null and ew.sqlSegment!=null and ew.emptyOfWhere\">\n${ew.sqlSegment}\n</if>");
        return where.toString();
    }

生成的sql

selectCount生成出來的SQL如下

SELECT COUNT(1) FROM activity 
<where>
<if test="ew!=null">
<if test="ew.entity!=null">
<if test="ew.entity.id!=null">
id=#{ew.entity.id}
</if>
	<if test="ew.entity.createTime!=null"> AND create_time=#{ew.entity.createTime}</if>
	<if test="ew.entity.editTime!=null"> AND edit_time=#{ew.entity.editTime}</if>
	<if test="ew.entity.isDelete!=null"> AND is_delete=#{ew.entity.isDelete}</if>
	<if test="ew.entity.keyCode!=null"> AND key_code=#{ew.entity.keyCode}</if>
	<if test="ew.entity.gasStationId!=null"> AND gas_station_id=#{ew.entity.gasStationId}</if>
	<if test="ew.entity.gasStationName!=null"> AND gas_station_name=#{ew.entity.gasStationName}</if>
	<if test="ew.entity.startTime!=null"> AND start_time=#{ew.entity.startTime}</if>
	<if test="ew.entity.endTime!=null"> AND end_time=#{ew.entity.endTime}</if>
	<if test="ew.entity.processor!=null"> AND processor=#{ew.entity.processor}</if>
	<if test="ew.entity.processorParams!=null"> AND processor_params=#{ew.entity.processorParams}</if>
	<if test="ew.entity.bizType!=null"> AND biz_type=#{ew.entity.bizType}</if>
	<if test="ew.entity.remainingJoinTimes!=null"> AND remaining_join_times=#{ew.entity.remainingJoinTimes}</if>
	<if test="ew.entity.optUserId!=null"> AND opt_user_id=#{ew.entity.optUserId}</if>
	<if test="ew.entity.optUserName!=null"> AND opt_user_name=#{ew.entity.optUserName}</if>
	<if test="ew.entity.status!=null"> AND status=#{ew.entity.status}</if>
	<if test="ew.entity.extra!=null"> AND extra=#{ew.entity.extra}</if>
	<if test="ew.entity.createSource!=null"> AND create_source=#{ew.entity.createSource}</if>
</if>
<if test="ew!=null and ew.sqlSegment!=null and ew.notEmptyOfWhere">
${ew.sqlSegment}
</if>
</if>
</where>
<if test="ew!=null and ew.sqlSegment!=null and ew.emptyOfWhere">
${ew.sqlSegment}
</if>

MapperProxy代理生成

MapperProxy生成的大致類圖

還記得在上面分析代碼的時候,我們BeanDefinition中得beanClass設置為MapperFactoryBean吧,MapperFactoryBean實現FactoryBean。實現FactoryBean好處是什么?我們先看看spring容器refresh的流程

public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext, DisposableBean {
...
// 這個就是spring容器啟動得核心流程都在這里。
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();
			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);
			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);
				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				// Initialize message source for this context.
				initMessageSource();
				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();
				// Initialize other special beans in specific context subclasses.
				onRefresh();
				// Check for listener beans and register them.
				registerListeners();
				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);
				// Last step: publish corresponding event.
				finishRefresh();
			}
			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}
				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();
				// Reset 'active' flag.
				cancelRefresh(ex);
				// Propagate exception to caller.
				throw ex;
			}
			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}
...
}

這里我們重點看finishBeanFactoryInitialization(beanFactory),這個方法主要是完成BeanFactory中得非懶加載Bean得初始化工作,在這也將會完成依賴注入的bean,依賴注入的時候,調用AbstractBeanFactory#getBean(String, Class<T>),具體可以詳細看看。后續會判斷此Bean是否是FactoryBean的類型,如果是將會調用FactoryBean#getObject();那么現在我們再回到MapperFactoryBean#getObject()實現。

MapperFactoryBean#getObject

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    ...
   public T getObject() throws Exception {
    //這里通過MybatisSqlSessionTemplate去獲取我們得Mapper代理。
    return getSqlSession().getMapper(this.mapperInterface);
  }
    ...
}

SqlSessionTemplate#getMapper

public class SqlSessionTemplate implements SqlSession, DisposableBean {
    ...
    @Override
    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
    }
    @Override
    public Configuration getConfiguration() {
        return this.sqlSessionFactory.getConfiguration();
    }
    ...
}

MybatisConfiguration#getMapper

public class MybatisConfiguration extends Configuration {
    public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
    ...
    //在注冊器中獲取
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mybatisMapperRegistry.getMapper(type, sqlSession);
    }
    ...
}

MybatisMapperRegistry#getMapper

public class MybatisMapperRegistry extends MapperRegistry {
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
    
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
        }
        try {
            // 通過代理工廠再實例化。我們得MapperProxy代理
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
}

MapperProxyFactory#newInstance

MapperProxyFactory是我們常說的工廠設計模式,為我們Mapper生成MapperProxy代理。

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  // 感覺這里寫的不好。。。這個可以直接寫道MapperProxy里啊,為嘛在這里初始化后做一個參數來傳遞?難道為了擴展???有什么擴展需要放到這里
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

MapperProxy

將會給我們每個mapper生成一個代理

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // 如果MapperMethod已經存在,放入緩存,否則初始化
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

  @UsesJava7
  private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
      throws Throwable {
    final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
        .getDeclaredConstructor(Class.class, int.class);
    if (!constructor.isAccessible()) {
      constructor.setAccessible(true);
    }
    final Class<?> declaringClass = method.getDeclaringClass();
    return constructor
        .newInstance(declaringClass,
            MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

  /**
   * Backport of java.lang.reflect.Method#isDefault()
   */
  private boolean isDefaultMethod(Method method) {
    return (method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
        && method.getDeclaringClass().isInterface();
  }
}

生成的MapperProxy代理后,將會注入到依賴此Bean的Service中。

后續CRUD的時候,會調用MapperProxy#invokeMapperMethod初始化的時候會初始化MethodSignature,MethodSignature類意思就是方法簽名,將會對paramNameResolver(參數處理器),returnType(返回類型),ResultHandler(結果處理器)的處理等。

  • paramNameResolver處理器,可以參看俊良的mybatis 參數
  • ResultHandler這個用法,可以參看我在mybatis 參數文章中的評論,

總結

跟着源碼看下,學習到東西還是很多得。

  • 設計模式:代理、工廠、模板、委派等
  • spring容器初始化流程
  • spring中很多擴展點等等

一個很簡單問題,解決是解決了,但並不代表你從中學到了什么。根據通過上面其實我們還可以總結一些寫插件的結論

  • BeanDefinition類型設置為實現了FactoryBean的一些類,比如這里的MapperFactoryBean,FeignClientFactoryBean(這里提出來是為了說明spring-cloud-openfeign也是基於這樣的思路搞得)
    • 實現FactoryBean得好處:在依賴bean得地方將會叫用getObject,這里要做的文章就多了。Spring源碼中有很多實現FactoryBean得類
  • 接口注入,比如這里得我們寫的XXXXDao,這種BaseMapper得注入,這種一般都采用了代理模式,spring-cloud-openfeign那些接口也是一樣。所以才能像正常調用一樣。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM