Weed3 for java 新的微型ORM框架


Weed3,微型ORM框架(支持:java sql,xml sql,annotation sql;template 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 主框架(沒有任何依賴)
可選組件 說明
org.noear:weed3-maven-plugin Maven插件,用於生成Xml sql mapper
org.noear:weed3-solon-plugin Solon插件,支持@Db注解、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 適配的擴展緩存服務
org.noear:weed3.render.beetl 基於 beetl 適配的擴展模板引擎
org.noear:weed3.render.enjoy 基於 enjoy 適配的擴展模板引擎
org.noear:weed3.render.freemarker 基於 freemarker 適配的擴展模板引擎
org.noear:weed3.render.velocity 基於 velocity 適配的擴展模板引擎

Weed3 meven配置:

<!-- 框架包 -->
<dependency>
    <groupId>org.noear</groupId>
    <artifactId>weed3</artifactId>
    <version>3.2.9</version>
</dependency>

<!-- maven 插件,用於生成Xml sql mapper接口 -->
<plugin>
    <groupId>org.noear</groupId>
    <artifactId>weed3-maven-plugin</artifactId>
    <version>3.2.9</version>
</plugin>

Weed3 入手流程:

  • 配置DataSource信息
  • 實始化DbContext
  • 調用DbContext上的接口(需要大至了解一下語法...)

一、 上下文對象 DbContext

所有weed3的操作,都是基於DbContext上的接口的操作。即,一切從實例化DbContext開始:
  • 1.使用application.yml配置數據源(或別的格式配置,或配置服務),格式示例:
#這是DbContext原生配置;如果是為連接池,請參考對方的配置;
demo.db:
    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("demo.db"); //這是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("demo.db").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);
  
    @sql("#user_stat.sql") 											//SQL模板風格(適用特別復雜的統計查詢)
    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方式提供服務。

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代碼效果。開發管理后台,很爽(因為查詢條件又雜又亂)。

此接口,可以方便的嵌入到JVM腳本引擎(js, groovy, lua, python, ruby),或GraalVM的語言里。

db.table() 接口:
1.字符串風格:彈性大、自由方便、可嵌入,語法便於跨平台;但改字段名會麻煩些(沒事兒也不亂改吧)。
  • 增,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();
  • 改,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();

//關聯查詢並輸出一個實體
db.table("user u")
  .innerJoin("user_ex e").onEq("u.id","e.user_id")
  .whereEq("u.id", 10).andEq("e.sex",1)
  .limit(1)
  .select("u.*,e.sex 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
//@Sql內部由此實現
//
User user = db.call("select * from user where id=@{id}").set("id",1).getItem(User.class);
  • call Xmlsql
//需@開頭 + sqlid
//
User user = db.call("@demo.dso.db.getUser").set("id",1).getItem(User.class);
  • call template sql
//需#開頭 + 模板路徑
Map<String,Object> args = new DataItem().set("date",20201010).getMap();
db.call("#user_stat.sql", args).getMapList();
(四)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;
 */

(八) 語法說明

有機會,將對一些細節再做介紹...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM