Weed3,微型ORM框架(支持:java sql,xml sql,annotation sql;存儲過程;事務;緩存;監聽;等...)javascript
05年時開發了第一代;
08年時開發了第二代,那時候進入互聯網公司,對性能有了全新的認識;
14年時開發了第三代。由於不喜歡濫用反射,不喜歡有不少配置,因此一直在執着的沒放棄。php
前兩代,都是在.net開發的;第三代,重點放在了java上。應該算是個功能全面且最小的ORM框架,無其它依賴,僅0.1mb。對外的接口也很少,主要由DbContext上的四個接口發起全部的操做。java
由於一些執念寫的東西都算是比較微型的:python
高性能:兩年前有個同事測過四個ORM框架,它是性能最好的(不知道如今是否是)。mysql
跨平臺:能夠嵌入到JVM腳本引擎(js, groovy, lua, python, ruby);有.net,php版本(久沒維護了)。redis
很小巧:只有0.1Mb嘛(且是功能完整,方案豐富;可極大簡化數據庫開發)。sql
有個性:不喜歡反射、不喜歡配置...(除了鏈接,不須要任何配置)。數據庫
其它的:支持緩存控制和跨數據庫事務(算是分佈式事務的一種吧)。瀏覽器
組件 | 說明 |
---|---|
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 封裝的擴展緩存服務 |
<!-- 框架包 --> <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>
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();
四大接口,也是DbContext在不一樣場景上的四種應用方案
核心接口:db.mapper(), db.table()。表明兩種徹底不一樣的風格和口味。
補充接口:db.call(), db.sql()。應對特殊的應用場景。
JVM腳本引擎
(js, groovy, lua, python, ruby)和部分GraalVM
語言使用。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));
@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);
```
這是Weed3最初的樣子,這也是我最喜歡的方法。也是具體跨平臺嵌入的關鍵能力。
BaseMapper內部也是由db.table()實現的,簡單幾行代就OK了。
靈活,有彈性,直接,能夠實現任何SQL代碼效果。開發管理後臺,很爽(由於查詢條件又雜又亂)。
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);
//刪掉id<12的記錄 db.table("user").whereLt("id",12).delete(); //刪掉id=12的記錄 (lamdba風格) db.table(User.class).whereEq(User::getId,12).delete();
//改掉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"); }
//統計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);
//若是有名字,加名字條件;(管理後臺的查詢,很實用的; 省了不少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();
//數據庫存儲過程使用 // User user = db.call("user_get").set("id",1).getItem(User.class);
//查詢儲過程使用 (@sql內部由此實現) // User user = db.call("select * from user where id=@{id}").set("id",1).getItem(User.class);
//Xml sql的弱類型使用方式 //需@開始 // User user = db.call("@demo.dso.db.getUser").set("id",1).getItem(User.class);
//因此接口最終都會轉爲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");
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);
ICacheServiceEx cache = new LocalCache().nameSet("cache"); //順帶加了緩存 @Sql(value="select * from user where id=@{id}", caching="cache") public UserModel getUser(int id);
@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 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 (任何單職類型)
方法 | 效果說明 |
---|---|
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 | ) |
方法 | 效果說明 |
---|---|
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 | 刪除 |
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; });
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; */
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; */
有機會,將對一些細節再作介紹...