sqltoy-ormORM 框架
sqltoy-orm是比hibernate+myBatis更加貼合項目的orm框架,具有hibernate增刪改的便捷性同時也具有比myBatis更加靈活優(yōu)雅的自定義sql查詢功能。 支持以下數(shù)據(jù)庫:
- oracle 11g+
- db2 9.5+,建議從10.5 開始
- mysql(mariadb/innosql)支持5.6、5.7、8.0 版本
- postgresql(greenplum) 支持9.5 以及以上版本
- sqlserver 2012+
- sqlite
- DM達夢數(shù)據(jù)庫
- elasticsearch 只支持查詢,版本支持5.7+版本,建議使用7.3以上版本
- clickhouse
- StarRocks(原dorisdb)
- oceanBase
- guassdb
- tidb
- impala(kudu)
- kingbase
- mongodb (只支持查詢)
- 其他數(shù)據(jù)庫支持基于jdbc的sql執(zhí)行(查詢和自定義sql的執(zhí)行)
1. 快速特點說明
1.1 最優(yōu)雅直觀的sql編寫模式
- sqltoy 的寫法(一眼就看明白sql的本意,后面變更調(diào)整也非常便捷,copy到數(shù)據(jù)庫客戶端里稍做出來即可執(zhí)行)
- sqltoy條件組織原理很簡單: 如 #[order_id=:orderId] 等于if(:orderId<>null) sql.append(order_id=:orderId);#[]內(nèi)只要有一個參數(shù)為null即剔除
- 支持多層嵌套:如 #[and t.order_id=:orderId #[and t.order_type=:orderType]]
- 條件判斷保留#[@if(:param>=xx ||:param<=xx1) sql語句] 這種@if()高度靈活模式,為特殊復(fù)雜場景下提供萬能鑰匙
select *
from sqltoy_device_order_info t
where #[t.ORDER_ID=:orderId]
#[and t.ORGAN_ID in (:authedOrganIds)]
#[and t.STAFF_ID in (:staffIds)]
#[and t.TRANS_DATE>=:beginDate]
#[and t.TRANS_DATE<:endDate]
- mybatis的寫法(一板一眼很工程化)
select *
from sqltoy_device_order_info t
<where>
<if test="orderId!=null">
and t.ORDER_ID=#{orderId}
</if>
<if test="authedOrganIds!=null">
and t.ORGAN_ID in
<foreach collection="authedOrganIds" item="order_id" separator="," open="(" close=")">
#{order_id}
</foreach>
</if>
<if test="staffIds!=null">
and t.STAFF_ID in
<foreach collection="staffIds" item="staff_id" separator="," open="(" close=")">
#{staff_id}
</foreach>
</if>
<if test="beginDate!=null">
and t.TRANS_DATE>=#{beginDate}
</if>
<if test="endDate!=null">
and t.TRANS_DATE<#{endDate}
</if>
</where>
1.2 天然防止sql注入,執(zhí)行過程:
- 假設(shè)sql語句如下
select *
from sqltoy_device_order_info t
where #[t.ORGAN_ID in (:authedOrganIds)]
#[and t.TRANS_DATE>=:beginDate]
#[and t.TRANS_DATE<:endDate]
- java調(diào)用過程
sqlToyLazyDao.findBySql(sql, new String[] { "authedOrganIds","beginDate", "endDate"}, new Object[] { authedOrganIdAry,beginDate,null}, DeviceOrderInfoVO.class);
- 最終執(zhí)行的sql是這樣的:
select *
from sqltoy_device_order_info t
where t.ORDER_ID=?
and t.ORGAN_ID in (?,?,?)
and t.TRANS_DATE>=?
- 然后通過: pst.set(index,value) 設(shè)置條件值,不存在將條件直接作為字符串拼接為sql的一部分
1.3 最強大的分頁查詢
1.3.1 分頁特點說明
- 1、快速分頁:@fast() 實現(xiàn)先取單頁數(shù)據(jù)然后再關(guān)聯(lián)查詢,極大提升速度。
- 2、分頁優(yōu)化器:page-optimize 讓分頁查詢由兩次變成1.3~1.5次(用緩存實現(xiàn)相同查詢條件的總記錄數(shù)量在一定周期內(nèi)無需重復(fù)查詢)
- 3、sqltoy的分頁取總記錄的過程不是簡單的select count(1) from (原始sql);而是智能判斷是否變成:select count(1) from 'from后語句', 并自動剔除最外層的order by
- 4、在極特殊情況下sqltoy分頁考慮是最優(yōu)化的,如:with t1 as (),t2 as @fast(select * from table1) select * from xxx 這種復(fù)雜查詢的分頁的處理,sqltoy的count查詢會是:with t1 as () select count(1) from table1, 如果是:with t1 as @fast(select * from table1) select * from t1 ,count sql 就是:select count(1) from table1
1.3.1 分頁sql示例
<!-- 快速分頁和分頁優(yōu)化演示 --> <sql id="sqltoy_fastPage"> <!-- 分頁優(yōu)化器,通過緩存實現(xiàn)查詢條件一致的情況下在一定時間周期內(nèi)緩存總記錄數(shù)量,從而無需每次查詢總記錄數(shù)量 --> <!-- alive-max:最大存放多少個不同查詢條件的總記錄量; alive-seconds:查詢條件記錄量存活時長(比如120秒,超過閥值則重新查詢) --> <page-optimize parallel="true" alive-max="100" alive-seconds="120" /> <value> <![CDATA[ select t1.*,t2.ORGAN_NAME -- @fast() 實現(xiàn)先分頁取10條(具體數(shù)量由pageSize確定),然后再關(guān)聯(lián) from @fast(select t.* from sqltoy_staff_info t where t.STATUS=1 #[and t.STAFF_NAME like :staffName] order by t.ENTRY_DATE desc ) t1 left join sqltoy_organ_info t2 on t1.organ_id=t2.ORGAN_ID ]]> </value> <!-- 這里為極特殊情況下提供了自定義count-sql來實現(xiàn)極致性能優(yōu)化 --> <!-- <count-sql></count-sql> --> </sql>
1.3.3 分頁java代碼調(diào)用
/** * 基于對象傳參數(shù)模式 */ public void findPageByEntity() { Page pageModel = new Page(); StaffInfoVO staffVO = new StaffInfoVO(); // 作為查詢條件傳參數(shù) staffVO.setStaffName("陳"); // 使用了分頁優(yōu)化器 // 第一次調(diào)用:執(zhí)行count 和 取記錄兩次查詢 Page result = sqlToyLazyDao.findPageBySql(pageModel, "sqltoy_fastPage", staffVO); System.err.println(JSON.toJSONString(result)); // 第二次調(diào)用:過濾條件一致,則不會再次執(zhí)行count查詢 //設(shè)置為第二頁 pageModel.setPageNo(2); result = sqlToyLazyDao.findPageBySql(pageModel, "sqltoy_fastPage", staffVO); System.err.println(JSON.toJSONString(result)); } /** * 基于參數(shù)數(shù)組傳參數(shù) */ public void findPageByParams() { Page result = sqlToyLazyDao.findPageBySql(new Page(), "sqltoy_fastPage",MapKit.keys("staffName").values("陳"),StaffInfoVO.class); System.err.println(JSON.toJSONString(result)); }
1.4 最巧妙的緩存應(yīng)用,將多表關(guān)聯(lián)查詢盡量變成單表(看下面的sql,如果不用緩存翻譯需要關(guān)聯(lián)多少張表?sql要有多長?多難以維護?)
- 1、 通過緩存翻譯: 將代碼轉(zhuǎn)化為名稱,避免關(guān)聯(lián)查詢,極大簡化sql并提升查詢效率
- 2、 通過緩存名稱模糊匹配: 獲取精準(zhǔn)的編碼作為條件,避免關(guān)聯(lián)like 模糊查詢
<sql id="sqltoy_order_search"> <!-- 緩存翻譯設(shè)備類型 cache:具體的緩存定義的名稱, cache-type:一般針對數(shù)據(jù)字典,提供一個分類條件過濾 columns:sql中的查詢字段名稱,可以逗號分隔對多個字段進行翻譯 cache-indexs:緩存數(shù)據(jù)名稱對應(yīng)的列,不填則默認為第二列(從0開始,1則表示第二列), 例如緩存的數(shù)據(jù)結(jié)構(gòu)是:key、name、fullName,則第三列表示全稱 --> <translate cache="dictKeyNameCache" cache-type="DEVICE_TYPE" columns="deviceTypeName" cache-indexs="1"/> <!-- 員工名稱翻譯,如果同一個緩存則可以同時對幾個字段進行翻譯 --> <translate cache="staffIdNameCache" columns="staffName,createName" /> <filters> <!-- 反向利用緩存通過名稱匹配出id用于精確查詢 --> <cache-arg cache-name="staffIdNameCache" param="staffName" alias-name="staffIds"/> </filters> <value> <![CDATA[ select ORDER_ID, DEVICE_TYPE, DEVICE_TYPE deviceTypeName,-- 設(shè)備分類名稱 STAFF_ID, STAFF_ID staffName, -- 員工姓名 ORGAN_ID, CREATE_BY, CREATE_BY createName -- 創(chuàng)建人名稱 from sqltoy_device_order_info t where #[t.ORDER_ID=:orderId] #[and t.STAFF_ID in (:staffIds)] ]]> </value> </sql>
最跨數(shù)據(jù)庫
- 1、提供類似hibernate性質(zhì)的對象操作,自動生成相應(yīng)數(shù)據(jù)庫的方言。
- 2、提供了最常用的:分頁、取top、取隨機記錄等查詢,避免了各自不同數(shù)據(jù)庫不同的寫法。
- 3、提供了樹形結(jié)構(gòu)表的標(biāo)準(zhǔn)鉆取查詢方式,代替以往的遞歸查詢,一種方式適配所有數(shù)據(jù)庫。
- 4、sqltoy提供了大量基于算法的輔助實現(xiàn),最大程度上用算法代替了以往的sql,實現(xiàn)了跨數(shù)據(jù)庫
- 5、sqltoy提供了函數(shù)替換功能,比如可以讓oracle的語句在mysql或sqlserver上執(zhí)行(sql加載時將函數(shù)替換成了mysql的函數(shù)),最大程度上實現(xiàn)了代碼的產(chǎn)品化。 default:SubStr\Trim\Instr\Concat\Nvl 函數(shù);可以參見org.sagacity.sqltoy.plugins.function.Nvl 代碼實現(xiàn)
<!-- 跨數(shù)據(jù)庫函數(shù)自動替換(非必須項),適用于跨數(shù)據(jù)庫軟件產(chǎn)品,如mysql開發(fā),oracle部署 --> <property name="functionConverts" value="default"> <!-- 也可以這樣自行根據(jù)需要進行定義和擴展 <property name="functionConverts"> <list> <value>org.sagacity.sqltoy.plugins.function.Nvl</value> <value>org.sagacity.sqltoy.plugins.function.SubStr</value> <value>org.sagacity.sqltoy.plugins.function.Now</value> <value>org.sagacity.sqltoy.plugins.function.Length</value> </list> </property> --> </bean>
1.5 提供行列轉(zhuǎn)換(數(shù)據(jù)旋轉(zhuǎn)),避免寫復(fù)雜的sql或存儲過程,用算法來化解對sql的高要求,同時實現(xiàn)數(shù)據(jù)庫無關(guān)(不管是mysql還是sqlserver)
<!-- 列轉(zhuǎn)行測試 --> <sql id="sys_unpvoitSearch"> <value> <![CDATA[ SELECT TRANS_DATE, sum(TOTAL_AMOUNT) TOTAL_AMOUNT, sum(PERSON_AMOUNT) PERSON_AMOUNT, sum(COMPANY_AMOUNT) COMPANY_AMOUNT FROM sys_unpivot_data group by TRANS_DATE ]]> </value> <!-- 將指定的列變成行(這里3列變成了3行) --> <unpivot columns="TOTAL_AMOUNT:總金額,PERSON_AMOUNT:個人金額,COMPANY_AMOUNT:企業(yè)金額" values-as-column="TRANS_AMOUNT" labels-as-column="AMOUNT_TYPE" /> </sql> <!-- 行轉(zhuǎn)列測試 --> <sql id="sys_pvoitSearch"> <value> <![CDATA[ select t.TRANS_DATE,t.TRANS_CHANNEL,TRANS_CODE,sum(t.TRANS_AMT) TRANS_AMT from sys_summary_case t group by t.TRANS_DATE,t.TRANS_CHANNEL,TRANS_CODE order by t.TRANS_DATE,t.TRANS_CHANNEL,TRANS_CODE ]]> </value> <pivot category-columns="TRANS_CHANNEL,TRANS_CODE" start-column="TRANS_AMT" default-value="0" default-type="decimal" end-column="TRANS_AMT" group-columns="TRANS_DATE" /> </sql>
1.6 提供分組匯總求平均算法(用算法代替sql避免跨數(shù)據(jù)庫語法不一致)
<!-- 匯總計算 (場景是sql先匯總,頁面上還需要對已有匯總再匯總的情況,如果用sql實現(xiàn)在跨數(shù)據(jù)庫的時候就存在問題)--> <sql id="sys_summarySearch"> <!-- 數(shù)據(jù)源sharding,多庫將請求壓力分攤到多個數(shù)據(jù)庫節(jié)點上,支撐更多并發(fā)請求 --> <sharding-datasource strategy="multiDataSource" /> <value> <![CDATA[ select t.TRANS_CHANNEL,t.TRANS_CODE,sum( t.TRANS_AMT ) from sys_summary_case t group by t.TRANS_CHANNEL,t.TRANS_CODE ]]> </value> <!-- reverse 表示將匯總信息在上面顯示(如第1行是匯總值,第2、3、4行為明細,反之,1、2、3行未明細,第4行為匯總) --> <summary columns="2" reverse="true" sum-site="left" radix-size="2"> <global sum-label="總計" label-column="0" /> <!-- 可以無限層級的分組下去--> <group sum-label="小計/平均" label-column="0" group-column="0" average-label="平均" /> </summary> </sql>
1.7 分庫分表
1.7.1 查詢分庫分表(分庫和分表策略可以同時使用)
sql參見showcase項目:com/sagframe/sqltoy/showcase/sqltoy-showcase.sql.xml 文件
sharding策略配置參見:src/main/resources/spring/spring-sqltoy-sharding.xml 配置
<!-- 演示分庫 -->
<sql id="sqltoy_db_sharding_case">
<sharding-datasource
strategy="hashBalanceDBSharding" params="userId" />
<value>
<![CDATA[
select * from sqltoy_user_log t
-- userId 作為分庫關(guān)鍵字段屬于必備條件
where t.user_id=:userId
#[and t.log_date>=:beginDate]
#[and t.log_date<=:endDate]
]]>
</value>
</sql>
<!-- 演示分表 -->
<sql id="sqltoy_15d_table_sharding_case">
<sharding-table tables="sqltoy_trans_info_15d"
strategy="historyTableStrategy" params="beginDate" />
<value>
<![CDATA[
select * from sqltoy_trans_info_15d t
where t.trans_date>=:beginDate
#[and t.trans_date<=:endDate]
]]>
</value>
</sql>
1.7.2 操作分庫分表(vo對象由quickvo工具自動根據(jù)數(shù)據(jù)庫生成,且自定義的注解不會被覆蓋)
@Sharding 在對象上通過注解來實現(xiàn)分庫分表的策略配置
參見:com.sagframe.sqltoy.showcase.ShardingCaseServiceTest 進行演示
package com.sagframe.sqltoy.showcase.vo; import java.time.LocalDate; import java.time.LocalDateTime; import org.sagacity.sqltoy.config.annotation.Sharding; import org.sagacity.sqltoy.config.annotation.SqlToyEntity; import org.sagacity.sqltoy.config.annotation.Strategy; import com.sagframe.sqltoy.showcase.vo.base.AbstractUserLogVO; /** * @project sqltoy-showcase * @author zhongxuchen * @version 1.0.0 Table: sqltoy_user_log,Remark:用戶日志表 */ /* * db則是分庫策略配置,table 則是分表策略配置,可以同時配置也可以獨立配置 * 策略name要跟spring中的bean定義name一致,fields表示要以對象的哪幾個字段值作為判斷依據(jù),可以一個或多個字段 * maxConcurrents:可選配置,表示最大并行數(shù) maxWaitSeconds:可選配置,表示最大等待秒數(shù) */ @Sharding(db = @Strategy(name = "hashBalanceDBSharding", fields = { "userId" }), // table = @Strategy(name = "hashBalanceSharding", fields = {"userId" }), maxConcurrents = 10, maxWaitSeconds = 1800) @SqlToyEntity public class UserLogVO extends AbstractUserLogVO { /** * */ private static final long serialVersionUID = 1296922598783858512L; /** default constructor */ public UserLogVO() { super(); } }
評論
圖片
表情
