欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MybatisPlus?BaseMapper?實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)增刪改查源碼

 更新時(shí)間:2023年01月31日 15:48:22   作者:myboy  
MybatisPlus?是一款在?Mybatis?基礎(chǔ)上進(jìn)行的增強(qiáng)?orm?框架,可以實(shí)現(xiàn)不寫(xiě)?sql?就完成數(shù)據(jù)庫(kù)相關(guān)的操作,這篇文章主要介紹了MybatisPlus?BaseMapper?實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)增刪改查源碼解析,需要的朋友可以參考下

MybatisPlus 是一款在 Mybatis 基礎(chǔ)上進(jìn)行的增強(qiáng) orm 框架,可以實(shí)現(xiàn)不寫(xiě) sql 就完成數(shù)據(jù)庫(kù)相關(guān)的操作。普通的 mapper 接口通過(guò)繼承 BaseMapper 接口,即可獲得增強(qiáng),如下所示:

public interface UserMapper extends BaseMapper<User> {
}

接下來(lái)就對(duì)其源碼一探究竟,看看他到底是如何實(shí)現(xiàn)的

環(huán)境搭建

1、使用 h2 數(shù)據(jù)庫(kù),方便測(cè)試,導(dǎo)入相關(guān)依賴(lài)

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:2.7.1'
    implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.3.1'
    implementation 'org.projectlombok:lombok:1.18.24'
    implementation 'com.h2database:h2:1.4.200'
}

2、springboot 配置文件

spring:
  datasource:
    driver-class-name: org.h2.Driver
    username: root
    password: test
  sql:
    init:
      schema-locations: classpath:db/schema-h2.sql
      data-locations: classpath:db/data-h2.sql

3、resources 目錄下新建 db 目錄,創(chuàng)建 sql 文件

schema-h2.sql

DROP TABLE IF EXISTS demo_user;

CREATE TABLE demo_user
(
    id int primary key,
    name varchar,
    age int,
    email varchar
);

data-h2.sql

DELETE
FROM demo_user;

INSERT INTO demo_user (id, name, age, email)
VALUES (1, 'Jone', 18, 'test1@baomidou.com'),
       (2, 'Jack', 20, 'test2@baomidou.com'),
       (3, 'Tom', 28, 'test3@baomidou.com'),
       (4, 'Sandy', 21, 'test4@baomidou.com'),
       (5, 'Billie', 24, 'test5@baomidou.com');

4、編寫(xiě) mapper 文件

public interface UserMapper extends BaseMapper<User> {
}

5、啟動(dòng)測(cè)試

@MapperScan("org.example.mapper")
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Main.class, args);
        UserMapper userMapper = context.getBean(UserMapper.class);
        System.out.println(userMapper.selectList(null));
    }
}

結(jié)果如下

[User(id=1, name=Jone, age=18, email=test1@baomidou.com), User(id=2, name=Jack, age=20, email=test2@baomidou.com), User(id=3, name=Tom, age=28, email=test3@baomidou.com), User(id=4, name=Sandy, age=21, email=test4@baomidou.com), User(id=5, name=Billie, age=24, email=test5@baomidou.com)]

從 @MapperScan 入手

@MapperScan 注解的作用是掃描指定 mapper 接口所在的包,并生成接口的代理對(duì)象,注入到 ioc 容器中,接口定義如下

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
}

可以看到 Import 了個(gè) MapperScannerRegistrar,點(diǎn)進(jìn)去看看這個(gè)類(lèi)做了什么

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      // 注冊(cè)一個(gè) beanDefinition
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
}

void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
  BeanDefinitionRegistry registry, String beanName) {

    // 注冊(cè)MapperScannerConfigurer的BeanDefinition
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    // ......
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}

這個(gè) importRegister 注冊(cè)了一個(gè) MapperScannerConfigurer,這個(gè)類(lèi)是個(gè) BeanDefinitionRegistryPostProcessor,核心邏輯就是在這個(gè)類(lèi)中,即掃描指定 mapper 接口所在的包,并生成接口的代理對(duì)象,注入到 ioc 容器中,查看該類(lèi)的 postProcessBeanDefinitionRegistry() 方法

@Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // 設(shè)置一些scanner參數(shù)
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    // ......
    // 掃描mapper接口
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

進(jìn)入父類(lèi) scan 方法,發(fā)現(xiàn)核心方法是子類(lèi)的 doScan(), 來(lái)到 MapperScannerConfigurer.doScan()

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 拿到掃描到的 beanDefinition
    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 {
      // 處理 mapper beanDefinition
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

核心在 processBeanDefinitions(beanDefinitions) 中

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();

      // 設(shè)置該BeanDefinition的beanClass是 MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBeanClass);

      // ......

      // 設(shè)置該MapperFactoryBean 中的 sqlSessionTemplateBeanName
      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        definition.getPropertyValues().add("sqlSessionTemplate",
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      }

      // ......

    }
}

通過(guò)這一系列源碼,可以知道,@MapperScan 指定的包在 MapperScannerConfigurer 被掃描成 BeanDefinition, 并且修改了 BeanDefinition 的 beanClass 屬性為 MapperFactory,這樣 spring 實(shí)例化 UserMapper 單例 bean 時(shí),會(huì)生成對(duì)應(yīng)的 MapperFactory

看看這個(gè) MapperFactory 是什么鬼

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  
  public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }

}

這個(gè)類(lèi)是個(gè) FactoryBean,那么它的 getObject() 方法就是調(diào)用 sqlSessionTemplate 的 getMapper() 方法獲取代理對(duì)象,關(guān)于這個(gè) getMapper() 方法的解析,可以參考我之前寫(xiě)的《Mybatis 通過(guò)接口實(shí)現(xiàn) sql 執(zhí)行原理解析

到這里,MapperFactory 生成的 bean 被放到了 ioc 容器中,結(jié)束了嗎?我們忽略了 MapperFactory 的父類(lèi) SqlSessionDaoSupport,下面一節(jié)來(lái)看看這個(gè)父類(lèi) SqlSessionDaoSupport 做了什么

SqlSessionDaoSupport

這個(gè)類(lèi)看名字是給 Dao 做支持的,Dao 指的就是那個(gè) mapper 接口,做什么支持?其實(shí)給就是給 BaseMapper 里定義的方法生成對(duì)應(yīng)的 Statemnet,注冊(cè)到 MybatisMapperRegistry 中,這樣調(diào)用 BaseMapper 方法時(shí),代理類(lèi)就會(huì)從 MybatisMapperRegistry 中找到 Statemnet,這樣可以取出 sql 執(zhí)行了,來(lái)看源碼,其他都是抽象方法,只有一個(gè)初始化方法

@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
    // 讓子類(lèi)處理
    checkDaoConfig();

    // Let concrete implementations initialize themselves.
    try {
            initDao();
    }
    catch (Exception ex) {
            throw new BeanInitializationException("Initialization of DAO failed", ex);
    }
}

調(diào)用了抽象方法,子類(lèi)實(shí)現(xiàn)了 checkDaoConfig(),來(lái)看下 MapperFactoryBean.checkDaoConfig()

protected void checkDaoConfig() {
    super.checkDaoConfig();
    Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    Configuration configuration = this.getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        try {
            // 解析這個(gè) mapper 方法
            configuration.addMapper(this.mapperInterface);
        } catch (Exception var6) {
            this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
            throw new IllegalArgumentException(var6);
        } finally {
            ErrorContext.instance().reset();
        }
    }

}

看到 configuration.addMapper(this.mapperInterface) 方法,相信看過(guò) mybatis 源碼的小伙伴們已經(jīng)知道要干什么了吧。就是解析這個(gè) mapper 類(lèi)方法,找到對(duì)應(yīng)的 sql,并封裝成 statemnet,下面看看這個(gè) configuration.addMapper(this.mapperInterface) 的實(shí)現(xiàn)邏輯吧

MybatisConfiguration.addMapper()

因?yàn)槭?MybatisPlus,所以源碼內(nèi)部的 Configuration 類(lèi)是 MybatisConfiguration,查看他的 addMapper() 方法源碼

@Override
public <T> void addMapper(Class<T> type) {
    mybatisMapperRegistry.addMapper(type);
}

再進(jìn)入 mybatisMapperRegistry.addMapper(type) 源碼

@Override
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
            // TODO 如果之前注入 直接返回
            return;
        }
        boolean loadCompleted = false;
        try {
            // TODO 注冊(cè)mapper類(lèi)對(duì)應(yīng)的代理工廠類(lèi),用于生成代理對(duì)象
            knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
            MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
            // 解析mapper類(lèi),生成 statement
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

進(jìn)入 parse() 方法查看

@Override
public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        // 解析mapper.xml
        loadXmlResource();
        configuration.addLoadedResource(resource);
        String mapperName = type.getName();
        assistant.setCurrentNamespace(mapperName);
        // 解析緩存
        parseCache();
        parseCacheRef();
        IgnoreStrategy ignoreStrategy = InterceptorIgnoreHelper.initSqlParserInfoCache(type);
        for (Method method : type.getMethods()) {
            if (!canHaveStatement(method)) {
                continue;
            }
            if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                && method.getAnnotation(ResultMap.class) == null) {
                parseResultMap(method);
            }
            try {
                // TODO 加入 注解過(guò)濾緩存
                InterceptorIgnoreHelper.initSqlParserInfoCache(ignoreStrategy, mapperName, method);
                parseStatement(method);
            } catch (IncompleteElementException e) {
                // TODO 使用 MybatisMethodResolver 而不是 MethodResolver
                configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
            }
        }
        // TODO 注入 CURD 動(dòng)態(tài) SQL , 放在在最后, because 可能會(huì)有人會(huì)用注解重寫(xiě)sql
        try {
            // https://github.com/baomidou/mybatis-plus/issues/3038
            if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
                parserInjector();
            }
        } catch (IncompleteElementException e) {
            configuration.addIncompleteMethod(new InjectorResolver(this));
        }
    }
    parsePendingMethods();
}

關(guān)注最后注釋?zhuān)⑷?CRUD 動(dòng)態(tài) SQL,其實(shí)就是給 BaseMapper 里的方法創(chuàng)建對(duì)應(yīng)的 Statement,查看內(nèi)部邏輯:

void parserInjector() {
    // DefaultSqlInjector.inspectInject();
    GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}

這里先獲取到默認(rèn)的 Sql 注入器 DefaultSqlInjector,再調(diào)用其 inspectInject() 方法注入 sql

@Override
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
    Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0);
    if (modelClass != null) {
        String className = mapperClass.toString();
        Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
        if (!mapperRegistryCache.contains(className)) {
            // 根據(jù)實(shí)體類(lèi),根據(jù)注解解析出表的信息
            TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
            // 拿到所有的AbstractMethod實(shí)現(xiàn)類(lèi)
            List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);
            if (CollectionUtils.isNotEmpty(methodList)) {
                // 循環(huán)注入自定義方法
                methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
            } else {
                logger.debug(mapperClass.toString() + ", No effective injection method was found.");
            }
            mapperRegistryCache.add(className);
        }
    }
}

這里面的 AbstractMethod 的實(shí)現(xiàn)類(lèi)有很多,如下

可以說(shuō),BaseMapper 中每個(gè)方法都有一個(gè)對(duì)應(yīng)的 AbstractMethod 實(shí)現(xiàn)類(lèi),以 selectList() 為例,可以找到 SelectList 類(lèi)

在下面循環(huán)注入的地方:methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)), 進(jìn)入 AbstractMethod.inject() 方法

public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
    this.configuration = builderAssistant.getConfiguration();
    this.builderAssistant = builderAssistant;
    this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
    /* 注入自定義方法 */
    injectMappedStatement(mapperClass, modelClass, tableInfo);
}

子類(lèi)實(shí)現(xiàn)了 injectMappedStatement 方法,還是以 SelectList 為例

@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
    // selectList sql 模版
    SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
    // 格式化sql
    String sql = String.format(sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(),
        sqlWhereEntityWrapper(true, tableInfo), sqlOrderBy(tableInfo), sqlComment());
    // 封裝成 sqlSource 
    SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
    // 注冊(cè) mapperStatement
    return this.addSelectMappedStatementForTable(mapperClass, methodName, sqlSource, tableInfo);
}

其中 sqlSelectColumns(tableInfo, true) 方法是構(gòu)造出 select 的所有列名,并加上動(dòng)態(tài)sql標(biāo)簽

<choose>
    <when test="ew != null and ew.sqlSelect != null">
    ${ew.sqlSelect}
    </when>
    <otherwise>id,name,age,email</otherwise>
</choose>

其中 sqlWhereEntityWrapper(true, tableInfo) 方法是構(gòu)造出 where 后面的條件語(yǔ)句,并加上動(dòng)態(tài)sql標(biāo)簽

<if test="ew != null">
<where>
    <if test="ew.entity != null">
    <if test="ew.entity.id != null">id=#{ew.entity.id}</if>
    <if test="ew.entity['name'] != null"> AND name=#{ew.entity.name}</if>
    <if test="ew.entity['age'] != null"> AND age=#{ew.entity.age}</if>
    <if test="ew.entity['email'] != null"> AND email=#{ew.entity.email}</if>
    </if>
    <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
    <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment}
    </if>
</where>
<if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
 ${ew.sqlSegment}
</if>
</if>

最后 format 后的 sql 語(yǔ)句是

<script>
    <if test="ew != null and ew.sqlFirst != null">
        ${ew.sqlFirst}
        </if> SELECT <choose>
        <when test="ew != null and ew.sqlSelect != null">
        ${ew.sqlSelect}
        </when>
        <otherwise>id,name,age,email</otherwise>
        </choose> FROM demo_user 
        <if test="ew != null">
        <where>
        <if test="ew.entity != null">
        <if test="ew.entity.id != null">id=#{ew.entity.id}</if>
        <if test="ew.entity['name'] != null"> AND name=#{ew.entity.name}</if>
        <if test="ew.entity['age'] != null"> AND age=#{ew.entity.age}</if>
        <if test="ew.entity['email'] != null"> AND email=#{ew.entity.email}</if>
        </if>
        <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
        <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment}
        </if>
        </where>
        <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
         ${ew.sqlSegment}
        </if>
        </if>  <if test="ew != null and ew.sqlComment != null">
        ${ew.sqlComment}
        </if>
</script>

最后是把 sql 封裝成了 SqlSource,并構(gòu)造 MapperStatement 存入 configuration.mappedStatements 中,后面 mapper 調(diào)用 selectList 方法時(shí),會(huì)從 mappedStatements 中找到對(duì)應(yīng)的 statement,并取出 sql 語(yǔ)句執(zhí)行,就能拿到數(shù)據(jù)了

小結(jié)

到此,MybatisPlus BaseMapper 實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)增刪改查源碼解析完畢,相信通過(guò)源碼的閱讀能對(duì) mybatisPlus 有更深的了解

到此這篇關(guān)于MybatisPlus BaseMapper 實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)增刪改查源碼解析的文章就介紹到這了,更多相關(guān)MybatisPlus BaseMapper 數(shù)據(jù)庫(kù)增刪改查內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論