檔案地址 https://xuejm.gitee.io/easy-query-doc/
GITHUB地址 https://github.com/xuejmnet/easy-query
GITEE地址 https://gitee.com/xuejm/easy-query
眾所鄒知orm的出現讓本來以sql實現的複雜繁瑣功能大大簡化,對於大部分程式設計師而言一個框架的出現是為了生產力的提升.。dbc定義了互動資料庫的規範,任何資料庫的操作都是隻需要滿足jdbc規範即可,而orm就是為了將jdbc的操作進行簡化。我個人「有幸」體驗過.net和java的兩個大orm,只能說差距很大,當然語言上的一些特性也讓java在實現orm上有著比較慢的進度,譬如泛型的出現,lambda的出現。
一個好的orm我覺得需要滿足以下幾點
其實說了這麼多總結一下就是一個好的orm應該有ide的提示外加泛型約束幫助開發可以非常順滑的把程式碼寫下去,並且錯誤部分可以完全的在編譯期間提現出來,執行時錯誤應該儘可能少的去避免。
首先如果你用過其他語言的orm那麼再用java的mybatis就像你用慣了java的stream然後去自行處理資料過濾,就像你習慣了kotlin的語法再回到java語法,很難受。這種難受不是自動擋到手動擋的差距,而且自動擋到手推車的差距。
xml
設定sql也不知道是哪個「小天才」想出來的,先不說寫程式碼的時候java程式碼和xml程式碼跳來跳去,而且xml下>
,<
必須要配合CDATA
不然xml解析就失敗,別說跳脫,我寫那玩意在加跳脫你確定讓我後續看得眼睛不要累死嗎?美名其曰xml和程式碼分離方便維護,但是你再怎麼方便修改了程式碼一樣需要重啟,並且因為程式碼寫在xml裡面導致動態條件得能力相對很弱。並且我也不知道mybatis為什麼天生不支援分頁,需要分頁外掛來支援,難道一個3202年的orm了還需要這樣嗎,很難搞懂mybatis的作者難道不寫crud程式碼的嗎?有些時候簡潔並不是偷懶的原因,當然也有可能是架構的問題導致的。
邏輯刪除的功能我覺得稍微正常一點的企業一定都會有這個功能,但是因為使用了myabtis,因為手寫sql,所以常常會忘記往sql中新增邏輯刪除欄位,從而導致一些奇奇怪怪的bug需要排查,因為這些都是編譯器無法體現的錯誤,因為他是字串,因為mybatis把這個問題的原因指向了使用者,這一點他很聰明,這個是使用者的錯誤而不是框架的,但是框架要做的就是儘可能的將一些重複工作進行封裝隱藏起來自動完成。
可能又會有一些使用者會說所見即所得這樣我才能知道他怎麼執行了,但是現在哪個orm沒有sql列印功能,哪個orm框架執行的sql和列印的sql是不一樣的,不是所見即所得。總體而言我覺得mybatis
充其量算是sqltemlate,比sqlhelper好的地方就是他是引數化防止sql注入。當然最主要的呀一點事難道java程式設計師不需要修改表,不需要動表結構,不需要後期維護的嗎還是說java程式設計師寫一個專案就換一個地方跳槽,還是說java程式設計師每個方法都有單元測試。我在轉java後理解了一點,原來這就是你們經常說的java加班嚴重,用這種框架加班不嚴重就有鬼了。
有幸在201幾年再網上看到了mybatis-plus
框架,這塊框架一出現就吸引了我,因為他在處理sql的方式上和.net的orm很相似,起碼都是強型別,起碼不需要java檔案和xml檔案跳來跳去,平常50%的程式碼也是可以通過框架的lambda表示式來實現,我個人比較排斥他的字串模式的querywrapper
,因為一門強型別語言缺少了強型別提示,在編寫程式碼的時候會非常的奇怪。包括後期的重構,當然如果你的程式碼後續不需要你維護那麼我覺得你用哪種方式都是ok的反正是一次性的,能出來結果就好了。
繼續說mybatis-plus
,因為工作的需要再2020年左右針對內部框架進行改造,並且讓mybatis-plus支援強型別group by,sum,min,max,any等api。
這個時候其實大部分情況下已經可以應對了,就這樣用了1年左右這個框架,包括後續的update的increment
,decrement
update table set column=column-1 where id=xxx and column>1
全部使用lambda強型別語法,可以應對多數情況,但是針對join始終沒有一個很好地方法。直到我遇到了mpj
也就是mybatis-plus-join
,但是這個框架也有問題,就是這個邏輯刪除在join的子表上不生效,需要手動處理,如果生效那麼在where上面,不知道現在怎麼樣了,當時我也是自行實現了讓其出現在join的on後面,但是因為實現是需要實現某個介面的,所以並沒有pr程式碼.
首先定義一個介面
public interface ISoftDelete {
Boolean getDeleted();
}
//其中join mapper是我自己的實現,主要還是`WrapperFunction`的那段定義
@Override
public Scf4jBaseJoinLinq<T1,TR> on(WrapperFunction<MPJAbstractLambdaWrapper<T1, ?>> onFunction) {
WrapperFunction<MPJAbstractLambdaWrapper<T1, ?>> join= on->{
MPJAbstractLambdaWrapper<T1, ?> apply = onFunction.apply(on);
if(ISoftDelete.class.isAssignableFrom(joinClass)){
SFunction deleted = LambdaHelper.getFunctionField(joinClass, "deleted", Boolean.class);
apply.eq(deleted,false);
}
return apply;
};
joinMapper.setJoinOnFunction(query->{
query.innerJoin(joinClass,join);
});
return joinMapper;
}
雖然實現了join
但是還是有很多問題出現和bug。
MetaObjectHandler
,支援entity
的insert
和update
但是不支援lambdaUpdateWrapper
,有時候當前更新人和更新時間都是需要的,你也可以說資料庫可以設定最後更新時間,但是最後修改人呢?beetsql
的原始碼很快的清楚了java這邊應該需要做的事情,為我編寫後續框架節約了太多時間,這邊也給beetsql
打個廣告 https://gitee.com/xiandafu/beetlsql
easy-query
一款無任何依賴的java全新高效能orm支援 單表 多表 子查詢 邏輯刪除 多租戶 差異更新 聯級一對一 一對多 多對一 多對多 分庫分表(支援跨表查詢分頁等) 動態表名 資料庫列高效加解密支援like crud攔截器 原子更新 vo物件直接返回
檔案地址 https://xuejm.gitee.io/easy-query-doc/
GITHUB地址 https://github.com/xuejmnet/easy-query
GITEE地址 https://gitee.com/xuejm/easy-query
//根據條件查詢表中的第一條記錄
List<Topic> topics = easyQuery
.queryable(Topic.class)
.limit(1)
.toList();
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM t_topic t LIMIT 1
<== Total: 1
//根據條件查詢id為3的集合
List<Topic> topics = easyQuery
.queryable(Topic.class)
.where(o->o.eq(Topic::getId,"3").eq(Topic::geName,"4")
.toList();
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM t_topic t WHERE t.`id` = ? AND t.`name` = ?
==> Parameters: 3(String),4(String)
<== Total: 1
Topic topic = easyQuery
.queryable(Topic.class)
//join 後面是雙引數委託,引數順序表示join表順序,可以通過then函數切換
.leftJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))
.where(o -> o.eq(Topic::getId, "3"))
.firstOrNull();
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM t_topic t LEFT JOIN t_blog t1 ON t.`id` = t1.`id` WHERE t.`id` = ? LIMIT 1
==> Parameters: 3(String)
<== Total: 1
List<BlogEntity> blogEntities = easyQuery
.queryable(Topic.class)
//join 後面是雙引數委託,引數順序表示join表順序,可以通過then函數切換
.innerJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))
.where((t, t1) -> t1.isNotNull(BlogEntity::getTitle).then(t).eq(Topic::getId, "3"))
//join查詢select必須要帶對應的返回結果,可以是自定義dto也可以是實體物件,如果不帶物件則返回t表主表資料
.select(BlogEntity.class, (t, t1) -> t1.columnAll())
.toList();
==> Preparing: SELECT t1.`id`,t1.`create_time`,t1.`update_time`,t1.`create_by`,t1.`update_by`,t1.`deleted`,t1.`title`,t1.`content`,t1.`url`,t1.`star`,t1.`publish_time`,t1.`score`,t1.`status`,t1.`order`,t1.`is_top`,t1.`top` FROM t_topic t INNER JOIN t_blog t1 ON t.`id` = t1.`id` WHERE t1.`title` IS NOT NULL AND t.`id` = ?
==> Parameters: 3(String)
<== Total: 1
```java
//SELECT * FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ?
Queryable<BlogEntity> subQueryable = easyQuery.queryable(BlogEntity.class)
.where(o -> o.eq(BlogEntity::getId, "1"));
List<Topic> x = easyQuery
.queryable(Topic.class).where(o -> o.exists(subQueryable.where(q -> q.eq(o, BlogEntity::getId, Topic::getId)))).toList();
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t WHERE EXISTS (SELECT 1 FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ? AND t1.`id` = t.`id`)
==> Parameters: false(Boolean),1(String)
<== Time Elapsed: 3(ms)
<== Total: 1
//SELECT t1.`id` FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ?
Queryable<String> idQueryable = easyQuery.queryable(BlogEntity.class)
.where(o -> o.eq(BlogEntity::getId, "123"))
.select(String.class, o -> o.column(BlogEntity::getId));//如果子查詢in string那麼就需要select string,如果integer那麼select要integer 兩邊需要一致
List<Topic> list = easyQuery
.queryable(Topic.class).where(o -> o.in(Topic::getId, idQueryable)).toList();
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t WHERE t.`id` IN (SELECT t1.`id` FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ?)
==> Parameters: false(Boolean),123(String)
<== Time Elapsed: 2(ms)
<== Total: 0
//@Component //如果是spring
public class MyLogicDelStrategy extends AbstractLogicDeleteStrategy {
/**
* 允許datetime型別的屬性
*/
private final Set<Class<?>> allowTypes=new HashSet<>(Arrays.asList(LocalDateTime.class));
@Override
protected SQLExpression1<WherePredicate<Object>> getPredicateFilterExpression(LogicDeleteBuilder builder,String propertyName) {
return o->o.isNull(propertyName);
}
@Override
protected SQLExpression1<ColumnSetter<Object>> getDeletedSQLExpression(LogicDeleteBuilder builder, String propertyName) {
// LocalDateTime now = LocalDateTime.now();
// return o->o.set(propertyName,now);
//上面的是錯誤用法,將now值獲取後那麼這個now就是個固定值而不是動態值
return o->o.set(propertyName,LocalDateTime.now())
.set("deletedUser",CurrentUserHelper.getUserId());
}
@Override
public String getStrategy() {
return "MyLogicDelStrategy";
}
@Override
public Set<Class<?>> allowedPropertyTypes() {
return allowTypes;
}
}
//為了測試防止資料被刪掉,這邊採用不存在的id
logicDelTopic.setId("11xx");
//測試當前人員
CurrentUserHelper.setUserId("easy-query");
long l = easyQuery.deletable(logicDelTopic).executeRows();
==> Preparing: UPDATE t_logic_del_topic_custom SET `deleted_at` = ?,`deleted_user` = ? WHERE `deleted_at` IS NULL AND `id` = ?
==> Parameters: 2023-04-01T23:15:13.944(LocalDateTime),easy-query(String),11xx(String)
<== Total: 0
- 要注意是否開啟了追蹤
spring-boot
下用@EasyQueryTrack
註解即可開啟- 是否將當前物件新增到了追蹤上下文 查詢新增
asTracking
或者 手動將查詢出來的物件進行easyQuery.addTracking(Object entity)
TrackManager trackManager = easyQuery.getRuntimeContext().getTrackManager();
try{
trackManager.begin();
Topic topic = easyQuery.queryable(Topic.class)
.where(o -> o.eq(Topic::getId, "7")).asTracking().firstNotNull("未找到對應的資料");
String newTitle = "test123" + new Random().nextInt(100);
topic.setTitle(newTitle);
long l = easyQuery.updatable(topic).executeRows();
}finally {
trackManager.release();
}
==> Preparing: UPDATE t_topic SET `title` = ? WHERE `id` = ?
==> Parameters: test1239(String),7(String)
<== Total: 1
學生和學生地址
//資料庫對像查詢
List<SchoolStudent> list1 = easyQuery.queryable(SchoolStudent.class)
.include(o -> o.one(SchoolStudent::getSchoolStudentAddress).asTracking().disableLogicDelete())
.toList();
//vo自定義列對映返回
List<SchoolStudentVO> list1 = easyQuery.queryable(SchoolStudent.class)
.include(o -> o.one(SchoolStudent::getSchoolStudentAddress).asTracking().disableLogicDelete())
.select(SchoolStudentVO.class,o->o.columnAll()
.columnInclude(SchoolStudent::getSchoolStudentAddress,SchoolStudentVO::getSchoolStudentAddress))
.toList();
學生和班級
//資料庫對像查詢
List<SchoolStudent> list1 = easyQuery.queryable(SchoolStudent.class)
.include(o -> o.one(SchoolStudent::getSchoolClass))
.toList();
//自定義列
List<SchoolStudentVO> list1 = easyQuery.queryable(SchoolStudent.class)
.include(o -> o.one(SchoolStudent::getSchoolClass))
.select(SchoolStudentVO.class,o->o
.columnAll()
.columnInclude(SchoolStudent::getSchoolClass,SchoolStudentVO::getSchoolClass,s->s.column(SchoolClassVO::getId))
)
.toList();
//vo自定義列對映返回
List<SchoolStudentVO> list1 = easyQuery.queryable(SchoolStudent.class)
.include(o -> o.one(SchoolStudent::getSchoolClass))
.select(SchoolStudentVO.class,o->o
.columnAll()
.columnInclude(SchoolStudent::getSchoolClass,SchoolStudentVO::getSchoolClass)
)
.toList();
班級和學生
//資料庫對像查詢
List<SchoolClass> list1 = easyQuery.queryable(SchoolClass.class)
.include(o -> o.many(SchoolClass::getSchoolStudents))
.toList();
//vo自定義列對映返回
List<SchoolClassVO> list1 = easyQuery.queryable(SchoolClass.class)
.include(o -> o.many(SchoolClass::getSchoolStudents))
.select(SchoolClassVO.class,o->o.columnAll()
.columnIncludeMany(SchoolClass::getSchoolStudents,SchoolClassVO::getSchoolStudents))
.toList();
班級和老師
List<SchoolClass> list2 = easyQuery.queryable(SchoolClass.class)
.include(o -> o.many(SchoolClass::getSchoolTeachers,1))
.toList();
List<SchoolClassVO> list2 = easyQuery.queryable(SchoolClass.class)
.include(o -> o.many(SchoolClass::getSchoolTeachers))
.select(SchoolClassVO.class,o->o.columnAll()
.columnIncludeMany(SchoolClass::getSchoolTeachers,SchoolClassVO::getSchoolTeachers))
.toList();
List<BlogEntity> blogEntities = easyQuery.queryable(BlogEntity.class)
.asTable(a -> "aa_bb_cc")
.where(o -> o.eq(BlogEntity::getId, "123")).toList();
==> Preparing: SELECT t.`id`,t.`create_time`,t.`update_time`,t.`create_by`,t.`update_by`,t.`deleted`,t.`title`,t.`content`,t.`url`,t.`star`,t.`publish_time`,t.`score`,t.`status`,t.`order`,t.`is_top`,t.`top` FROM aa_bb_cc t WHERE t.`deleted` = ? AND t.`id` = ?
==> Parameters: false(Boolean),123(String)
<== Total: 0
List<BlogEntity> blogEntities = easyQuery.queryable(BlogEntity.class)
.asTable(a->{
if("t_blog".equals(a)){
return "aa_bb_cc1";
}
return "xxx";
})
.where(o -> o.eq(BlogEntity::getId, "123")).toList();
==> Preparing: SELECT t.`id`,t.`create_time`,t.`update_time`,t.`create_by`,t.`update_by`,t.`deleted`,t.`title`,t.`content`,t.`url`,t.`star`,t.`publish_time`,t.`score`,t.`status`,t.`order`,t.`is_top`,t.`top` FROM aa_bb_cc1 t WHERE t.`deleted` = ? AND t.`id` = ?
==> Parameters: false(Boolean),123(String)
<== Total: 0
List<BlogEntity> x_t_blog = easyQuery
.queryable(Topic.class)
.asTable(o -> "t_topic_123")
.innerJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))
.asTable("x_t_blog")
.where((t, t1) -> t1.isNotNull(BlogEntity::getTitle).then(t).eq(Topic::getId, "3"))
.select(BlogEntity.class, (t, t1) -> t1.columnAll()).toList();
==> Preparing: SELECT t1.`id`,t1.`create_time`,t1.`update_time`,t1.`create_by`,t1.`update_by`,t1.`deleted`,t1.`title`,t1.`content`,t1.`url`,t1.`star`,t1.`publish_time`,t1.`score`,t1.`status`,t1.`order`,t1.`is_top`,t1.`top` FROM t_topic_123 t INNER JOIN x_t_blog t1 ON t1.`deleted` = ? AND t.`id` = t1.`id` WHERE t1.`title` IS NOT NULL AND t.`id` = ?
==> Parameters: false(Boolean),3(String)
<== Total: 0
感謝各位看到最後,希望以後我的開源框架可以幫助到您,如果您覺得有用可以點點star,這將對我是極大的鼓勵
更多檔案資訊可以參考git地址或者檔案
檔案地址 https://xuejm.gitee.io/easy-query-doc/
GITHUB地址 https://github.com/xuejmnet/easy-query
GITEE地址 https://gitee.com/xuejm/easy-query