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要求所有的方法最後一個參數是一個不定長對象數組,傳入的內容分為以下各種情況:
對於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形式,這樣改進後方法更少,可讀性和可維護性也更好。