jSqlBox5.0.1 版,參數內嵌式 SQL 了解一下,把 SQL 掰直了寫

2020-12-25 開源中國

jSqlBox主要特點是架構優、尺寸小、功能全,基本上所有與資料庫操作相關的功能,jSqlBox都已提供。它的主要特點有:  

1.內核基於DbUtils並與之兼容,最差情況下可以降級當成DbUtils來使用,上手快。  
2.提倡在java裡拼寫SQL,獨創參數內嵌式SQL寫法(下面會詳細介紹)。  
3.只有單個1M大小的jar包,不依賴任何第三方庫,不依賴Spring(但也支持在Spring環境下使用)。  
4.支持分庫分表、聲明式事務、分布式事務、緩存翻譯、長文本、ActiveRecord。
5.支持80多種資料庫方言,分頁、函數變換、DDL生成、實體源碼生成、實體或資料庫結構導出Excel。
6.主要的實體類註解兼容JPA標準。  

擁抱SQL,在Java中直接寫SQL是jSqlBox的主要特點,如果對SQL精通,也可以只利用SQL來完成項目的開發。說SQL開發效率不如ORM,只是因為沒有把SQL寫到極致。上次有人說「直接在JAVA裡寫SQL有什麼出奇,我一直這麼玩」。我回答:「如果不出意外,jSqlBox玩的比你更調皮」。這是因為jSqlBox採用的"參數內嵌式SQL寫法",它的功能比你能想到的還要多得多。  

傳統的SQL有什麼問題? 最大的問題就是它是「彎」的,下面這個SQL大家看出問題沒?
DB.exe("insert into users (id, name, age, address) values(?, ?, ?, ?)", param(1, "張三", 10, "北京"));
name、問號、和它的實參"張三",這三個關聯的要素出現在三個間隔遙遠的位置,這是違反常理的,只有寫成下面這樣,才會把三個關聯要素在縱向對齊在一起: 
insert into users (id,   name,   age,   address)
                 values(?,    ?,      ?,     ?)
                param(1,   "張三",  10,    "北京"));
也就是說,現在的SQL要做到可維護性好,就必須「彎」著寫。當然實際項目因為動態參數的問題,沒人把SQL彎著寫,所以問題就來了,參數一多,列名、問號、參數這三者之就配對困難,程式設計師要經常1、2、3、4去數數,影響開發和維護效率。  
jSqlBox為了解決這個問題,採取的方案是參數內嵌式SQL寫法,把SQL給掰直了:
DB.exe("insert into users (id ", param(1), ",name ", par("張三"), ", age", par(10), ",address)", par("北京") , valuesQuestions());
這種寫法無論SQL寫多長,都不影響可維護性,如果要獲得更好的可維護性,可以把它豎過來寫,新增欄位只要添加一行即可,注意豎過來寫也是直的,不是彎的:

DB.exe("insert into users (id ", param(1), //                ",name ", par("張三"), //                ", age", par(10), //                ",address)", par("北京") ,                valuesQuestions());

具體jSqlBox使用說明請見它的用戶手冊,本文是對jSqlBox參數內嵌式SQL寫法的詳細介紹:

參數內嵌式SQL是jSqlBox的首創,在SQL裡直接寫參數,SQL執行時自動轉化為preparedStatement,這種方式的優點是被賦值的欄位和實際參數可以寫在同一行上,欄位很多時利於維護,也方便根據不確定的條件動態拼接SQL。SQL參數必須放在par或que方法裡,如果是SqlResultHandler、拉截器、Connection、DbContext, SqlItem等已知類型的對象,則不必用方法括住。字符串類型如果不放在par或que方法裡的話則視為SQL文本的一部分。
很多場合業務邏輯不複雜,但是欄位很多,SQL寫得很長,當要添加、修改一個欄位時,光是找到這個欄位和它對應的是哪一個參數就很麻煩(用模板是一種方案,但模板佔位符要多打幾個字,模板本身的快速定位查找也是個問題,因為通常IDE不支持定位到XML或文本文件的某一行。) 利用SQL內嵌參數這種寫法,可以方便地增加、刪除欄位,因為每一個欄位和它對應的實參都寫在了同一行上。
參數內嵌式SQL是jSqlBox5.0版起的默認書寫格式,所有以qry/ins/exe/upd/entity打頭的方法都採用參數內嵌式SQL寫法。

參數內嵌式SQL示例:

DbContext db= new DbContext(dataSource); db.exe("insert into users (", // " name ,", par("Sam"), //一個參數寫一行 notNull("age,", user.getAge()), //notNull方法的第二個參數為null時,這一項將不會添加到SQL中 " address ", par("Canada"), // ") ", valuesQuestions()); //自動根據參數個數補上 values(?,?...?)片段 db.exe("update users set name=?,address=?", par("Tom", "China"));//參數也可以連寫 Assert.assertEquals(1L, ctx.iQueryForObject("select count(*) from users where name=? and address=?", par("Tom", "China"))); db.exe("delete from users where name=", que("Tom"), " or address=", que("China"));//問號也可以省

上例中SQL只有幾行,還看不出它的優點,但是如果有二十行、二十個參數,就能體會這種寫法的好處了。

上例的nutNull、par、que等方法是用「import static com.github.drinkjava2.jsqlbox.DB.*; 」這種靜態引入方式使用。que(或ques)與par(或param)方法的區別是que會在原地留下一個問號字符串去拼SQL,而par僅會返回一個「」空字符串,到底用que還是par要視具體情況而定。

在DB中定義了大量的靜態方法供使用,當系統中設定好一個DbContext默認全局實例後,可以直接引用DB工具類中的SQL方法,例如:

DbContext.setGlobalDbContext(new DbContext(someDataSource));exe("delete from users where userId=",que(1)); //直接使用靜態方法

上述靜態引入DB類的SQL方法的局限是只限於單數據源場合。

DB不是jSqlBox的核心類 ,如果對它的方法命名不滿意,用戶可以根據它的源碼寫出自已喜歡的靜態方法庫,以方便靜態引入。

當需要根據複雜的條件來動態拼接SQL時,參數內嵌式SQL寫起來也很簡單:

ctx.exe("insert into users (", // " name", par(name), // when(age!=null," ,age ", par(age)), //when是條件判斷,相當於IF " ,address ", par(address), //這裡只能用par,因為valuesQuestions方法會補足問號 ") ", valuesQuestions()); ctx.exe("update users set ", // " name=", que(name), //這裡也可以寫成 " name=? ", par(name) when(age!=null, ", age=", que(age)), // when(address!=null, ", address=", que(address)), // " where name is not null" ); Assert.assertEquals(1L, ctx.qryLongValue(// "select count(*) from users where 1=1 ", // when(name!=null," and name=", que(name)),// when("Tom".equals(name)," and name=", que(name)),// when("China".equals(address)," and address=", que(address)),// " order by name" ));

這有點類似模板語言,但比模強的地方是無需學習模板語法,Java本身就是最好的模板,而且Java方法可以隨時自定義添加,具體怎麼添加大家可以看一下DB.par()、DB.when()等方法的源碼就明白了,僅有1行代碼。

參數內嵌式SQL要求所有的方法最後一個參數是一個不定長對象數組,傳入的內容分為以下各種情況:

  • 用來拼接SQL和參數的元素, 如:
    字符串類型: 會被解釋為SQL文本
    數組類型: 會被遞歸解析,直到數組不再有嵌套數組為止
    param或par(參數1,參數2...): 會被解釋為SQL參數並在原地返回一個空字符串,它定義在DB類中,通常靜態引入使用,下同 ques或que(參數)會被解釋為SQL參數, 並在原地返回一個問號字符串 notNull(str,obj) obj非空時,會被解釋為SQL參數,並在SQL中添加str作為SQL文本
    noNull(str,obj...) 沒有一個obj為空時,將所有obj參數相連成一個SQL參數,並在SQL中添加str作為SQL文本
    valuesQuestions() 自動根據參數個數,生成一個values(?,?...?)SQL文本片段
    CustomizedSqlItem: 自定義的特殊SQL條目,用戶可以自已定義如何來翻譯成SQL或參數。 when(boolean, obj...) 根據條件返回對象數組,如條件不滿足則返回一個空字符串, when支持嵌套。
  • 特殊類型, 如:
    pagin(pageNumber,pageSize) 會被解釋為一個分頁攔截器, 詳見分頁一節
    other(obj...)方法,將一些額外參數(通常是欄位別名或顯示寬度等)保存在線程局部變量,並返回一個空字符串,用DB.getOthers()可以獲取保存的參數
    shardTB(shardvalues) 根據傳入值生成分表後的表名字符串,詳見分庫分表一節
    shardDB(shardvalues) 根據傳入值解釋為分庫後的DbContext,詳見分庫分表一節
    shard(shardvalues) 根據傳入值會解釋為分庫後的DbContext和表名,詳見分庫分表一節
    Xxxx.class: 如果一個參數是User.class這種類型,表示SQL方法將根據User類來翻譯成SQL,常用於Text類多行文本解析和實體類查詢。
    TableModel實例:傳入一個TableModel可以進行覆蓋實體到數據表的預設配置,詳見動態配置一節。
    SqlResultHandler實例: 某些方法需要傳入一個SqlResultHander參數,詳見DbUtils
    SqlHander拉截器實例: 傳入SqlHandler攔截器,詳見攔截器一章。
    Connection實例:傳入Connection實例, 運行期由這個Connection去執行SQL。 DbContext實例:傳入DbContext實例, 運行期由這個實例去執行SQL。
    SqlTemplateEngine實例:當接收到一個SqlTemplateEngine接口的實例後(如DB.TEMPLATE),SQL轉為模板方式運行
    IGNORE_NULL 這是一個開關參數,當實體插入和修改時(即entityInsert/entityUpdate方法),忽略掉實體的所有null值欄位
    IGNORE_EMPTY 這是一個開關參數,當實體插入和修改時,忽略掉實體的所有null值欄位和空字符串欄位。

對於jSqlBox的SQL方法變長參數的理解,可以將它看成是Windows作業系統下的消息,每一個SQL條目,只是一個消息而已,jSqlBox將會匯總所有消息並把它們翻譯成實際的SQL或參數並執行,在jSqlBox中所有內容都可以作為參數傳遞,如攔截器、模板引擎、甚至DbContext實例本身,也可以作為參數傳遞(這種情況下,傳入的DbContext實例將奪取SQL執行權,常用於多數據源場合)。另一方面,jSqlBox的大多數SQL方法、CURD方法(包括ActiveRecord的方法),都允許額外附加不限數量的SQL條目,以實現最大的靈活性,這就是為什麼jSqlBox中的大多數方法最後一個參數都是一個可變對象數組參數的原因。

最後再上幾個複雜點的例子,顯示參數內嵌式SQL的靈活強大:

//寫出支持重構的SQL:ctx.iExecute("insert into ", USER, " ( ", //USER、NAME是在User類中定義的常量,靜態引入 NAME, ",", par("Sam"), // ADDRESS, " ", par("Canada"), // ") ", valuesQuesions());//傳入一個自帶模板對象DB.TEMPLATE,就可以使用SQL模板了。如果傳入Beetl模板就會支持複雜的模板語法了UserAR sam = new UserAR("Sam", "Canada");UserAR tom = new UserAR("Tom", "China");paramMap.put("user", sam);ctx2.exe(DB.TEMPLATE, "insert into users (name, address) values(#{user.name},:user.address)", paramMap);ctx2.exe(DB.TEMPLATE,"update users set name=#{user.name}, address=:user.address", bind("user", tom));Assert.assertEquals(1L, ctx2.qryLongValue(TEMPLATE,"select count(*) from users where name=#{name} and address=:addr", bind("name", "Tom", "addr", "China")));//other方法可以存放任意額外信息,用DB.others()方法可獲取,為什麼顯示列寬和顏色要寫到SQL裡? 這是給前後端是同一個人時設計的,參見GoSqlGo項目Map<String, Object> result = DB.qryMap("select ", // " id", other("id", 10), //jSqlBox是GoSqlGo唯一指定DAO工具 when(u.age==5, ", name as name1 ", other("姓名1", "年紀=5", "註:用紅字顯示")), // when(true, ", name as name2 ", other("姓名2", "顯示列寬=10")), // " from TitleDemoEntity", // " where id<>", que("a"), // when(name != null, " and name like ", que("%" + name + "%")), // new PrintSqlHandler() //); //萬物皆可傳new User(100, "Tom", "China").update(ctx2," and age>?", param(5), IGNORE_EMPTY, new PrintSqlHander());

上例最後一行做了以下事情:
手工切換到ctx2這個DbContext實例上(即操作另一個數據源)
ActiveRecord實體User主鍵為100的記錄,如果age欄位大於5則更新它的內容 勿略User實體的所有null或空值屬性
列印SQL到控制臺
如果User類ID上有@ShardDB或@ShardTB註解,會根據ID=100的值進行分庫分表操作

DbContext和DB類中定義的參數內嵌式SQL方法一覽:

qry(Object...) 執行一個查詢,返回類型由傳入的SqlResultHander或SqlHander來決定qryObject(Object...) 執行一個查詢,返回一個Object值qryLongValue(Object...) 執行一個查詢,返回一個long值qryIntValue(Object...) 執行一個查詢,返回一個int值qryString(Object...) 執行一個查詢,返回一個字符串值qryMapList(Object...) 執行一個查詢,返回一個List<map<String,Object>>類型qryMap(Object...) 執行一個查詢,將第一行記錄返回一個map<String,Object>類型qryList(Object...) 執行一個查詢,將第一列記錄返回一個List<Object>類型qryEntityList(Object...) 執行一個查詢,第一個參數通常是實體類類型,返回一個實體列表upd(Object...) 執行一個SQL, 等效於DbUtils的update方法,但不用捕獲導常ins(Object...) 執行一個insert SQL,等效於DbUtils的insert方法,但不用捕獲導常exe(Object...) 執行一個SQL, 等效於DbUtils的execute方法,但不用捕獲導常entityXxxx(Object...) 實體相關的一系列CURD方法,詳見entity方法一節

從5.0版起,jSqlBox刪除了p、i、n、t、e開頭的方法,只使用參數內嵌式風格為唯一書寫SQL方式,e開頭的方法改為entityXxxx形式,這樣改進後方法更少,可讀性和可維護性也更好。

本次5.0.1.jre8版更新內容:

  • 去除pinte系列方法, i系統方法改為qry/ins/exe/upd系列方法, e系列方法改為entity打頭,t系列方法改為傳入模板,p和n這兩種寫法因為很少用,所以直接取消,詳見用戶手冊。5.0版不再兼容4.0版,精簡和重命名是為了更好的發展,或者說胳膊扭不過大腿,不止一次被人抱怨這個p/i/n/t/e的命名了。
  • 去除DB.sql()方法,默認字符串都是SQl片段
  • 大寫的PARAM、QUES、VALUESQUESTION方法去掉,大寫的PARAM很少用到,而且容易與小寫的param方法混淆。
  • PrintSqlhandler調試攔載器改進為可以輸出參數代入後的完整SQL,方便粘貼到SQL工具裡運行。例如本文開頭的示例會輸出為:
        insert into users (id, name, age, address) values(1, '張三', 10, '北京')
  • 添加DB.other、DB.when、DB.par、DB.que四個方法,DB.par等同於舊版的DB.param方法, DB.que等同於舊版的DB.ques方法。
  • 添加@UUID26註解
  • SQL參數和Java類型轉換做成可配置,見jSqlBox配置一節
  • StrUtils工具類中的array靜態方法,當條目為空時返為(null)
  • 新增DB.qryList方法,返回查詢內容的第一行內容
  • 新增DB.qryMap方法,返回查詢內容的第一列內容
  • 不再使用JDBPRO類,只保留DB一個靜態工具類

相關焦點

  • 女朋友都能看懂的,SQL優化乾貨
    ,如果沒有找到就直接返回0 ,所以可以使用如下sql:select * from teacher where INSTR(name,'老師')>02、使用了in和not in,會全表掃描普通查詢:
  • 不懂就問:SQL 語句中 where 條件後 寫上1=1 是什麼意思
    if(condition 2) { sql=sql+" and var3=value3";}如果我們不寫1=1的話,當condition 1為真時,代碼拼接後被執行的SQL代碼如下:select *from table_namewhere
  • MySQL怎麼刪除#sql開頭的臨時表
    處理方法3.1   同時存在.frm 和.ibd名稱相同的文件如果 #sql-*.ibd 和 #sql-*.frm兩個文件都存在數據目錄裡的話,可以直接drop table。但注意刪除時候表名的變化。/* 直接刪除,表名前加#mysql50*/root@testdb 01:42:57> DROP TABLE `#mysql50##sql-ib87-856498050`;註: #mysql50#前綴是MySQL 5. 1 中引入的文件名安全編碼。另外,表名因不符合命名規範,想要執行該腳本需要將表名用反引號括起來。
  • sql盲注
    http://localhost/sqli/Less-8/?id=1『 and (select ascii(substr((select column_name from information_schema.columns where table_name=『users『 limit 0,1),2,1)))=105--+第一個欄位 117,115,101,114,95,105,100
  • 從0 開始手寫一個 Mybatis 框架,三步搞定!
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫繼上一篇手寫SpringMVC之後,我最近趁熱打鐵,研究了一下Mybatis。MyBatis框架的核心功能其實不難,無非就是動態代理和jdbc的操作,難的是寫出來可擴展,高內聚,低耦合的規範的代碼。
  • 關於SQL注入教程
    開始教程: 什麼是SQL注入 sql注入:利用現有應用程式,將(惡意)的SQL命令注入到後臺資料庫執行一些惡意的操作
  • FreeSql 正式發布 1.10.1 版本
    QQ群:4336577(已滿)、8578575(在線)、52508226(在線)增加這些內容只是為了洗稿1.5 至 1.10 的主要變更:1.5.0 -> 1.10.0 更新的重要功能如下:一、增加 Firebird
  • 超級SQL注入工具(起步篇)-附下載
    Net Framework 2.0。運行環境XP、Win7,Win10環境已測試,其他環境請自測下載地址:公眾號後臺回復「sql注入」即可程序簡介1.1.11.導出配置點擊導出配置,將選擇需要導出程序配置信息的路徑,程序將導出配置信息到 一個XML文件中,以後可以使用菜單中的導入配置來加載配置信息。2.注入中心2.1.數據包方法一:在數據包中輸入URL地址http://127.0.0.1:8090/mysql.jsp?
  • 經典SQL面試題及答案分析
    *(SUM(case when a.score>=70 and a.score<=80 then 1 else 0 end)/SUM(case when a.score then 1 else 0 end)),2) as 中等率, ROUND(100*(SUM(case when a.score>=80 and a.score<=90 then 1 else 0 end)/SUM
  • delete關鍵字了解一下
    在上一篇文章中我們學習了如何更新mysql中的數據內容,用到的是update這個關鍵字,今天我們要學習的是如何讓在mysql中刪除記錄,也就是從箱子裡面拿走東西,用到的關鍵字是delete這個關鍵字,下面我們就通過一個例子來了解一下。
  • MyBatis動態SQL(認真看看, 以後寫SQL就爽多了)
    如我們在寫前面的[在 WHERE 條件中使用 if 標籤] SQL 的時候, where 1=1 這個條件我們是不希望存在的。4.1 where4.1.1 查詢條件根據輸入的學生信息進行條件檢索。不使用 where 1=1。
  • 都是SQL「注釋」惹的禍(上)
    附3.1 創建表sql:create tableT_IM_PURINWAREHSENTRY( fid                     VARCHAR2, freturnsqty             NUMBER(28,16) default 0 not null, fprice                  NUMBER(28,16) default 0 not null, famount                 NUMBER(19,4) default 0 not null,
  • 若依後臺管理系統 3.0 發布,進行模塊拆分
    若依管理系統 v3.0 已發布,更新日誌:1、升級poi到最新版3.172、導出修改臨時目錄絕對路徑3、升級laydate升級到最新版5.0.94、升級SpringBoot
  • JavaMelody v1.57.0 發布,系統監控工具
    JavaMelody v1.57.0 發布,此版本更新內容如下:fix: check if async before flushing the response (ee87b4b
  • [開源] .Net orm FreeSql 1.5.0 最新版本(番號:好久不見)
    很久很久沒有寫文章了,上一次還是在元旦發布 1.0 版本的時候,今年版本規劃是每月底發布小版本(年底發布 2.0),全年的開源工作主要是收集用戶需求增加功能,完善測試,修復 bug。FreeSql 1.0 -> 1.5 相隔半年有哪些新功能?只能說每個功能都能讓我興奮,並且能感受到使用者也一樣興奮(妄想症)。
  • RuoYi 4.3.1 發布,請及時更換默認秘鑰 - OSCHINA - 中文開源技術...
    ; 1.5.2 版本存在一處權限繞過漏洞,當受影響版本的Shiro框架結合Spring dynamic controllers使用時,未經授權的遠程攻擊者可以通過精心構造的請求包進行權限繞過,可能造成鑑權系統失效以及後臺功能暴露。
  • Presto 分布式SQL查詢引擎及原理分析
    用戶可以使用標準SQL進行數據查詢和分析計算;5.擴展性:有眾多 SPI 擴展點支持,開發人員可編寫UDF、UDTF。/presto --server host:8088 --catalog hive --schema default(左右滑動查看全部代碼)先解釋下各參數的含義:--server 是presto服務地址;--catalog 是默認使用哪個數據源