亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

public interface UserMapper extends BaseMapper<User> {
}

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

環(huán)境搭建

1、使用 h2 數(shù)據(jù)庫,方便測試,導入相關(guān)依賴

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、編寫 mapper 文件

public interface UserMapper extends BaseMapper<User> {
}

5、啟動測試

@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 接口所在的包,并生成接口的代理對象,注入到 ioc 容器中,接口定義如下

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

可以看到 Import 了個 MapperScannerRegistrar,點進去看看這個類做了什么

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

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

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

這個 importRegister 注冊了一個 MapperScannerConfigurer,這個類是個 BeanDefinitionRegistryPostProcessor,核心邏輯就是在這個類中,即掃描指定 mapper 接口所在的包,并生成接口的代理對象,注入到 ioc 容器中,查看該類的 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));
  }

進入父類 scan 方法,發(fā)現(xiàn)核心方法是子類的 doScan(), 來到 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;
      }

      // ......

    }
}

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

看看這個 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;
  }

}

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

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

SqlSessionDaoSupport

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

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

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

調(diào)用了抽象方法,子類實現(xiàn)了 checkDaoConfig(),來看下 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 {
            // 解析這個 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) 方法,相信看過 mybatis 源碼的小伙伴們已經(jīng)知道要干什么了吧。就是解析這個 mapper 類方法,找到對應的 sql,并封裝成 statemnet,下面看看這個 configuration.addMapper(this.mapperInterface) 的實現(xiàn)邏輯吧

MybatisConfiguration.addMapper()

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

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

再進入 mybatisMapperRegistry.addMapper(type) 源碼

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

進入 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 加入 注解過濾緩存
                InterceptorIgnoreHelper.initSqlParserInfoCache(ignoreStrategy, mapperName, method);
                parseStatement(method);
            } catch (IncompleteElementException e) {
                // TODO 使用 MybatisMethodResolver 而不是 MethodResolver
                configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
            }
        }
        // TODO 注入 CURD 動態(tài) SQL , 放在在最后, because 可能會有人會用注解重寫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)注最后注釋,注入 CRUD 動態(tài) SQL,其實就是給 BaseMapper 里的方法創(chuàng)建對應的 Statement,查看內(nèi)部邏輯:

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

這里先獲取到默認的 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ù)實體類,根據(jù)注解解析出表的信息
            TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
            // 拿到所有的AbstractMethod實現(xiàn)類
            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 的實現(xiàn)類有很多,如下

可以說,BaseMapper 中每個方法都有一個對應的 AbstractMethod 實現(xiàn)類,以 selectList() 為例,可以找到 SelectList 類

在下面循環(huán)注入的地方:methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)), 進入 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);
}

子類實現(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);
    // 注冊 mapperStatement
    return this.addSelectMappedStatementForTable(mapperClass, methodName, sqlSource, tableInfo);
}

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

<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 后面的條件語句,并加上動態(tài)sql標簽

<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 語句是

<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 方法時,會從 mappedStatements 中找到對應的 statement,并取出 sql 語句執(zhí)行,就能拿到數(shù)據(jù)了

小結(jié)

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

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

相關(guān)文章

  • Java 內(nèi)存溢出的原因和解決方法

    Java 內(nèi)存溢出的原因和解決方法

    這篇文章主要介紹了Java 內(nèi)存溢出的原因和解決方法,幫助大家更好的維護Java程序,保持穩(wěn)定性,感興趣的朋友可以了解下
    2020-12-12
  • Spring Boot 在啟動時進行配置文件加解密的方法詳解

    Spring Boot 在啟動時進行配置文件加解密的方法詳解

    這篇文章主要介紹了Spring Boot 在啟動時進行配置文件加解密的方法,本文通過實例給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-06-06
  • Java Jackson之ObjectMapper常用用法總結(jié)

    Java Jackson之ObjectMapper常用用法總結(jié)

    這篇文章主要給大家介紹了關(guān)于Java Jackson之ObjectMapper常用用法的相關(guān)資料,ObjectMapper是一個Java庫,用于將JSON字符串轉(zhuǎn)換為Java對象或?qū)ava對象轉(zhuǎn)換為JSON字符串,需要的朋友可以參考下
    2024-01-01
  • java異常(Exception)處理機制詳解

    java異常(Exception)處理機制詳解

    這篇文章主要介紹了java異常(Exception)處理機制詳解的相關(guān)資料,主要介紹異常的定義及使用方法,需要的朋友可以參考下
    2017-03-03
  • JavaWEB項目之如何配置動態(tài)數(shù)據(jù)源

    JavaWEB項目之如何配置動態(tài)數(shù)據(jù)源

    這篇文章主要介紹了JavaWEB項目之如何配置動態(tài)數(shù)據(jù)源問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 使用WebSocket實現(xiàn)即時通訊(一個群聊的聊天室)

    使用WebSocket實現(xiàn)即時通訊(一個群聊的聊天室)

    這篇文章主要為大家詳細介紹了使用WebSocket實現(xiàn)即使通訊,實現(xiàn)一個群聊的聊天室,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • Mybatis-plus通過添加攔截器實現(xiàn)簡單數(shù)據(jù)權(quán)限

    Mybatis-plus通過添加攔截器實現(xiàn)簡單數(shù)據(jù)權(quán)限

    系統(tǒng)需要根據(jù)用戶所屬的公司,來做一下數(shù)據(jù)權(quán)限控制,具體一點,就是通過表中的company_id進行權(quán)限控制,項目使用的是mybatis-plus,所以通過添加攔截器的方式,修改查詢sql,實現(xiàn)數(shù)據(jù)權(quán)限,本文就通過代碼給大家詳細的講解一下,需要的朋友可以參考下
    2023-08-08
  • SpringBoot通知機制的實現(xiàn)方式

    SpringBoot通知機制的實現(xiàn)方式

    這篇文章主要介紹了SpringBoot通知機制的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • 一篇文章教你如何用Java自定義一個參數(shù)校驗器

    一篇文章教你如何用Java自定義一個參數(shù)校驗器

    這篇文章主要介紹了使用java自定義一個參數(shù)校驗器,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習
    2021-09-09
  • IDEA啟動Tomcat報Unrecognized option: --add-opens=java.base/java.lang=ALL-UNNAMED的解決方法

    IDEA啟動Tomcat報Unrecognized option: --add-opens=java

    這篇文章主要為大家介紹了解決IDEA啟動Tomcat報Unrecognized option: --add-opens=java.base/java.lang=ALL-UNNAMED的方法,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2023-08-08

最新評論