最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Mybatis-Plus注入SQL原理分析

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

前言

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

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

特性:

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

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

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

案例

下面我們先從一個簡單的 demo 入手,來感受一下 MyBatis-plus 的便捷性。

MP封裝的 BaseMapper 接口

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

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


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

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

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

實體類對象

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

	private static final long serialVersionUID = 1L;

	/**
	 * 用戶編號
	 */
	private String code;
	/**
	 * 賬號
	 */
	private String account;
	/**
	 * 密碼
	 */
	private String password;
	/**
	 * 昵稱
	 */
	private String name;
}

UserMapper 繼承 BaseMapper 接口

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

測試

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

最終查詢的 SQL 語句如下圖:

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

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

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

原理解析

Mybatis-Plus 在啟動后會將 BaseMapper 中的一系列的方法注冊到 meppedStatements 中,那么究竟是如何注入的呢?下面我們一起來分析下。

在 Mybatis-Plus 中,ISqlInjector 負責 SQL 的注入工作,它是一個接口,AbstractSqlInjector 是它的實現(xiàn)類,SqlInjector SQL 自動注入器接口的相關(guān) UML 圖如下:

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

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

首先跳進來 AbstractSqlInjector 抽象類執(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 實現(xiàn)類列表
                List<AbstractMethod> methodList = this.getMethodList(mapperClass);
                if (CollectionUtils.isNotEmpty(methodList)) {
                    TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
                    // 循環(huán)注入自定義方法,這里開始注入 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 方法,跳進去

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

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

那么實現(xiàn)類是怎么獲取到的呢?

在 AbstractSqlInjector 抽象類 inspectInject 方法從 this.getMethodList 方法獲取,如下圖:

這里的 getMethodList 方法獲取 CRUD 實現(xiàn)類列表

/**
 * SQL 默認注入器
 *
 * @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());
    }
}

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

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

@Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
      /** 定義 mybatis xml method id, 對應 <id="xyz"> **/
        SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
    /** 構(gòu)造 id 對應的具體 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ù)庫表信息是如何獲取的?主要根據(jù)AbstractSqlInjector抽象類的 inspectInject 方法中的initTableInfo方法獲取,如下圖:

/**
     * <p>
     * 實體類反射獲取表信息【初始化】
     * </p>
     *
     * @param clazz 反射實體類
     * @return 數(shù)據(jù)庫表反射信息
     */
    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;
        }

        /* 沒有獲取到緩存信息,則初始化 */
        tableInfo = new TableInfo(clazz);
        GlobalConfig globalConfig;
        if (null != builderAssistant) {
            tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace());
            tableInfo.setConfiguration(builderAssistant.getConfiguration());
            globalConfig = GlobalConfigUtils.getGlobalConfig(builderAssistant.getConfiguration());
        } else {
            // 兼容測試場景
            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);

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

        return tableInfo;
    }

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

我們在定義實體類的同時,指定了該實體類對應的表名。

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

  /**
     * <p>
     * 初始化 表主鍵,表字段
     * </p>
     *
     * @param clazz        實體類
     * @param globalConfig 全局配置
     * @param tableInfo    數(shù)據(jù)庫表反射信息
     */
    public static void initTableFields(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo, List<String> excludeProperty) {
        /* 數(shù)據(jù)庫全局配置 */
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        ReflectorFactory reflectorFactory = tableInfo.getConfiguration().getReflectorFactory();
        //TODO @咩咩 有空一起來擼完這反射模塊.
        Reflector reflector = reflectorFactory.findForClass(clazz);
        List<Field> list = getAllFields(clazz);
        // 標記是否讀取到主鍵
        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;
            }

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

        /* 檢查邏輯刪除字段只能有最多一個 */
        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 語句是怎么注入的了,如果想要更加深入了解的小伙伴,可以自己根據(jù)上面的源碼方法深入去了解。

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

相關(guān)文章

  • idea自動加載html、js而無需重啟進程的操作

    idea自動加載html、js而無需重啟進程的操作

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

    使用Java讀取Word文件的簡單例子分享

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

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

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

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

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

    springboot整合mybatis plus與druid詳情

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

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

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

    maven在IDEA下載依賴包速度慢的問題解決

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

    Java多線程中的Interrupt簡析

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

    Java別說取余(%)運算簡單你真的會嗎

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

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

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

最新評論

黔江区| 德格县| 鹤庆县| 义马市| 浦县| 龙里县| 桓台县| 申扎县| 太白县| 广东省| 嘉峪关市| 洛扎县| 鸡东县| 长垣县| 盱眙县| 贵阳市| 漾濞| 伊金霍洛旗| 台前县| 加查县| 宽城| 东丰县| 滕州市| 汤阴县| 沙湾县| 黄山市| 瑞安市| 奉节县| 洪江市| 丰原市| 温州市| 太谷县| 石家庄市| 晋中市| 长阳| 塔城市| 拜泉县| 阳高县| 庆阳市| 拜城县| 涞水县|