流暢的orm讓我發現我抵觸的是mybatis而不是java

来源:https://www.cnblogs.com/xuejiaming/archive/2023/12/17/17910064.html
-Advertisement-
Play Games

流暢的orm讓我發現我抵觸的是mybatis而不是java 背景介紹 開發.net 也快10年了,到第三年的時候我已經漸漸瓶頸了,於是我在網上找各種資料但是大部分c#資料全是皮毛資料,稍微深一點點就再講表達式expression,感覺完全沒有那個深度,但是同時期的java講解的都是基本原理,和框架思 ...


流暢的orm讓我發現我抵觸的是mybatis而不是java

背景介紹

  開發.net 也快10年了,到第三年的時候我已經漸漸瓶頸了,於是我在網上找各種資料但是大部分c#資料全是皮毛資料,稍微深一點點就再講表達式expression,感覺完全沒有那個深度,但是同時期的java講解的都是基本原理,和框架思想,所以遇到瓶頸了我就會看java,我也是那個時候漸漸地掌握了兩門語言,對我而言我學的是java的思想(電腦的思想)主要是數據結構和演算法思想,這在同時期的c#資料是很難找到相同價值的。但是在使用java的3-4年時間裡面那種噁心的orm讓我也漸漸對其產生厭惡,因為java在那個時期對orm的需求僅僅只是能實現功能和結果集轉對象,更多的精力都是在大數據方向上,所以對我們這些crud仔而言orm及其不友好,尤其是用過c#的orm後,但是在工作不久後除了mybatis就是mybatis-plus,這讓業務開發的效率大大降低,bug率大大提升(c#的orm轉到java的orm而言),強類型和複雜sql不能共存仿佛成為了javaer口中的理所應當。

  經過不斷的努力終於在今年4月份正式發佈easy-query orm,這款orm參考了大量的c#的orm框架 efcorefreesqlsqlsugar等,也參考了大量的java的orm框架。站在各位大佬的肩膀上讓這個orm的開發周期大大降低,雖然java沒有c#的expression(非官方的有但是穩定性和安全性等堪憂),但是通過另闢蹊徑我也是找到了一條新的出路也算是讓java在編寫業務的時候可以流暢一把。

框架介紹

`easy-query`一款輕量級、高性能、強類型、易擴展符合C#開發者的JAVA自研ORM,擁有動態條件動態排序,自定義軟刪除,自定義條件攔截,單表多表,自定義sql,自定義函數,差異更新,分表分庫(支持跨庫跨表聚合查詢),支持高性能加密解密欄位模糊搜索等一系列功能

github地址 easy-query https://github.com/xuejmnet/easy-query

gitee地址 easy-query https://gitee.com/xuejm/easy-query

api預覽

新版本api entity-query擁有非常流暢和語義化的api,並且繼承所有之前的api可用,配合插件做到無需apt既可以動態變更代理對象實現無感開發編程

資料庫對象


@Data
@Table("t_topic")
@EntityFileProxy
public class Topic implements ProxyEntityAvailable<Topic , TopicProxy> {
    @Column(primaryKey = true)
    private String id;
    private Integer stars;
    private String title;
    private LocalDateTime createTime;

    @Override
    public Class<TopicProxy> proxyTableClass() {
        return TopicProxy.class;
    }
}

按id查詢

Topic topic = entityQuery.queryable(Topic.class)
                .whereById("1").firstOrNull();

==> Preparing: SELECT `id`,`stars`,`title`,`create_time` FROM `t_topic` WHERE `id` = ? LIMIT 1
==> Parameters: 1(String)

自定義條件查詢


List<Topic> list = entityQuery.queryable(Topic.class)
        .where(o -> {
            o.id().eq("1");
            o.createTime().le(LocalDateTime.now());
        })
        .toList();

==> Preparing: SELECT `id`,`stars`,`title`,`create_time` FROM `t_topic` WHERE `id` = ? AND `create_time` <= ?
==> Parameters: 1(String),2023-12-16T14:17:04.065(LocalDateTime)

count查詢

long count = entityQuery.queryable(Topic.class)
        .where(o -> {
            o.title().like("11");
            o.createTime().le(LocalDateTime.now());
        }).count();

==> Preparing: SELECT COUNT(*) FROM `t_topic` WHERE `title` LIKE ? AND `create_time` <= ?
==> Parameters: %11%(String),2023-12-16T14:17:04.065(LocalDateTime)

返回自定義列


List<Topic> list = entityQuery.queryable(Topic.class)
        .where(o->{
            o.title().like("123");
            o.createTime().ge(LocalDateTime.of(2022,2,1,3,4));
        })
        .orderBy(o -> {
            o.id().asc();
            o.createTime().desc();
        })
        .select(o->o.FETCHER.id().title())//僅返回id和title
        .toList();



==> Preparing: SELECT t.`id`,t.`title` FROM `t_topic` t WHERE t.`title` LIKE ? AND t.`create_time` >= ? ORDER BY t.`id` ASC,t.`create_time` DESC
==> Parameters: %123%(String),2022-02-01T03:04(LocalDateTime)


List<Topic> list = entityQuery.queryable(Topic.class)
        .where(o->{
            o.title().like("123");
            o.createTime().ge(LocalDateTime.of(2022,2,1,3,4));
        })
        .orderBy(o -> {
            o.id().asc();
            o.createTime().desc();
        })
        .select(o->o.FETCHER.allFieldsExclude(o.id()))//返回所有欄位除了id
        .toList();

==> Preparing: SELECT t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t WHERE t.`title` LIKE ? AND t.`create_time` >= ? ORDER BY t.`id` ASC,t.`create_time` DESC
==> Parameters: %123%(String),2022-02-01T03:04(LocalDateTime)

分組

List<Topic> list = entityQuery.queryable(Topic.class)
        .where(o->{
            o.title().like("123");
            o.createTime().ge(LocalDateTime.of(2022,2,1,3,4));
        })
        .groupBy(o-> o.id())//多個用GroupBy.of(.....)
        .select(Topic.class,(o,tr)->Select.of(
                o.id(),
                o.id().count().as(tr.stars())//count(id) as stars
        ))
        .toList();
        

==> Preparing: SELECT t.`id`,COUNT(t.`id`) AS `stars` FROM `t_topic` t WHERE t.`title` LIKE ? AND t.`create_time` >= ? GROUP BY t.`id`
==> Parameters: %123%(String),2022-02-01T03:04(LocalDateTime)

分頁


EasyPageResult<Topic> pageResult = entityQuery.queryable(Topic.class)
        .where(o -> {
            o.title().like("123");
            o.createTime().ge(LocalDateTime.of(2022, 2, 1, 3, 4));
        })
        .orderBy(o -> {
            o.id().asc();
            o.createTime().desc();
        })
        .select(o -> o.FETCHER.id().title())
        .toPageResult(1, 20);



==> Preparing: SELECT COUNT(*) FROM `t_topic` t WHERE t.`title` LIKE ? AND t.`create_time` >= ?
==> Parameters: %123%(String),2022-02-01T03:04(LocalDateTime)
<== Time Elapsed: 2(ms)
<== Total: 1
==> Preparing: SELECT t.`id`,t.`title` FROM `t_topic` t WHERE t.`title` LIKE ? AND t.`create_time` >= ? ORDER BY t.`id` ASC,t.`create_time` DESC LIMIT 20
==> Parameters: %123%(String),2022-02-01T03:04(LocalDateTime)
<== Time Elapsed: 3(ms)
<== Total: 20

join多表查詢

List<Topic> list = entityQuery.queryable(Topic.class)
        .leftJoin(Topic.class, (t, t1) -> {//第一個參數t表示第一個表,第二個參數t1表示第二個表
            t.id().eq(t1.id());
        })
        .where((t, t1) -> {
            t.title().like("11");
            t1.createTime().le(LocalDateTime.of(2021, 1, 1, 1, 1));
        }).select(Topic.class, (t, t1, tr) -> Select.of(//t表示sql的第一個表,t1表示第二個表,tr表示返回的結果匿名錶
                    t.FETCHER.id().stars(),//這兩者寫法是一樣的`FETCHER`是為了鏈式你也可以不用fetcher
                    t1.FETCHER.id().as(tr.title())
            )).toList();


==> Preparing: SELECT t.`id`,t.`stars`,t1.`id` AS `title` FROM `t_topic` t LEFT JOIN `t_topic` t1 ON t.`id` = t1.`id` WHERE t.`title` LIKE ? AND t1.`create_time` <= ?
==> Parameters: %11%(String),2021-01-01T01:01(LocalDateTime)

可能第一眼覺得select過於複雜




List<Topic> list = entityQuery.queryable(Topic.class)
        .leftJoin(Topic.class, (t, t1) -> {
            t.id().eq(t1.id());
        })
        .where((t, t1) -> {
            t.title().like("11");
            t1.createTime().le(LocalDateTime.of(2021, 1, 1, 1, 1));
        }).select(Topic.class, (t, t1, tr) -> Select.of(
                    t.id(),//不使用`FETCHER`直接返回也是可以的
                    t1.stars(),
                    t1.id().as(tr.title())
            )).toList();

排序


List<Topic> list = entityQuery.queryable(Topic.class)
        .leftJoin(Topic.class, (t, t1) -> {
            t.id().eq(t1.id());
        })
        .orderBy((t, t1) -> {
            t.id().asc();
            t1.createTime().desc();
        })
        //查詢t表的所有除了id和title,並且返回t1的title取別名為id
        .select(Topic.class,(t,t1,tr)->t.allFieldsExclude(t.id(),t.title())._concat(t1.title().as(tr.id())))
        .toList();

==> Preparing: SELECT t.`stars`,t.`create_time`,t1.`title` AS `id` FROM `t_topic` t LEFT JOIN `t_topic` t1 ON t.`id` = t1.`id` ORDER BY t.`id` ASC,t1.`create_time` DESC
<== Time Elapsed: 6(ms)
<== Total: 101

子表統計查詢


        List<BlogEntity> list = entityQuery.queryable(BlogEntity.class)
                .where(o -> {
                    //先對createTime進行格式化之後進行左匹配
                    o.createTime().dateTimeFormat("yyyy-MM-dd").likeMatchLeft("2023");
                })
                .select(o -> {
                    //構建子表統計
                    SQLSelectAsExpression subQuery = Select.subQueryAs(() -> {
                        return entityQuery.queryable(BlogEntity.class)
                                .where(x -> {
                                    x.id().eq(o.id());//條件就是主表的id和自己一樣
                                })
                                .select(x -> x.id().count());
                    }, o.createTime());//別名

                    return Select.of(
                            o.FETCHER.allFieldsExclude(o.title(), o.top()),
                            subQuery
                    );
                }).toList();

生成的sql


-- 第1條sql數據
SELECT
    t.`id`,
    t.`create_time`,
    t.`update_time`,
    t.`create_by`,
    t.`update_by`,
    t.`deleted`,
    t.`content`,
    t.`url`,
    t.`star`,
    t.`publish_time`,
    t.`score`,
    t.`status`,
    t.`order`,
    t.`is_top`,
    (SELECT
        COUNT(t1.`id`) 
    FROM
        `t_blog` t1 
    WHERE
        t1.`deleted` = false 
        AND t1.`id` = t.`id`) AS `create_time` 
FROM
    `t_blog` t 
WHERE
    t.`deleted` = false 
    AND DATE_FORMAT(t.`create_time`,'%Y-%m-%d') LIKE '2023%'

動態條件動態排序

後端管理往往需要複雜的動態條件組合和動態排序,稍不註意就會產生sql註入等問題

本框架給大伙帶來的動態解決方案可以說非常完美,支持單表,多表,單欄位排序,多欄位排序,並且不會出現sql註入等一系列問題

動態查詢1

//前段上傳的json對象
@Data
public class SysUserQueryRequest {
    private String name;
    private String account;
    private String departName;
    private String phone;
    private LocalDateTime createTimeBegin;
    private LocalDateTime createTimeEnd;
}

//由前端上傳json
SysUserQueryRequest sysUserQueryRequest = new SysUserQueryRequest();
sysUserQueryRequest.setName("小明");
sysUserQueryRequest.setCreateTimeBegin(LocalDateTime.now().plusDays(-10));
sysUserQueryRequest.setCreateTimeEnd(LocalDateTime.now());
sysUserQueryRequest.setPhone("180");


//快速實現分頁查詢 條件過濾預設非null不加入條件如果是字元串還需滿足非空
List<SysUser> pageResult = entityQuery.queryable(SysUser.class)
                .filterConfigure(NotNullOrEmptyValueFilter.DEFAULT)//非null並且字元串非空即加入條件
                .where(o -> {
                        o.name().like(sysUserQueryRequest.getName());
                        o.account().like(sysUserQueryRequest.getAccount());
                        o.phone().like(sysUserQueryRequest.getPhone());
                        o.departName().like(sysUserQueryRequest.getDepartName());
                        o.createTime().rangeClosed(sysUserQueryRequest.getCreateTimeBegin(), sysUserQueryRequest.getCreateTimeEnd());
                })
                .toList();

==> Preparing: SELECT `id`,`name`,`account`,`depart_name`,`phone`,`create_time` FROM `t_sys_user` WHERE `name` LIKE ? AND `phone` LIKE ? AND `create_time` >= ? AND `create_time` <= ? LIMIT 10
==> Parameters: %小明%(String),%180%(String),2023-11-11T21:51:34.740(LocalDateTime),2023-11-21T21:51:34.740(LocalDateTime)

動態查詢2

@Data
public class SysUserQueryRequest {
    @EasyWhereCondition
    private String name;
    @EasyWhereCondition
    private String account;
    @EasyWhereCondition
    private String departName;
    @EasyWhereCondition
    private String phone;
    @EasyWhereCondition(type = EasyWhereCondition.Condition.RANGE_LEFT_CLOSED,propName = "createTime" )
    private LocalDateTime createTimeBegin;
    @EasyWhereCondition(type = EasyWhereCondition.Condition.RANGE_RIGHT_CLOSED,propName = "createTime" )
    private LocalDateTime createTimeEnd;
}

//由前端上傳json
SysUserQueryRequest sysUserQueryRequest = new SysUserQueryRequest();
sysUserQueryRequest.setName("小明");
sysUserQueryRequest.setCreateTimeBegin(LocalDateTime.now().plusDays(-10));
sysUserQueryRequest.setCreateTimeEnd(LocalDateTime.now());
sysUserQueryRequest.setPhone("180");


//快速實現分頁查詢 動態對象條件
EasyPageResult<SysUser> pageResult = entityQuery.queryable(SysUser.class)
                        .whereObject(sysUserQueryRequest)
                        .toPageResult(1, 10);

==> Preparing: SELECT `id`,`name`,`account`,`depart_name`,`phone`,`create_time` FROM `t_sys_user` WHERE `name` LIKE ? AND `phone` LIKE ? AND `create_time` >= ? AND `create_time` <= ? LIMIT 10
==> Parameters: %小明%(String),%180%(String),2023-11-11T21:51:34.740(LocalDateTime),2023-11-21T21:51:34.740(LocalDateTime)

動態查詢3

最原始的方法

//由前端上傳json
SysUserQueryRequest sysUserQueryRequest = new SysUserQueryRequest();
sysUserQueryRequest.setName("小明");
sysUserQueryRequest.setCreateTimeBegin(LocalDateTime.now().plusDays(-10));
sysUserQueryRequest.setCreateTimeEnd(LocalDateTime.now());
sysUserQueryRequest.setPhone("180");


//快速實現分頁查詢 手動處理是否需要添加到查詢條件中
List<SysUser> pageResult = entityQuery.queryable(SysUser.class)
        .where(o -> {//條件裡面判斷是否要繼續
                o.name().like(EasyStringUtil.isNotBlank(sysUserQueryRequest.getName()),sysUserQueryRequest.getName());
                o.account().like(EasyStringUtil.isNotBlank(sysUserQueryRequest.getAccount()),sysUserQueryRequest.getAccount());
                o.phone().like(EasyStringUtil.isNotBlank(sysUserQueryRequest.getPhone()),sysUserQueryRequest.getPhone());
                o.departName().like(EasyStringUtil.isNotBlank(sysUserQueryRequest.getDepartName()),sysUserQueryRequest.getDepartName());
                o.createTime().rangeClosed(sysUserQueryRequest.getCreateTimeBegin() != null,sysUserQueryRequest.getCreateTimeBegin(),sysUserQueryRequest.getCreateTimeEnd() != null, sysUserQueryRequest.getCreateTimeEnd());
        })
        .toList();

動態排序


public class UISort implements ObjectSort {

    private final Map<String, Boolean> sort;

    public UISort(Map<String,Boolean> sort){

        this.sort = sort;
    }
    @Override
    public void configure(ObjectSortBuilder builder) {
        for (Map.Entry<String, Boolean> s : sort.entrySet()) {
            //自行判斷key和value是否為null 因為是包裝類型可能會出現npe
            // key為需要排序的屬性,value表示需要排序是不是asc
            builder.orderBy(s.getKey(),s.getValue());
        }
    }
}


HashMap<String, Boolean> propertySortMap = new HashMap<String, Boolean>() {{
    put("id", true);//id正序
    put("title", false);//標題倒序
}};
String sql = easyQuery.queryable(BlogEntity.class)
        .orderByObject(new UISort(propertySortMap))
        .toSQL();
Assert.assertEquals("SELECT `id`,`create_time`,`update_time`,`create_by`,`update_by`,`deleted`,`title`,`content`,`url`,`star`,`publish_time`,`score`,`status`,`order`,`is_top`,`top` FROM `t_blog` WHERE `deleted` = ? ORDER BY `id` ASC,`title` DESC",sql);

whereObject配合orderByObject將form表單查詢的難度降低到了一個人人可用的水平

最後

可能有很多小伙伴會推薦我jpa或者jooq我想說如果我沒能力那麼我可能會選擇他們,如果他們支持國產資料庫我可能會選擇他們,但是你我更願意推薦easy-query因為我會聆聽開發者的聲音起碼你叫的動我,我是一個在crud混的菜鳥開發,crud的困難,orm的困難必須是一個混跡在業務開發的程式員才能開發出來的好框架,在沒開發出這個api的時候已經有很多小伙伴使用lambda的api進行了開發反向非常不錯,期待您的使用。

easy-query

文檔地址 https://xuejm.gitee.io/easy-query-doc/

GITHUB地址 https://github.com/xuejmnet/easy-query

GITEE地址 https://gitee.com/xuejm/easy-query


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 介紹 一個源代碼生成器,用於向 C#項目添加一組用戶定義的 Win32 P/Invoke 方法和相關的類型。 鏈接地址: https://github.com/microsoft/CsWin32 還在手動添加平臺調用的代碼或者增加無用的程式集?微軟的官方解決方案來了! 特色 快速將 P/Invoke ...
  • 使用開源、免費軟體進行取證(不氪金取證方法) Autopsy https://www.autopsy.com Autopsy® is the premier end-to-end open source digital forensics platform. Built by Basis Techn ...
  • 習題要求 創建好作業後,先進入文件夾/home/acs/homework/lesson_3/,然後: (0) 進入homework_0文件夾,編寫自動完成lesson_1作業的腳本helper.sh。要求: [1] 當前目錄下僅包含helper.sh [2] helper.sh具有可執行許可權 [3] ...
  • 一、垂直分庫場景 場景:在業務系統中,涉及一下表結構,但是由於用戶與訂單每天都會產生大量的數據,單台伺服器的數據存儲以及處理能力是有限的,可以對資料庫表進行拆分,原有資料庫如下 說明1:整個業務系統中的表,大致分為四個,商品信息類的表,訂單相關的表,用戶相關表及省市區相關的表,這裡暫時將省市區的表和 ...
  • 一、Storm集群構建 編寫storm 與 zookeeper的yml文件 storm yml文件的編寫 具體如下: version: '2' services: zookeeper1: image: registry.aliyuncs.com/denverdino/zookeeper:3.4.8 ...
  • 1、JetPack Compose、組合函數與註解和文本修改 1、JetPack Compose:Jetpack Compose 是由 Google 推出的用於構建 Android 用戶界面的現代化工具包。它是一個聲明式的 UI 工具包,用於簡化 Android 應用程式的用戶界面設計和開發。Jet ...
  • Chrome 擴展項目使用前端 html,css,js 基礎技術開發,一大痛點就是改動代碼後的擴展更新問題。仔細想想想,目前前端項目開發已經有 HMR 熱重載技術,在開發 Web 頁面時可以實時查看效果,極大的提升了開發體驗。那麼,能否把這種極致的體驗帶到 Chrome 擴展開發中來呢?經過多番折騰... ...
  • 三、python數據、變數、註釋 1.數據 什麼是數據?在python中像數字,漢字,英文,圖片,音頻都是數據。目前瞭解就可以了 打開命令視窗,win+r 輸入cmd 打開python 2.變數和標識符 什麼是變數? 當我們編寫代碼的時候,我們會找一塊地方存放數據,而數據存放一個的時候還特別好找,當 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...