Weed3 for java 新的微型ORM框架
Weed3,微型ORM框架(支援:java sql,xml sql,annotation sql;儲存過程;事務;快取;監聽;等...)
05年時開發了第一代;
08年時開發了第二代,那時候進入網際網路公司,對效能有了全新的認識;
14年時開發了第三代。因為不喜歡濫用反射,不喜歡有很多配置,所以一直在執著的沒放棄。
前兩代,都是在.net開發的;第三代,重點放在了java上。應該算是個功能全面且最小的ORM框架,無其它依賴,僅0.1mb。對外的介面也不多,主要由DbContext上的四個介面發起所有的操作。
因為一些執念寫的東西都算是比較微型的:
- Snack3(Json框架 70kb,有序列化,有Jsonpath,有格式轉換機制;強調構建能力)
- Solon(Web框架 80kb)
- 一個手機瀏覽器(0.1mb,可是有完整功能哦;算是一個創意作品)
Weed3 特點和理念:
高效能:兩年前有個同事測過四個ORM框架,它是效能最好的(不知道現在是不是)。
跨平臺:可以嵌入到JVM指令碼引擎(js, groovy, lua, python, ruby);有.net,php版本(久沒維護了)。
很小巧:只有0.1Mb嘛(且是功能完整,方案豐富;可極大簡化資料庫開發)。
有個性:不喜歡反射、不喜歡配置...(除了連線,不需要任何配置)。
其它的:支援快取控制和跨資料庫事務(算是分散式事務的一種吧)。
Weed3 元件:
元件 | 說明 |
---|---|
org.noear:weed3-parent | 框架版本管理 |
org.noear:weed3 | 主框架(沒有任何依賴) |
org.noear:weed3-maven-plugin | Maven外掛,用於生成Xml sql mapper |
org.noear:weed3.cache.memcached | 基於 Memcached 封裝的擴充套件快取服務 |
org.noear:weed3.cache.redis | 基於 Redis 封裝的擴充套件快取服務 |
org.noear:weed3.cache.ehcache | 基於 ehcache 封裝的擴充套件快取服務 |
org.noear:weed3.cache.j2cache | 基於 j2cache 封裝的擴充套件快取服務 |
Weed3 meven配置:
<!-- 框架包 -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>weed3</artifactId>
<version>3.2.4.1</version>
</dependency>
<!-- maven 外掛,用於生成Xml sql mapper介面 -->
<plugin>
<groupId>org.noear</groupId>
<artifactId>weed3-maven-plugin</artifactId>
<version>3.2.4.1</version>
</plugin>
Weed3 入手流程:
- 配置DataSource資訊
- 實始化DbContext
- 呼叫DbContext上的介面(需要大至瞭解一下語法...)
一、 上下文物件 DbContext
所有weed3的操作,都是基於DbContext上的介面的操作。即,一切從例項化DbContext開始:
- 1.使用
application.yml
配置資料來源(或別的格式配置,或配置服務),格式示例:
demodb:
schema: demo
url: jdbc:mysql://localdb:3306/demo?...
driverClassName: com.mysql.cj.jdbc.Driver
username: demo
password: UL0hHlg0Ybq60xyb
2.有配置之後開始實列化DbContext:
如果是 Spring 框架,可以通過註解獲取配置
如果是 solon 框架,可以通過註解 或 介面獲取配置
//使用Properties配置的示例
Properties properties = XApp.cfg().getProp("demodb"); //這是solon框架的介面
DbContext db = new DbContext(properties);
//使用Map配置的示例
DbContext db = new DbContext(map);
//使用proxool執行緒池配置的示例(好像現在不流行了)//proxool通過xml配置
DbContext db = new DbContext("user","proxool.xxx_db");
//使用DataSource配置的示例(一般使用連線池框架時用;推薦 Hikari 連線池)
//下行demo裡用的正是 Hikari 連線池
DataSource dataSource = new HikariDataSource(...);
DbContext db = new DbContext("user", dataSource);
//還有就是用url,username,password(這個就不需要配置了)
DbContext db = new DbContext("user","jdbc:mysql://x.x.x:3306/user","root","1234");
/* 我平時都用配置服務,所以直接由配置提供資料庫上下文物件。 */
//使用配置服務直接拿到DbContext
DbContext db = WaterClient.Config.get("demodb").getDb();
二、四大介面 db.mapper(), db.table(), db.call(), db.sql()
四大介面,也是DbContext在不同場景上的四種應用方案
核心介面:db.mapper(), db.table()。代表兩種完全不同的風格和口味。
補充介面:db.call(), db.sql()。應對特殊的應用場景。
其中db.table(), db.call(), db.sql() 可以友好的嵌入到JVM指令碼引擎
(js, groovy, lua, python, ruby)和部分GraalVM
語言使用。
因為作者還有個嵌入式FaaS引擎。統一的執行發起物件、無注入無配置、且弱型別的介面作用重大;可以便利的嵌入各種語言中,並提供統一的ORM體驗。
(一)db.mapper(),提供mapper操作支援
mapper風格,是現在極為流行的一種。大多人都在用。
此介面提供了BaseMapper模式,@Sql注入模式,Xml sql配置模式。其中,Xml sql 的內部處理會在啟動時預編譯為Java class;效能應該是靠譜的(好像有點兒jsp的預編譯味道)。
1.db.mapperBase(clz) 獲取BaseMapper例項
自Xxx-plus之後,要是個沒有BaseMapper,好像都不好意思說自己是個ORM框架了。
這個介面確實帶來了極大的方法,簡單的CRUD完全省掉了。
//直接使用BaseMapper
BaseMapper<User> userDao= db.mapperBase(User.class);
//增
userDao.insert(user,false); //false:表示排除null值
//刪
userDao.deleteById(12);
//改:通過ID改
userDao.updateById(user,false); //false:表示排除null值
//改:通過條件改
userDao.update(user,false,m->m.whereEq(User::getType,12).andEq(User::getSex,1));
//查.通過ID查
User user = userDao.selectById(12);
//查.通過條件查(條件,可以是字串風格;可以是lambda風格)
User user = userDao.selectItem(m -> m.whereEq(User::getId,12));
- 2.db.mapper(clz),獲取Mapper例項
@Namespace("demo.dso.db")
public interface UserDao { //此介面,可以擴充套件自 BaseMapper<T>
@sql("select * from `user` where id=@{id}") //變數風格
User getUserById(int id);
@sql("select * from `user` where id=?") //佔位符風格
User getUserById2(int id);
long addUser(User m); //沒有註解,需編寫xml sql配置
}
UserDao userDao = db.mapper(UserDao.class);
User user = userDao.getUserById(12);
userDao.addUser(user);
3.db.mapper(xsqlid, args),獲取Xml sql mapper結果
此介面的好處是,可以把DAO做成一箇中臺:把xml sql 放在資料庫裡,統一管理;並通過開發一個DAO閘道器,以RPC或REST API方式提供服務。
```java
Map<String,Object> args = new HashMap<>();
args.put("id",22);
//xsqlid = @{sqlid} = @{namespace}.{id}
User user = db.mapper("@demo.dso.db.getUserById",args);
```
(二)db.table(),提供純java鏈式操作
這是Weed3最初的樣子,這也是我最喜歡的方法。也是具體跨平臺嵌入的關鍵能力。
BaseMapper內部也是由db.table()實現的,簡單幾行代就OK了。
靈活,有彈性,直接,可以實現任何SQL程式碼效果。開發管理後臺,很爽(因為查詢條件又雜又亂)。
db.table() 支援 兩種風格:
1.字串風格:彈性大、自由方便、可嵌入,語法便於跨平臺;但改欄位名字時麻煩;
2.lambda風格:約束性強、便於管理(改欄位名時極方便);但寫起來麻煩;語法不好跨平臺性。
- 增,INSEERT
User user = new User();
..
//單條插入
db.table("user").set("name","noear").insert();
db.table("user").setEntity(user).insert();
db.table("user").setEntityIf(user, (k,v)->v!=null).insert(); //過濾null
//批量插入
db.table("user").insertList(list);
- 刪,DELETE
//刪掉id<12的記錄
db.table("user").whereLt("id",12).delete();
//刪掉id=12的記錄 (lamdba風格)
db.table(User.class).whereEq(User::getId,12).delete();
- 改,UPDATE
//改掉id=23的sex欄位
db.table("user").set("sex",1).whereEq("id",23).update();
//根據手機號,新增或更新
public void saveUser(UserModel m){
db.talbe("user").setEntityIf(m, (k,v)->v!=null).upsert("mobile");
}
- 查,SELECT
//統計id<100, 名字長度>10的記錄數(可以自由的使用SQL函式)
db.table("user").where("id<?", 100).and("LENGTH(name)>?",10).count();
//查詢20條,id>10的記錄
db.table("user").whereGte("id", 10).limit(20).select("*").getMapList();
//關聯查詢並輸出一個實體(lamdba風格) //還是字串風格更有彈性和簡潔
db.table(User.class)
.innerJoin(UserEx.class).onEq(User::getId,UserEx::getUserId)
.where(User::getId, 10).andEq(UserEx::getSex,1)
.limit(1)
.select(User.class,$(UserEx::getSex).alias("user_sex"))
.getItem(User.class);
- 具有過濾能力的介面:whereIf, andIf, orIf, setIf, setMapIf, setEntityIf
//如果有名字,加名字條件;(管理後臺的查詢,很實用的; 省了很多if)
db.talbe("user").whereIf(name!=null, "name=?", name).limit(10).select("");
//插入,過濾null
db.table("user").setMapIf(map,(k,v)->v!=null).insert(); //過濾null
//更新
db.table("user")
.setIf(name!=null, "name",name)
.setIf(sex>0, "sex", sex)
.setIf(mobile!=null && mobile.length() =11,"mobile",mobile)
.where("id=?",id)
.update();
(三)db.call(),提供call操作
- call資料庫儲存過程
//資料庫儲存過程使用
//
User user = db.call("user_get").set("id",1).getItem(User.class);
- call查詢過程
//查詢儲過程使用 (@sql內部由此實現)
//
User user = db.call("select * from user where id=@{id}").set("id",1).getItem(User.class);
- call Xmlsql
//Xml sql的弱型別使用方式 //需@開始
//
User user = db.call("@demo.dso.db.getUser").set("id",1).getItem(User.class);
(四)db.sql(),提供手寫sql操作
//所以介面最終都會轉為db.sql(),算是最底層的一個介面
//
User user = db.sql("select * from user where id=?",12).getItem(User.class);
Long total = db.sql("select count(*) from user").getValue(0l);
//db.sql() 的快捷版: db.exe(),用於快速批處理
//
db.exe("delete from user where id=12");
db.exe("update user sex=1 where id=12");
三、Mapper 語法
(一)BaseMapper 介面
Long insert(T entity, boolean excludeNull);
void insertList(List<T> list);
Integer deleteById(Object id);
Integer deleteByIds(Iterable<Object> idList);
Integer deleteByMap(Map<String, Object> columnMap);
Integer delete(Act1<WhereQ> condition);
Integer updateById(T entity, boolean excludeNull);
Integer update(T entity, boolean excludeNull, Act1<WhereQ> condition);
Long upsert(T entity, boolean excludeNull);
Long upsertBy(T entity, boolean excludeNull, String conditionFields);
boolean existsById(Object id);
boolean exists(Act1<WhereQ> condition);
T selectById(Object id);
List<T> selectByIds(Iterable<Object> idList);
List<T> selectByMap(Map<String, Object> columnMap);
T selectItem(T entity);
T selectItem(Act1<WhereQ> condition);
Map<String, Object> selectMap(Act1<WhereQ> condition);
Object selectValue(String column, Act1<WhereQ> condition);
Long selectCount(Act1<WhereQ> condition);
List<T> selectList(Act1<WhereQ> condition);
List<Map<String, Object>> selectMapList(Act1<WhereQ> condition);
List<Object> selectArray(String column, Act1<WhereQ> condition);
List<T> selectPage(int start, int end, Act1<WhereQ> condition);
List<Map<String, Object>> selectMapPage(int start, int end, Act1<WhereQ> condition);
(二)annotation sql
- 示例
ICacheServiceEx cache = new LocalCache().nameSet("cache");
//順帶加了快取
@Sql(value="select * from user where id=@{id}", caching="cache")
public UserModel getUser(int id);
- Sql 註解說明
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sql {
String value() default ""; //程式碼
String caching() default ""; //快取服務名稱
String cacheClear() default ""; //快取清除標籤
String cacheTag() default ""; //快取標籤
int usingCache() default 0; //快取時間
}
(三)Xml sql
- 示例
<?xml version="1.0" encoding="utf-8" ?>
<mapper namespace="weed3demo.xmlsql2">
<sql id="getUser" :return="demo.model.UserModel" :note="獲取使用者資訊">
SELECT * FROM user WHERE id = @{id:int}
</sql>
</mapper>
- 語法
mapper 開始標籤
namespace (屬性:名稱空間,{namespace}.{id} = sqlid)
sql 程式碼塊定義指令
id (屬性:id)
:require(屬性:匯入包或類)
:param?(屬性:外部輸入變數申明;預設會自動生成::新增***)
:declare(屬性:內部變數型別預申明)
:return(屬性:返回型別)
:note(屬性:描述、說明、註解)
:caching(屬性:快取服務name) //是對 ICacheController 介面的對映
:cacheClear?(屬性:清除快取)
:cacheTag?(屬性:快取標籤,支援在入參或結果裡取值替換)
:usingCache?(屬性:快取時間,int)
if 判斷控制指令(沒有else)
test (屬性:判斷檢測程式碼)
//xml避免語法增強:
//lt(<) lte(<=) gt(>) gte(>=) and(&&) or(||)
//例:m.sex gt 12 :: m.sex >=12
//簡化語法增強:
//??(非null,var!=null) ?!(非空字串,StringUtils.isEmpty(var)==false)
//例:m.icon?? ::m.icon!=null
//例:m.icon?! ::StringUtils.isEmpty(m.icon)==false
for 迴圈控制指令 (通過 ${var}_index 可獲得序號,例:m_index::新增***)
var (屬性:迴圈變數申明)
items (屬性:集合變數名稱)
sep? (屬性:分隔符::新增***)
trim 修剪指令
trimStart(屬性:開始位去除)
trimEnd(屬性:結尾位去除)
prefix(屬性:新增字首)
suffix(屬性:新增字尾)
ref 引用程式碼塊指令
sql (屬性:程式碼塊id)
name:type = 變數申明(可用於屬性::param, :declare,var,或巨集定義 @{..},${..})
@{name:type} = 變數注入
${name:type} = 變數替換
//列表([]替代<>)
:return="List[weed3demo.mapper.UserModel]" => List<UserModel>
:return="List[String]" => List<String> (Date,Long,...大寫開頭的單值型別)
:return="MapList" => List<Map<String,Object>>
:return="DataList" => DataList
//一行
:return="weed3demo.mapper.UserModel" => UserModel
:return="Map" => Map<String,Object>
:return="DataItem" => DataItem
//單值
:return="String" => String (任何單職型別)
四、Table 語法
(一)條件操作(與Mapper共享)
方法 | 效果說明 |
---|---|
where, whereIf | |
whereEq, whereNeq | ==, != |
whereLt, whereLte | <, <= |
whereGt, whereGte | >, >= |
whereLk, whereNlk | LIKE, NOT LIKE |
whereIn, whereNin | IN(..), NOT IN(..) |
whereBtw, whereNbtw | BETWEEN, NOT BETWEEN |
and系統方法 | 同where |
or系統方法 | 同where |
begin | ( |
end | ) |
(二)表操作(Table獨佔)
方法 | 效果說明 |
---|---|
set, setIf | 設定值 |
setMap, setMapIf | 設定值 |
setEntity, setEntityIf | 設定值 |
table | 主表 |
innerJoin, leftJoin, rightJoin | 關聯表 |
on, onEq | 關聯條件 |
orderBy, orderByAsc, orderByDesc | 排序 |
groupBy | 組 |
having | 組條件 |
limit | 限制範圍 |
select | 查詢(返回IQuery) |
count | 查詢快捷版,統計數量 |
exists | 查詢快捷版,是否存在 |
update | 更新 |
insert | 插入 |
delete | 刪除 |
(三)IQuery介面
long getCount() throws SQLException;
Object getValue() throws SQLException;
<T> T getValue(T def) throws SQLException;
Variate getVariate() throws SQLException;
<T> T getItem(Class<T> cls) throws SQLException;
<T> List<T> getList(Class<T> cls) throws SQLException;
DataList getDataList() throws SQLException;
DataItem getDataItem() throws SQLException;
List<Map<String,Object>> getMapList() throws SQLException;
Map<String,Object> getMap() throws SQLException;
<T> List<T> getArray(String column) throws SQLException;
<T> List<T> getArray(int columnIndex) throws SQLException;
- 等...
五、 快取和事務
- 快取(不需要的可以跳過)
ICacheServiceEx cache = new LocalCache().nameSet("cache");
User user = db.table("user")
.where("id=?",12)
.caching(cache) //加快取,時間為cache的預設時間
.select("*").getItem(User.class);
- 快取控制(不需要的可以跳過)
//查詢時,快取
User user = db.table("user")
.where("id>?",12)
.limit(100,20) //分頁查詢
.caching(cache)
.usingCache(60*5) //快取5分鐘
.cacheTag("user_all") //加快取標籤user_all
.select("*").getList(User.class);
//更新時,清除快取 //下次查詢時,又可拿到最新資料
db.table("user").set("sex",0).where("id=101").update();
cache.clear("user_all");
- 單庫資料庫事務
db.tran(t->{
//註冊使用者
long user_id = userDao.addUser(user);
//註冊後送10個金幣(在同一個事務裡完成)
userDao.addUserGold(user_id, 10);
});
- 跨庫資料庫事務(不知道算不算是分散式事務的一種)
new DbTranQueue().execute((tq) -> {
//使用者系統,新增使用者關金幣
db1.tran().join(tq).execute(t -> {
user.id = userDao.addUser(user); //id自增
});
//銀行系統
db2.tran().join(tq).execute(t -> {
bankDao.addAccount(user.id); //新建賬號
bankDao.addAccountGold(user.id, 10); //新增賬號叫金幣
bankDao.addJournal(user.id,10); //新增日記賬
});
//擴播訊息//為後續橫向擴充套件業務
MsgUtil.sendMessage("user.registered",user.value);
});
(六) 監聽與記錄
- 監聽異常
WeedConfig.onException((cmd,ex)->{
//可以做個記錄
ex.printStackTrace();
});
- 觀察效能
WeedConfig.onExecuteAft((cmd)->{
//cmd.timespan() //獲取執行時長(毫秒)
});
- 記錄行為
WeedConfig.onLog((cmd) -> {
if (cmd.isLog >= 0) { //isLog: -1,不需要記錄;0,預設;1,需要記錄
//cmd.text; //執行程式碼
//cmd.paramS; //執行引數
//cmd.paramMap(); //執行引數Map化
}
});
- 程式碼過濾
//例:禁止DELETE操作
WeedConfig.onExecuteBef((cmd)->{
if(cmd.text.indexOf("DELETE ") >=0){
return false;
}
return true;
});
(七) 嵌入到JVM指令碼
- 嵌入到javascript引擎(nashorn)
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine _eng = scriptEngineManager.getEngineByName("nashorn");
Invocable _eng_call = (Invocable)_eng;
_eng.put("db", db);
/*
* var map = db.table("user").where('id=?',1).getMap();
* var user_id = map.id;
*/
- 嵌入到groovy引擎
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine _eng = scriptEngineManager.getEngineByName("groovy");
Invocable _eng_call = (Invocable)_eng;
_eng.put("db", db);
/*
* def map = db.table("user").where('id=?',1).getMap();
* def user_id = map.id;
*/
有機會,將對一些細節再做介紹...