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

Mybatis-Plus注入SQL原理分析

 更新時(shí)間:2022年07月08日 09:54:36   作者:養(yǎng)歌  
本文主要介紹了Mybatis-Plus注入SQL原理分析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

MyBatis-Plus 是一個(gè) MyBatis 的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,為簡(jiǎn)化開(kāi)發(fā)、提高效率而生。

那么 MyBatis-Plus 是怎么加強(qiáng)的呢?其實(shí)就是封裝好了一些 crud 方法,開(kāi)發(fā)人員不需要再寫(xiě) SQL 了,間接調(diào)用方法就可以獲取到封裝好的 SQL 語(yǔ)句。

特性:

  • 無(wú)侵入:只做增強(qiáng)不做改變,引入它不會(huì)對(duì)現(xiàn)有工程產(chǎn)生影響,如絲般順滑
  • 損耗小:?jiǎn)?dòng)即會(huì)自動(dòng)注入基本 CURD,性能基本無(wú)損耗,直接面向?qū)ο蟛僮?/li>
  • 強(qiáng)大的 CRUD 操作:內(nèi)置通用 Mapper、通用 Service,僅僅通過(guò)少量配置即可實(shí)現(xiàn)單表大部分 CRUD 操作,更有強(qiáng)大的條件構(gòu)造器,滿足各類(lèi)使用需求
  • 支持 Lambda 形式調(diào)用:通過(guò) Lambda 表達(dá)式,方便的編寫(xiě)各類(lèi)查詢條件,無(wú)需再擔(dān)心字段寫(xiě)錯(cuò)
  • 支持主鍵自動(dòng)生成:支持多達(dá) 4 種主鍵策略(內(nèi)含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問(wèn)題
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式調(diào)用,實(shí)體類(lèi)只需繼承 Model 類(lèi)即可進(jìn)行強(qiáng)大的 CRUD 操作
  • 支持自定義全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 內(nèi)置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,支持模板引擎,更有超多自定義配置等您來(lái)使用
  • 內(nèi)置分頁(yè)插件:基于 MyBatis 物理分頁(yè),開(kāi)發(fā)者無(wú)需關(guān)心具體操作,配置好插件之后,寫(xiě)分頁(yè)等同于普通 List 查詢
  • 分頁(yè)插件支持多種數(shù)據(jù)庫(kù):支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多種數(shù)據(jù)庫(kù)
  • 內(nèi)置性能分析插件:可輸出 Sql 語(yǔ)句以及其執(zhí)行時(shí)間,建議開(kāi)發(fā)測(cè)試時(shí)啟用該功能,能快速揪出慢查詢
  • 內(nèi)置全局?jǐn)r截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規(guī)則,預(yù)防誤操作

支持的數(shù)據(jù)庫(kù):

  • MySQL、 Oracle 、 db2 、PostgreSQL 、 SqlServer 等等。

案例

下面我們先從一個(gè)簡(jiǎn)單的 demo 入手,來(lái)感受一下 MyBatis-plus 的便捷性。

MP封裝的 BaseMapper 接口

public interface BaseMapper<T> extends Mapper<T> {

    /**
     * 插入一條記錄
     *
     * @param entity 實(shí)體對(duì)象
     */
    int insert(T entity);


    /**
     * 根據(jù) entity 條件,刪除記錄
     *
     * @param wrapper 實(shí)體對(duì)象封裝操作類(lèi)(可以為 null)
     */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);

    /**
     * 根據(jù) whereEntity 條件,更新記錄
     *
     * @param entity        實(shí)體對(duì)象 (set 條件值,可以為 null)
     * @param updateWrapper 實(shí)體對(duì)象封裝操作類(lèi)(可以為 null,里面的 entity 用于生成 where 語(yǔ)句)
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    /**
     * 根據(jù) ID 查詢
     *
     * @param id 主鍵ID
     */
    T selectById(Serializable id);
}

實(shí)體類(lèi)對(duì)象

/**
 * 實(shí)體類(lèi)
 *
 * @author Chill
 */
@Data
@TableName("user")
@EqualsAndHashCode(callSuper = true)
public class User extends TenantEntity {

	private static final long serialVersionUID = 1L;

	/**
	 * 用戶編號(hào)
	 */
	private String code;
	/**
	 * 賬號(hào)
	 */
	private String account;
	/**
	 * 密碼
	 */
	private String password;
	/**
	 * 昵稱(chēng)
	 */
	private String name;
}

UserMapper 繼承 BaseMapper 接口

/**
 * Mapper 接口
 *
 * @author Chill
 */
public interface UserMapper extends BaseMapper<User> {
}

測(cè)試

@Override
	public User getById(String id){
		User user = userMapper.selectById(id);
		return null;
	}

最終查詢的 SQL 語(yǔ)句如下圖:

從打印的日志我們可以知道,MyBatis-Plus 最終為我們自動(dòng)生成了 SQL 語(yǔ)句。根據(jù)上述操作分析:UserMapper 繼承了 BaseMapper,擁有了 selectById 的方法,但是 MyBatis-Plus 是基于 mybatis 的增強(qiáng)版,關(guān)鍵在于最終仍然需要提供具體的SQL語(yǔ)句,來(lái)進(jìn)行數(shù)據(jù)庫(kù)操作。

下面我們 DEBUG 跟蹤 MyBatis-Plus 是如何生成業(yè)務(wù) sql 以及自動(dòng)注入的,如下圖所示:

發(fā)現(xiàn) SQL 語(yǔ)句在 MappedStatement 對(duì)象中,而 sqlSource 存的就是相關(guān)的 SQL 語(yǔ)句,基于上面的分析,我們想要知道 SQL 語(yǔ)句是什么時(shí)候獲取到的,就是要找到 mappedStatement 被添加的位置。追蹤到 AbstractMethod 的抽象方法中。

原理解析

Mybatis-Plus 在啟動(dòng)后會(huì)將 BaseMapper 中的一系列的方法注冊(cè)到 meppedStatements 中,那么究竟是如何注入的呢?下面我們一起來(lái)分析下。

在 Mybatis-Plus 中,ISqlInjector 負(fù)責(zé) SQL 的注入工作,它是一個(gè)接口,AbstractSqlInjector 是它的實(shí)現(xiàn)類(lèi),SqlInjector SQL 自動(dòng)注入器接口的相關(guān) UML 圖如下:

找到了下面我們所講到的都基于這幾個(gè)類(lèi)實(shí)現(xiàn),接著上一個(gè)問(wèn)題,追蹤到 AbstractMethod 的抽象方法中,

下面我們繼續(xù) DEBUG 跟蹤代碼是怎么注入的。

首先跳進(jìn)來(lái) AbstractSqlInjector 抽象類(lèi)執(zhí)行 inspectInject 方法

@Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        Class<?> modelClass = extractModelClass(mapperClass);
        if (modelClass != null) {
            String className = mapperClass.toString();
            Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
            if (!mapperRegistryCache.contains(className)) {
            //獲取 CRUD 實(shí)現(xiàn)類(lèi)列表
                List<AbstractMethod> methodList = this.getMethodList(mapperClass);
                if (CollectionUtils.isNotEmpty(methodList)) {
                    TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
                    // 循環(huán)注入自定義方法,這里開(kāi)始注入 sql
                    methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
                } else {
                    logger.debug(mapperClass.toString() + ", No effective injection method was found.");
                }
                mapperRegistryCache.add(className);
            }
        }
    }

在這里我們找到 inject 方法,跳進(jìn)去

在跳進(jìn)去 injectMappedStatement 方法,選擇你執(zhí)行的 CRUD 操作,我這里以 slectById 為例

從這里我們找到了 addMappedStatement() 方法,可以看到,生成了 SqlSource 對(duì)象,再將 SQL 通過(guò) addSelectMappedStatement 方法添加到 meppedStatements 中。

那么實(shí)現(xiàn)類(lèi)是怎么獲取到的呢?

在 AbstractSqlInjector 抽象類(lèi) inspectInject 方法從 this.getMethodList 方法獲取,如下圖:

這里的 getMethodList 方法獲取 CRUD 實(shí)現(xiàn)類(lèi)列表

/**
 * SQL 默認(rèn)注入器
 *
 * @author hubin
 * @since 2018-04-10
 */
public class DefaultSqlInjector extends AbstractSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        return Stream.of(
            new Insert(),
            new Delete(),
            new DeleteByMap(),
            new DeleteById(),
            new DeleteBatchByIds(),
            new Update(),
            new UpdateById(),
            new SelectById(),
            new SelectBatchByIds(),
            new SelectByMap(),
            new SelectOne(),
            new SelectCount(),
            new SelectMaps(),
            new SelectMapsPage(),
            new SelectObjs(),
            new SelectList(),
            new SelectPage()
        ).collect(toList());
    }
}

從上面的源碼可知,項(xiàng)目啟動(dòng)時(shí),首先由默認(rèn)注入器生成基礎(chǔ) CRUD 實(shí)現(xiàn)類(lèi)對(duì)象,其次遍歷實(shí)現(xiàn)類(lèi)列表,依次注入各自的模板 SQL,最后將其添加至 mappedstatement。

那么 SQL 語(yǔ)句是怎么生成的?此時(shí) SqlSource 通過(guò)解析 SQL 模板、以及傳入的表信息和主鍵信息構(gòu)建出了 SQL 語(yǔ)句,如下所示:

@Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
      /** 定義 mybatis xml method id, 對(duì)應(yīng) <id="xyz"> **/
        SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
    /** 構(gòu)造 id 對(duì)應(yīng)的具體 xml 片段 **/
        SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(),
            sqlSelectColumns(tableInfo, false),
            tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
            tableInfo.getLogicDeleteSql(true, true)), Object.class);
         /** 將 xml method 方法添加到 mybatis 的 MappedStatement 中 **/
        return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
    }

那么數(shù)據(jù)庫(kù)表信息是如何獲取的?主要根據(jù)AbstractSqlInjector抽象類(lèi)的 inspectInject 方法中的initTableInfo方法獲取,如下圖:

/**
     * <p>
     * 實(shí)體類(lèi)反射獲取表信息【初始化】
     * </p>
     *
     * @param clazz 反射實(shí)體類(lèi)
     * @return 數(shù)據(jù)庫(kù)表反射信息
     */
    public synchronized static TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {
        TableInfo tableInfo = TABLE_INFO_CACHE.get(clazz);
        if (tableInfo != null) {
            if (builderAssistant != null) {
                tableInfo.setConfiguration(builderAssistant.getConfiguration());
            }
            return tableInfo;
        }

        /* 沒(méi)有獲取到緩存信息,則初始化 */
        tableInfo = new TableInfo(clazz);
        GlobalConfig globalConfig;
        if (null != builderAssistant) {
            tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace());
            tableInfo.setConfiguration(builderAssistant.getConfiguration());
            globalConfig = GlobalConfigUtils.getGlobalConfig(builderAssistant.getConfiguration());
        } else {
            // 兼容測(cè)試場(chǎng)景
            globalConfig = GlobalConfigUtils.defaults();
        }

        /* 初始化表名相關(guān) */
        final String[] excludeProperty = initTableName(clazz, globalConfig, tableInfo);

        List<String> excludePropertyList = excludeProperty != null && excludeProperty.length > 0 ? Arrays.asList(excludeProperty) : Collections.emptyList();

        /* 初始化字段相關(guān) */
        initTableFields(clazz, globalConfig, tableInfo, excludePropertyList);

        /* 放入緩存 */
        TABLE_INFO_CACHE.put(clazz, tableInfo);

        /* 緩存 lambda */
        LambdaUtils.installCache(tableInfo);

        /* 自動(dòng)構(gòu)建 resultMap */
        tableInfo.initResultMapIfNeed();

        return tableInfo;
    }

分析 initTableName() 方法,獲取表名信息源碼中傳入了實(shí)體類(lèi)信息 class,其實(shí)就是通過(guò)實(shí)體上的@TableName 注解拿到了表名。

我們?cè)诙x實(shí)體類(lèi)的同時(shí),指定了該實(shí)體類(lèi)對(duì)應(yīng)的表名。

那么獲取到表名之后怎么獲取主鍵及其他字段信息呢?主要根據(jù)AbstractSqlInjector抽象類(lèi)的 inspectInject 方法中的initTableFields方法獲取,如下圖:

  /**
     * <p>
     * 初始化 表主鍵,表字段
     * </p>
     *
     * @param clazz        實(shí)體類(lèi)
     * @param globalConfig 全局配置
     * @param tableInfo    數(shù)據(jù)庫(kù)表反射信息
     */
    public static void initTableFields(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo, List<String> excludeProperty) {
        /* 數(shù)據(jù)庫(kù)全局配置 */
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        ReflectorFactory reflectorFactory = tableInfo.getConfiguration().getReflectorFactory();
        //TODO @咩咩 有空一起來(lái)擼完這反射模塊.
        Reflector reflector = reflectorFactory.findForClass(clazz);
        List<Field> list = getAllFields(clazz);
        // 標(biāo)記是否讀取到主鍵
        boolean isReadPK = false;
        // 是否存在 @TableId 注解
        boolean existTableId = isExistTableId(list);
    
        List<TableFieldInfo> fieldList = new ArrayList<>(list.size());
        for (Field field : list) {
            if (excludeProperty.contains(field.getName())) {
                continue;
            }

            /* 主鍵ID 初始化 */
            if (existTableId) {
                TableId tableId = field.getAnnotation(TableId.class);
                if (tableId != null) {
                    if (isReadPK) {
                        throw ExceptionUtils.mpe("@TableId can't more than one in Class: \"%s\".", clazz.getName());
                    } else {
                        isReadPK = initTableIdWithAnnotation(dbConfig, tableInfo, field, tableId, reflector);
                        continue;
                    }
                }
            } else if (!isReadPK) {
                isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field, reflector);
                if (isReadPK) {
                    continue;
                }
            }

            /* 有 @TableField 注解的字段初始化 */
            if (initTableFieldWithAnnotation(dbConfig, tableInfo, fieldList, field)) {
                continue;
            }

            /* 無(wú) @TableField 注解的字段初始化 */
            fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field));
        }

        /* 檢查邏輯刪除字段只能有最多一個(gè) */
        Assert.isTrue(fieldList.parallelStream().filter(TableFieldInfo::isLogicDelete).count() < 2L,
            String.format("@TableLogic can't more than one in Class: \"%s\".", clazz.getName()));

        /* 字段列表,不可變集合 */
        tableInfo.setFieldList(Collections.unmodifiableList(fieldList));

        /* 未發(fā)現(xiàn)主鍵注解,提示警告信息 */
        if (!isReadPK) {
            logger.warn(String.format("Can not find table primary key in Class: \"%s\".", clazz.getName()));
        }
    }

到處我們知道 SQL 語(yǔ)句是怎么注入的了,如果想要更加深入了解的小伙伴,可以自己根據(jù)上面的源碼方法深入去了解。

到此這篇關(guān)于Mybatis-Plus注入SQL原理分析的文章就介紹到這了,更多相關(guān)Mybatis-Plus注入SQL內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • idea自動(dòng)加載html、js而無(wú)需重啟進(jìn)程的操作

    idea自動(dòng)加載html、js而無(wú)需重啟進(jìn)程的操作

    這篇文章主要介紹了idea自動(dòng)加載html、js而無(wú)需重啟進(jìn)程的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-08-08
  • 使用Java讀取Word文件的簡(jiǎn)單例子分享

    使用Java讀取Word文件的簡(jiǎn)單例子分享

    這篇文章主要介紹了使用Java讀取Word文件的簡(jiǎn)單例子分享,包括讀取word文件的表格數(shù)據(jù)的示例,需要的朋友可以參考下
    2015-10-10
  • Java設(shè)計(jì)模式之抽象工廠模式(Abstract?Factory)

    Java設(shè)計(jì)模式之抽象工廠模式(Abstract?Factory)

    這篇文章主要為大家詳細(xì)介紹了Java設(shè)計(jì)模式之抽象工廠模式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Java中圖片的常用操作代碼總結(jié)

    Java中圖片的常用操作代碼總結(jié)

    這篇文章主要為大家詳細(xì)介紹了Java中對(duì)圖片進(jìn)行常用操作處理的代碼,例如生成自定義圖片、獲取圖片格式、圖片的裁剪與壓縮等,感興趣的小伙伴可以了解一下
    2022-11-11
  • springboot整合mybatis plus與druid詳情

    springboot整合mybatis plus與druid詳情

    這篇文章主要介紹了springboot整合mybatis plus與druid詳情,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的下伙伴可以參考一下
    2022-09-09
  • 三種Spring BeanName生成器,你了解嗎

    三種Spring BeanName生成器,你了解嗎

    無(wú)論我們是通過(guò) XML 文件,還是 Java 代碼,亦或是包掃描的方式去注冊(cè) Bean,都可以不設(shè)置BeanName,而Spring均會(huì)為之提供默認(rèn)的 beanName,本文我們就來(lái)看看 Spring 中三種處理不同情況的 beanName生成器吧
    2023-09-09
  • maven在IDEA下載依賴(lài)包速度慢的問(wèn)題解決

    maven在IDEA下載依賴(lài)包速度慢的問(wèn)題解決

    這篇文章主要介紹了maven在IDEA下載依賴(lài)包速度慢的問(wèn)題解決方案,本文通過(guò)圖文結(jié)合的形式給大家分享解決方案,供大家參考,需要的朋友可以參考下
    2024-01-01
  • Java多線程中的Interrupt簡(jiǎn)析

    Java多線程中的Interrupt簡(jiǎn)析

    這篇文章主要介紹了Java多線程中的Interrupt簡(jiǎn)析,Interrupt 的其作用是"中斷"線程, 但實(shí)際上線程仍會(huì)繼續(xù)運(yùn)行, 這是一個(gè)非常容易混淆的概念,Interrupt 的真正作用是給線程對(duì)象設(shè)置一個(gè)中斷標(biāo)記, 并不會(huì)影響線程的正常運(yùn)行,需要的朋友可以參考下
    2023-09-09
  • Java別說(shuō)取余(%)運(yùn)算簡(jiǎn)單你真的會(huì)嗎

    Java別說(shuō)取余(%)運(yùn)算簡(jiǎn)單你真的會(huì)嗎

    這篇文章主要介紹了Java別說(shuō)取余(%)運(yùn)算簡(jiǎn)單你真的會(huì)嗎,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • Java實(shí)現(xiàn)LRU緩存的實(shí)例詳解

    Java實(shí)現(xiàn)LRU緩存的實(shí)例詳解

    這篇文章主要介紹了Java實(shí)現(xiàn)LRU緩存的實(shí)例詳解的相關(guān)資料,這里提供實(shí)例幫助大家理解掌握這部分內(nèi)容,需要的朋友可以參考下
    2017-08-08

最新評(píng)論