一個新的微型ORM開源框架

Weed3 一個微型ORM框架(只有0.1Mb哦)

源碼:https://github.com/noear/weed3
源碼:https://gitee.com/noear/weed3

05年的時候開始寫這個框架的1代版本隧熙。盯拱。涌萤。
08年時進入互聯(lián)網(wǎng)公司重構(gòu)寫了2代版本。。嫩实。
14年重構(gòu)寫了現(xiàn)在的3代版本(有java 和 .net 的兩個平臺版本)。。躁劣。
最近被迫加了xml sql mapper的支持。库菲。账忘。
然后順帶加了sql注解。熙宇。鳖擒。
終于包也變大到0.1Mb了。烫止。蒋荚。

上次一個群里的朋友說,這是個清奇的框架馆蠕。這個講法很有意思啊期升。。

總體上來講互躬,這個框架的特點就是不喜歡反射播赁、不喜歡配置(但仍然避免不了)!:鸲伞容为!是希望通過良好的接口設計,來完全成簡潔的操控體驗。或許你覺得隨便手寫點sql都比它好(怎么可能呢坎背,哈哈~~)

對于一些老人來說替劈,這樣描述可能給較好:它相當于 mybatis + mybatis-puls (有個對標物,容易理解些)沼瘫。抬纸。。不過我沒用過它們耿戚,可能講得也不對湿故。

另外,它很小膜蛔,它很快坛猪,它很自由(也有人說,太自由反而難控制)

【1】先Hello world一下

  • 建個任何類型的java項目皂股,引入框架包
<dependency>
    <groupId>org.noear</groupId>
    <artifactId>weed3</artifactId>
    <version>3.2.9</version>
</dependency>

<!-- 這個是順帶的墅茉,數(shù)據(jù)庫連接器總要有一個 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
  • 不用任何配置,三行代碼就可運行
// hello world 走起...(數(shù)據(jù)庫鏈接改個對口的...)
public static void main(String[] args){
    DbContext db  = new DbContext("user","jdbc:mysql://127.0.0.1:3306/user","root","1234");
    String rst = db.sql("SELECT 'hello world!'").getValue();//獲取值
    System.out.println(rst);
}
//
// 如果是 mybatis 呜呐,估計得先忙會兒配置...
//
  • 應該算是簡單的吧(引入兩外包就斤;寫三行代碼)

不能hello world的東西不是好東西。哈哈:-P

weed3 支持純java鏈式寫法 或者 xml mapper寫法蘑辑。安排上會先介紹純java寫法洋机。。洋魂。再慢慢講開來绷旗。

【2.1】開始純java使用

純java使用時,有三大接口可用:db.table(..), db.call(..), db.sql()副砍。一般使用db.table(..)接口進行鏈式操作居多衔肢。它的接口采用與SQL映射的方式命名。豁翎。角骤。使用的人,容易想到能有哪些鏈式接口谨垃。像:.where(..) .and(..) .innerJoin(..)等...

鏈式操作的套路:
db.table(..) 開始启搂。
.update(..).insert(..).delete(..).select(..)
其中.select(..) 會返回IQuery接口刘陶,提供了各種類型結(jié)果的選擇胳赌。

首先,添加meven依賴
<dependency>
  <groupId>org.noear</groupId>
  <artifactId>weed3</artifactId>
  <version>3.2.9</version>
</dependency>

<!-- 數(shù)據(jù)庫連接器匙隔,我就不管了 -->
然后疑苫,實例化數(shù)據(jù)庫上下文對象
  • 所有weed3的操作,都是基于DbContext。所以要先實列化一下捍掺。撼短。。
  1. 需要有配置挺勿,可以在application.properties獲取曲横,可以通過配置服務獲取,可以臨時手寫一下不瓶。禾嫉。

如果是 Spring 框架,可以通過注解獲取配置
如果是 solon 框架蚊丐,可以通過注解 或 Aop.prop().get("xxx")獲取配置

2.有配置之后開始實列化DbContext熙参。這里臨時手寫一下。

//使用proxool線程池配置的示例(好像現(xiàn)在不流行了)
DbContext db  = new DbContext("user","proxool.xxx_db"); 

//使用DataSource配置的示例(一般使用連接池框架時用麦备;推薦 Hikari 連接池)
//下行demo里用的正是 Hikari 連接池
DbContext db  = new DbContext("user",new HikariDataSource(...)); 

//還有就是用url,username,password
DbContext db  = new DbContext("user","jdbc:mysql://x.x.x:3306/user","root","1234");

/* 我平時都用配置服務孽椰,所以直接由配置提供數(shù)據(jù)庫上下文對象。 */
現(xiàn)在凛篙,開始做簡單的數(shù)據(jù)操作
  • 常規(guī)查詢操作
//統(tǒng)計小于10的用戶數(shù)量
long num = db.table("user_info").where("user_id<?", 10).count();

//檢查用戶是不是存在
bool rst = db.table("user_info").where("user_id=?", 10).exists();

//獲取用戶性別
int sex = db.table("user_info").where("user_id=?", 10)
            .select("sex").getValue();

//獲取一個用戶信息
UserModel mod = db.table("user_info").where("user_id=?", 10).and("sex=1")
                  .select("*").getItem(UserModel.class);
  • 再來一把全套的"增刪改查"
//簡易.增
db.table("test").set("log_time", "$DATE(NOW())").insert();

//簡易.刪
db.table("test").where("id=?",1).delete();

//簡易.改
db.table("test").set("log_time", "$DATE(NOW())").where("id=?",1).update();

//簡易.查
var map = db.table("test").where("id=?",1).select("*").getMap();
關于條件的另一套接口
//where 組
whreEq(filed,val)  //filed等于val
whereLt(filed,val) //小于
whereLte(filed,val) //小于等于
whereGt(filed,val) //大于
whereGte(filed,val) //大于等于
whereLk(filed,val) // LIKE
whereIn(filed,ary) // IN
whereNin(filed,ary) // NOT IN

//and 組
andEq(filed,val)  //filed等于val
andLt(filed,val) //小于
andLte(filed,val) //小于等于
andGt(filed,val) //大于
andGte(filed,val) //大于等于
andLk(filed,val) // LIKE
andIn(filed,ary) // IN
andNin(filed,ary) // NOT IN

//or 組
orEq(filed,val)  //filed等于val
orLt(filed,val) //小于
orLte(filed,val) //小于等于
orGt(filed,val) //大于
orGte(filed,val) //大于等于
orLk(filed,val) // LIKE
orIn(filed,ary) // IN
orNin(filed,ary) // NOT IN

//demo::
db.table("test").whereEq("id",1).delete();
db.table("test").whereEq("id",1).orEq("name","xidong").delete();

這是一個簡單的開始黍匾,希望能有個好的印象。

【2.2】細講插入和更新

這一節(jié)重點講講插入和更新的賦值
  • 支持常規(guī)賦值
String mobile="xxx"; //我的手機號不能寫

db.table("test")
  .set("mobile",mobile) //變量賦值
  .set("sex",1) //常量賦值
  .insert();
  • 支持sql附值(這個是可以帶來方便的***)
    如果值以:$開頭呛梆,表示后面為SQL代碼(不能出現(xiàn)空隔膀捷,且100字符以內(nèi)。否則視為普通字符串值)削彬,如下:
//比如:當前時間賦值
db.table("test").set("log_time","$NOW()").insert();

//再比如:字段加1附值
db.table("test").set("num","$num+1")
  .where("id=?",1).update();

//再比如:根據(jù)另一個字段的md5,批量更新
db.table("test").set("txt_md5","$MD5(txt)")
  .where("id>? AND id<?",1000,2000).update();


/* 如何開啟或禁用功能秀仲?(其實融痛,它是挺安全的)*/

//1.只控制本次操作是否使用此功能
db.table("test").usingExpr(false) // true 開啟,false 關閉

//2.全局配置開啟或關掉這個功能:
WeedConfig.isUsingValueExpression=false; //全局默認關掉
  • 支持map附值(字段不能是數(shù)據(jù)表里沒有的..)
Map<String,Object> map = new HashMap<>();
...

//插入
db.table("test").setMap(map).insert();

//更新
db.table("test").setMap(map).where("id=?",1).update();
  • 支持 entity 附值(字段不能是數(shù)據(jù)表里沒有的..)
UserModel user = new UserModel();

//插入
db.table("test").setEntity(user).insert();

//更新
db.table("test").setEntity(user).where("id=?",1).update();
  • 支持(沒有則插入神僵,有則更新)的簡化操作
//簡化方案
db.table("test")
  .set("mobile","111")
  .set("sex",1)
  .set("icon","http://xxxx")
  .updateExt("mobile");

//此代碼相當于:(下面這個可麻煩了很多哦)
if(db.talbe("test").where("mobile=?","111").exists()){
  db.talbe("test")
    .set("mobile","111")
    .set("sex",1)
    .set("icon","http://xxxx")
    .insert()
}else{
  db.talbe("test")
    .set("sex",1)
    .set("icon","http://xxxx")
    .where("mobile=?","111").update();  
}
  • 支持根據(jù)情況附值(講法來怪怪的..)
//1.老套跑
var qr = db.table("test").set("sex",1);
if(icon!=null){
  qr.set("icon",icon);
}
qr.where("mobile=?","111").update();  
//2.鏈式操作套路
db.table("test").set("sex",1).expre((tb)->{ //加個表達式
  if(icon!=null){
    tb.set("icon",icon);
  }
}).where("mobile=?","111").update();  

關于更新和刪除的條件雁刷,參考查詢的章節(jié)。條件都是一樣的嘛

【2.3.1】查詢之輸出

查詢可是個復雜的話題了保礼,可能我們80%的數(shù)據(jù)庫處理都在查詢沛励。
今天先講講weed3的查詢能輸出什么?
  • 1.1.快捷查詢數(shù)量
db.table("user_info").where("user_id<?", 10).count();
  • 1.2.快捷查詢是否存在
db.table("user_info").where("user_id<?", 10).exists();
  • 2.1.查詢一行的一個字段,輸出單值
bool val = db.table("user_info")
             .where("user_id=?", 10)
             .select("sex").getValue(false); //設個默認值為:false
  • 2.2.查詢多行的一個字段炮障,輸出數(shù)組
List<String> ary = db.table("user_info")
             .where("user_id=?", 10)
             .select("mobile").getArray("mobile");
  • 3.1.查詢一行目派,輸出map
Map<String,Object> map = db.table("user_info")
             .where("user_id=?", 10)
             .select("*").getMap(); 
  • 3.2.查詢多行,輸出map list
List<Map<String,Object>> list = db.table("user_info")
             .where("user_id>?", 10).limit(20) //限20條記錄
             .select("*").getMapList(); 
  • 4.1.查詢一行胁赢,輸出entity
UserModel m = db.table("user_info")
             .where("user_id=?", 10)
             .select("*").getItem(UserModel.class); 

//用戶模型(我統(tǒng)叫它模型)
//這里寫了最簡單的格式企蹭,可以改為bean風格
public class UserModel{
    public String name;
    public String mobile;
    public int sex;
}
  • 4.2.查詢多行,輸出entity list
List<UserModel> list = db.table("user_info")
             .where("user_id>?", 10).limit(0,20) //分頁取20行
             .select("*").getList(UserModel.class); 
那還能再輸出什么?
  • 1.select("...") 返回的是一個:IQuery
public interface IQuery extends ICacheController<IQuery> {
     long getCount() throws SQLException;
     Object getValue() throws SQLException;
     <T> T getValue(T def) throws SQLException;

     Variate getVariate() throws SQLException;
     Variate getVariate(Act2<CacheUsing,Variate> cacheCondition) throws SQLException;

     <T extends IBinder> T getItem(T model) throws SQLException;
     <T extends IBinder> T getItem(T model, Act2<CacheUsing, T> cacheCondition) throws SQLException;


     <T extends IBinder> List<T> getList(T model) throws SQLException;
     <T extends IBinder> List<T> getList(T model, Act2<CacheUsing, List<T>> cacheCondition) throws SQLException;

     <T> T getItem(Class<T> cls) throws SQLException;
     <T> T getItem(Class<T> cls,Act2<CacheUsing, T> cacheCondition) throws SQLException;

     <T> List<T> getList(Class<T> cls) throws SQLException;
     <T> List<T> getList(Class<T> cls,Act2<CacheUsing, List<T>> cacheCondition) throws SQLException;

     DataList getDataList() throws SQLException;
     DataList getDataList(Act2<CacheUsing, DataList> cacheCondition) throws SQLException;
     DataItem getDataItem() throws SQLException;
     DataItem getDataItem(Act2<CacheUsing, DataItem> cacheCondition) 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;
}

  • 2.其中 getDataList() 返加的是 DataList谅摄,它有一些類型轉(zhuǎn)換接口:
/** 將所有列轉(zhuǎn)為類做為數(shù)組的數(shù)據(jù)(類為:IBinder 子類) */
List<T> toList(T model);
/** 將所有列轉(zhuǎn)為類做為數(shù)組的數(shù)據(jù) */
List<T> toEntityList(Class<T> cls);
/** 選1列做為MAP的key徒河,并把行數(shù)據(jù)做為val */
Map<String,Object> toMap(String keyColumn);
/** 選兩列做為MAP的數(shù)據(jù) */
Map<String,Object> toMap(String keyColumn,String valColumn);
/** 選一列做為SET的數(shù)據(jù) */
Set<T> toSet(String column)
/** 選一列做為數(shù)組的數(shù)據(jù) */
List<T> toArray(String columnName)
/** 選一列做為數(shù)組的數(shù)據(jù) */
List<T> toArray(int columnIndex)
/** 轉(zhuǎn)為json字符串 */
String toJson();
    1. 其中 getVariate() 返回的是 Variate,也提供了些轉(zhuǎn)換接口
T value(T def);
double doubleValue(double def);
long longValue(long def);
int intValue(int def);
String stringValue(String def);

【2.3.2】查詢之條件

查詢查然是個麻煩的話題送漠。顽照。。
還好這節(jié)的條件會比較簡單
  • 單表條件查詢(有了簡單的自然能拼成復雜的)
//weed3 的條件構(gòu)建闽寡,是相當自由的
String mobile = "111"; 
db.table("test")
  .where("mobile=?",mobile).and().begin("sex=?",1).or("sex=2").end()
  .limit(20)
  .select("*").getMapList()

db.table("test")
  .where("mobile=?",mobile).and("(sex=? OR sex=2)",1)
  .limit(20)
  .select("*").getMapList()

db.table("test").where("mible=? AND (sex=1 OR sex=2)",mobile)
  .limit(20)
  .select("*")

//以上三種代兵,效果是一樣的。下隧。奢人。因為很自由,所以很容易使用(也有觀點認為:所以很難控制)
  • 有時候一些條件需要動態(tài)控制
//這個示例淆院,管理后臺很常見
int type=ctx.paramAsInt("type",0);
String key=ctx.param("key");
int date_start=ctx.paramAsInt("date_start",0);
int date_end=ctx.paramAsInt("date_end",0);

DbTableQuery qr = db.table("test").where("1=1");
if(type > 0){
  qr.and("type=?", type);
}

if(key != null){
  qr.and('"title LIKE ?",key+"%");
}

if(date_start>0 && date_end >0){
  qr.and("( date >=? AND date<=? )", date_start, date_end);
}

qr.select("id,title,icon,slug").getMapList();
  • 多表關聯(lián)查詢:innerJoin(..), leftJoin(..), rightJoin(..)
//innerJoin()
db.table("user u")
  .innerJoin("user_book b").on("u.id = b.user_id")
  .select("u.name,b.*")

//leftJoin()
db.table("user u")
  .leftJoin("user_book b").on("u.id = b.user_id").and("u.type=1")
  .select("u.name,b.*")

//rightJoin()
db.table("user u")
  .rightJoin("user_book b").on("u.id = b.user_id")
  .where("b.type=1").and("b.price>",12)
  .select("u.name,b.*")
  • 想別的關聯(lián)查詢怎么樣何乎?(如:full join)
//因為不是所有的數(shù)據(jù)庫都支持 full join,所以...
db.table("user u")
  .append("FULL JOIN user_book b").on("u.id = b.user_id")
  .select("u.name,b.*")

//.append(..) 可以添加任何內(nèi)容的接口

【2.3.3】查詢之緩存控制

緩存控制土辩,是查詢中的重點

框架提供的是控制服務支救。而非緩存服務本身,了解這個很重要拷淘。

緩存控制需要兩個重要的接口定義:
  • 1.緩存服務適配接口 ICacheService(平常用它的加強版 ICacheServiceEx )
//用它可以包裝各種緩存服務
public interface ICacheService {
    void store(String key, Object obj, int seconds);
    Object get(String key);
    void remove(String key);
    int getDefalutSeconds();
    String getCacheKeyHead();
}

/** weed3內(nèi)置了三個實現(xiàn)類:
 *EmptyCache各墨,空緩存
 *LocalCache,本地緩存
 *SecondCache启涯,二級緩存容器(可以把兩個 ICacheService 拼到一起贬堵,變成一個二級緩存服務;多嵌套一下就是三級緩存服務了)
 */
  • 2.在緩存服務上進行的操控接口:ICacheController (所有的weed操控對象都實現(xiàn)了ICacheController)
public interface ICacheController<T> {
    //使用哪個緩存服務
    T caching(ICacheService service);
    //是否使用緩存
    T usingCache(boolean isCache);
    //使用緩存并設置時間
    T usingCache(int seconds);
    //為緩存添加標簽
    T cacheTag(String tag);
}
有了上面的基礎后结洼,現(xiàn)在開始使用緩存控制
  • 1.先搞個服務實例出來
//緩存key header為 test , 默認緩存時間為 60s
ICacheService cache = new LocalCache("test",60); 
  • 2.用起來

使用緩存黎做,時間為默認(會自動產(chǎn)生穩(wěn)定的緩存key)

db.table("test").select("*").caching(cache).getMapList();

使用緩存,并緩存30s

db.table("test")
  .caching(cache).usingCache(30) //也可以放在table() 和 select()之間
  .select("*").getMapList();

給緩存加個tag(tag 相當于 緩存key的虛擬文件夾)

db.table("test")
  .caching(cache)
  .usingCache(30).cacheTag('test_all') //這是tag松忍,不是key
  .limit(10,20)
  .select("*").getMapList();

*3.精細控制

根據(jù)查詢結(jié)果控制緩存時間

db.table("test").where("id=?",10)
  .caching(cache)
  .select("*").getItem(UserModel.class,(cu,m)->{
    if(m.hot > 2){
        uc.usingCache(60 * 5); //熱門用戶緩存5分鐘
    }else{
        uc.usingCache(30);
    }
  });
  • 4.緩存清除

以一個分頁查詢?yōu)槔?/p>

db.table("test").limit(20,10)
  .caching(cache).cacheTag("test_all")
  .select("*").getMapList();

db.table("test").limit(30,10)
  .caching(cache).cacheTag("test_all")
  .select("*").getMapList();

//不管你有多少分頁蒸殿,一個tag把它清光
cache.clear("test_all");
  • 5.緩存更新

這個極少用(需要單項更新的緩存,建議用redis)

db.table("test").where("id=?",10)
  .caching(cache).cacheTag("test_"+10)
  .select("*").getItem(UserModel.class);

cache.update("test_"+10,(UserModel m)->{
    m.sex = 10;
    return m;
});

框架的緩存控制鸣峭,也是極為自由的喲宏所。應該是的吧?哈合摊溶。

緩存服務的可用情況
1.內(nèi)置緩存服務
  • org.noear.weed.cache.EmptyCache // 空緩存
  • org.noear.weed.cache.LocalCache // 輕量級本地緩存(基于Map實現(xiàn))
  • org.noear.weed.cache.SecondCache // 二級緩存(組裝兩個 ICacheServiceEx 實現(xiàn))
2.擴展緩存服務
  • org.noear.weed.cache.ehcache.EhCache // 基于ehcache封裝
<dependency>
  <groupId>org.noear</groupId>
  <artifactId>weed3.cache.ehcache</artifactId>
  <version>3.2.3.4</version>
</dependency>
  • org.noear.weed.cache.j2cache.J2Cache // 基于國人開發(fā)的J2Cache封裝
<dependency>
  <groupId>org.noear</groupId>
  <artifactId>weed3.cache.j2cache</artifactId>
  <version>3.2.3.4</version>
</dependency>
  • org.noear.weed.cache.memcached.MemCache // 基于memcached封裝
<dependency>
  <groupId>org.noear</groupId>
  <artifactId>weed3.cache.memcached</artifactId>
  <version>3.2.3.4</version>
</dependency>
  • org.noear.weed.cache.redis.RedisCache // 基于redis封裝
<dependency>
  <groupId>org.noear</groupId>
  <artifactId>weed3.cache.redis</artifactId>
  <version>3.2.3.4</version>
</dependency>
  • 也可以自己封裝個 ICacheServiceEx ...
要不要自己封裝個爬骤?

【2.3.4】查詢之其它

再補充些查詢相關的內(nèi)容
  • 別名
db.table("user u")
  .limit(20)
  .select("u.mobile mob");
  • 去重
db.table("user")
  .limit(20)
  .select("distinct  mobile");
  • 分組
db.table("user u")
  .groupBy("u.mobile").having("count(*) > 1")
  .select("u.mobile,count(*) num");
  • 排序
db.table("user u")
  .orderBy("u.mobile ASC")
  .select("u.mobile,count(*) num");
  • 分組+排序(或者隨意組合)
db.table("user u")
  .where("u.id < ?",1000)
  .groupBy("u.mobile").having("count(*) > 1")
  .orderBy("u.mobile ASC")
  .caching(cache)
  .select("u.mobile,count(*) num").getMap("mobile,num")

【2.4】存儲過程與查詢過程

關于存儲過程的支持,設計了兩個方案
  • 1.對接數(shù)據(jù)庫的存儲過程調(diào)用
db.call("user_get").set("_user_id",1).getMap();
  • 2.SQL查詢過程(我叫它:查詢過程)

看起來跟mybatis的SQL注解代碼有點兒像

//由SQL構(gòu)建的一個查詢
db.call("SELECT * FROM user WHERE id=@{user_id}").set("user_id",1).getMap();
還可以對它們進行實體化(變成一個獨立的類)

實體化的作用在于莫换,可將數(shù)據(jù)處理安排到別的模塊(或文件夾)

  • 1.對接數(shù)據(jù)庫的存儲過程實體化
public class user_get extends DbStoredProcedure {
    public user_get() {
        super(DbConfig.test);

        call("user_get");
        set("_userID", () -> userID);
    }

    public long userID;
}

user_get sp  =new user_get();
sp.userID=10;
Map<String,Object> map = sp.caching(cache).getMap();//順帶加個緩存
  • 2.查詢過程的實體化
public class user_get2 extends DbQueryProcedure {
    public user_get2() {
        super(db);

        sql("select * from user where type=@{type} AND sex=@{sex}");
        // 這個綁定寫法盖腕,想了很久才想出來的(就是不想反射:斩)
        set("type", () -> type);
        set("sex", () -> sex);
    }

    public int type;
    public int sex;
}
//DbQueryProcedure 提供了與 DbStoredProcedure 相同的接口
user_get2 sp  =new user_get2();
sp.userID=10;
Map<String,Object> map = sp.caching(cache).getMap();

【2.5】解決數(shù)據(jù)庫關鍵字問題

weed3提供了字段和對象格式化支持,通過DbContext進行設定
//以mysql為例
DbContext db = new DbContext(...).fieldFormatSet("`%`")//設定字段格式符
                                 .objectFormatSet("`%`");//設定對象格式符
//%號為占位符溃列,`%`表過你的字段會轉(zhuǎn)為:`字段名`

字段格式符對會對:
.set(..), .select(..), .orderBy(..), .groupBy(..) 里的字段進行處理

對對象格式符對會:
.table(..), innerJoin(..), leftJoin(..), rightJoin(..) 里的表名進行處理

如果不設置劲厌,則需要自己手動處理關鍵字;手動處理與自動處理并不沖突听隐。
//手動處理
db.table("`user`").where("`count`<10 AND sex=1").count();
格式化是由IDbFormater來處理的补鼻,如果覺得里面的實現(xiàn)不好,還可以自己寫一個替換它:)
IDbFormater df = new DfNew(); //DfNew 算是自己寫的
db.formaterSet(df); //搞定

//附:
public interface IDbFormater {
    /** 字段格式符設置 */
    void fieldFormatSet(String format);
    /** 對象格式符設置 */
    void objectFormatSet(String format);

    /** 格式化字段(用于:set(..,v)) */
    String formatField(String name);
    /** 格式化多列(用于:select(..) orderBy(..) groupBy(..)) */
    String formatColumns(String columns);
    /** 格式化條件(用于:where(..) and(..) or(..))  */
    String formatCondition(String condition);
    /** 格式化對象(用于:from(..), join(..)) */
    String formatObject(String name);
}

【2.5】盤點三大java使用接口(table,call,sql)

1.table() 執(zhí)行:鏈式ORM操作

此處略(前面主要就講這個接口)

2.call(..) 執(zhí)行:存儲過程 或 查詢過程
//執(zhí)行存儲過程
db.call("user_get").set("_user_id",1).getMap();

//執(zhí)行查詢過程(我暫時這么叫它)
db.call("select * from user where id=@{user_id}").set("user_id",1).getMap();
3.sql(..) 執(zhí)行:SQL語句
db.sql("select * from user where id=?",1).getMap();

db.sql(..) 還有一個快捷版:db.exec(..)雅任。相當于:db.sql(...).execute(); //批處理時风范,可快速寫增、刪沪么、改動作
例:db.exec("DELETE FROM test where a=1")

最終統(tǒng)一返回:IQuery (保證了體驗的統(tǒng)一性)

db.table(..).select(..) -> IQuery
db.call(..) -> IQuery
db.sql(..) -> IQuery


public interface IQuery extends ICacheController<IQuery> {
     long getCount() throws SQLException;
     Object getValue() throws SQLException;
     <T> T getValue(T def) throws SQLException;

     Variate getVariate() throws SQLException;
     Variate getVariate(Act2<CacheUsing,Variate> cacheCondition) throws SQLException;

     <T extends IBinder> T getItem(T model) throws SQLException;
     <T extends IBinder> T getItem(T model, Act2<CacheUsing, T> cacheCondition) throws SQLException;


     <T extends IBinder> List<T> getList(T model) throws SQLException;
     <T extends IBinder> List<T> getList(T model, Act2<CacheUsing, List<T>> cacheCondition) throws SQLException;

     <T> T getItem(Class<T> cls) throws SQLException;
     <T> T getItem(Class<T> cls,Act2<CacheUsing, T> cacheCondition) throws SQLException;

     <T> List<T> getList(Class<T> cls) throws SQLException;
     <T> List<T> getList(Class<T> cls,Act2<CacheUsing, List<T>> cacheCondition) throws SQLException;

     DataList getDataList() throws SQLException;
     DataList getDataList(Act2<CacheUsing, DataList> cacheCondition) throws SQLException;
     DataItem getDataItem() throws SQLException;
     DataItem getDataItem(Act2<CacheUsing, DataItem> cacheCondition) 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;
}

【3.1】開始Xml Mapper的使用

使用約定***
  • 1.約定resources/weed3/ 為 xml sql 根目錄
  • 2.項目開啟編譯參數(shù):-parameters
準備開始做個簡單的例子

這次需要引用一個meven插件(玩過mybatis都懂的)

框架引用

<dependency>
  <groupId>org.noear</groupId>
  <artifactId>weed3</artifactId>
  <version>3.2.3.4</version>
</dependency>

meven插件引用(用于生成mapper類)

<!-- 放到 build / plugins / 下面 -->
<plugin>
    <groupId>org.noear</groupId>
    <artifactId>weed3-maven-plugin</artifactId>
    <version>3.2.3.4</version>
</plugin>
(一)現(xiàn)在硼婿,先寫個簡單的xml文件
  • resources/weed3/DbUserApi.xml
<?xml version="1.0" encoding="utf-8" ?>
<mapper namespace="weed3demo.xmlsql" :db="testdb">
    <sql id="user_get" 
         :return="weed3demo.mapper.UserModel" 
         :note="獲取用戶信息">
        SELECT * FROM `user` WHERE id=@{user_id}
    </sql>
</mapper>
(二)可以有兩種方式調(diào)用剛才的xml sql
通過 db.call("@...") 調(diào)用
DbContext db = new DbContext(...);

UserModel um = db.call("@weed3demo.xmlsql.user_get")
                 .set("user_id")
                 .getItem(UserModel.class);
生成Mapper接口,通過動態(tài)代理使用
1.用meven插件把它生成(雙擊:weed3:generator)
WX20191015-224938@2x.png
2.生成的Java文件(java/weed3demo/xmlsql/DbUserApi.java)
package weed3demo.xmlsql;
@Namespace("weed3demo.xmlsql")
public interface DbUserApi{
  /** 獲取用戶信息*/
  weed3demo.mapper.UserModel user_get(int user_id);
}
3.試一下
//全局
public static void main(String[] args){
  //配置一個上下文禽车,并加上名字(給xml mapper 用)
  DbContext db = new DbContext(...).nameSet("testdb");

  //通過代理獲取xml mapper
  DbUserApi dbUserApi = XmlSqlMapper.get(DbUserApi.class);
  //使用它
  UserModel tmp = dbUserApi.user_get(10);
}

【3.2】Xml Mapper的指令和語法

五個指令 + 三種變量形式寇漫。先來段xml

這個示例里把各種情況應該呈現(xiàn)出來了

<?xml version="1.0" encoding="utf-8" ?>
<mapper namespace="weed3demo.xmlsql2" :db="testdb">
    <sql id="user_add1" :return="long"
         :param="m:weed3demo.mapper.UserModel,sex:int"
         :note="添加用戶">
        INSERT user(user_id,mobile,sex) VALUES(@{m.user_id},@{m.mobile},@{sex})
    </sql>

    <sql id="user_add2" :return="long" :note="添加用戶">
        INSERT user(user_id) VALUES(@{user_id:int})
    </sql>

    <sql id="user_add_for" :return="long" :note="批量添加用戶3">
        INSERT user(id,mobile,sex) VALUES
        <for var="m:weed3demo.mapper.UserModel" items="list">
            (@{m.user_id},@{m.mobile},@{m.sex})
        </for>
    </sql>

    <sql id="user_del" :note="刪除一個用戶">
        DELETE FROM user WHERE id=@{m.user_id:long}
        <if test="sex gt 0">
            AND sex=@{sex:int}
        </if>
    </sql>

    <sql id="user_set"
         :note="更新一個用戶,并清理相關相存"
         :caching="localCache"
         :cacheClear="user_${user_id},user_1">
        UPDATE user SET mobile=@{mobile:String},sex=@{sex:int}
        <if test="icon != null">
            icon=@{icon:String}
        </if>
    </sql>

    <sql id="user_get_list"
         :note="獲取一批符合條件的用戶"
         :declare="foList:int,user_id:long"
         :return="List[weed3demo.mapper.UserModel]"
         :caching="localCache"
         :cacheTag="user_${user_id},user_1">
        SELECT id,${cols:String} FROM user
        <trim prefix="WHERE" trimStart="AND ">
            <if test="mobile?!">
                AND mobile LIKE '${mobile:String}%'
            </if>
            <if test="foList == 0">
                AND type='article'
            </if>
            <if test="foList == 1">
                AND type='post'
            </if>
        </trim>
    </sql>

    <sql id="user_cols1">name,title,style,label</sql>
    <sql id="user_cols2">name,title</sql>

    <sql id="user_get_list2"
         :note="獲取一批符合條件的用戶"
         :declare="foList:int,user_id:long"
         :return="List[weed3demo.mapper.UserModel]"
         :caching="localCache"
         :cacheTag="user_${user_id},user_1">
        SELECT id,
        <if test="foList == 0">
            <ref sql="user_cols1"/>
        </if>
        <if test="foList == 1">
            <ref sql="user_cols2"/>
        </if>
        FROM user WHERE sex>1 AND mobile LIKE '${mobile:String}%'

    </sql>
</mapper>
四個指令說明
sql 代碼塊定義指令
  :require(屬性:導入包或類)
  :param?(屬性:外部輸入變量申明殉摔;默認會自動生成::新增***)
  :declare(屬性:內(nèi)部變量類型預申明)
  :return(屬性:返回類型)

  :db (屬性:數(shù)據(jù)庫上下文name)
  :note(屬性:描述州胳、說明、注解)

  :caching(屬性:緩存服務name) //是對 ICacheController 接口的映射
  :cacheClear?(屬性:清除緩存)
  :cacheTag?(屬性:緩存標簽逸月,支持在入?yún)⒒蚪Y(jié)果里取值替換)
  :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 循環(huán)控制指令 (通過 ${var}_index 可獲得序號栓撞,例:m_index::新增***)
  var (屬性:循環(huán)變量申明)
  items (屬性:集合變量名稱)
  sep? (屬性:分隔符::新增***)

trim 修剪指令
  trimStart(屬性:開始位去除)
  trimEnd(屬性:結(jié)尾位去除)
  prefix(屬性:添加前綴)
  suffix(屬性:添加后綴)

ref 引用代碼塊指令
  sql (屬性:代碼塊id)
三種變量形式
name:type    = 變量申明(僅用于var ,或:declare)
@{name:type} = 變量注入(僅用于代碼塊)
${name:type} = 變量替換(用于代碼塊碗硬,或:cacheTag瓤湘,或:cacheClear)
關于返回值的幾種形式說明
//多行,列表(用[]替代<>)
: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 (或別的任何單職類型)

【4.1】開始注解sql的使用

使用約定***
  • 1.項目開啟編譯參數(shù):-parameters
先來個demo
  • 1.申明一個mapper
public interface DbMapper1{
    @Sql(value = "select * from ${tb} where app_id = @{app_id} limit 1", 
         caching = "test", 
         cacheTag = "app_${app_id}")
    AppxModel appx_get(String tb, int app_id) throws Exception;
}
  • 2.使用它
DbContext db = new DbContext(...);

DbMapper1 dm = db.mapper(DbMapper1.class);
AppxModel m = dm.appx_get("appx",1); 
兩種變量形式 + 緩存控制
兩種變量形式
  • ${} 替代變量(相當于占位符恩尾,進行字符串拼接)
  • @{} 編譯變量(會編譯為?岭粤,通過變量傳遞給jdbc)
緩存控制
  • caching 緩存服務
  • cacheTag 緩存標簽(在key之上,建立的虛擬tag特笋;為便于清理)
  • usingCache 緩存使用時間
  • cacheClear 緩存清理(通過cacheTag形式清理)
再來一個demo2

更新之后,清掉緩存:app_${app_id}

public interface DbMapper2{
    @Sql(value = "update appx set name=@{name} where app_id = @{app_id}", 
         caching = "test", 
         cacheClear = "app_${app_id}")
    void appx_set(int app_id, String name) throws Exception;
}
再來一個demo3

使用查詢結(jié)果構(gòu)建cahce tag:app_type${type}

public interface DbMapper3{
    @Sql(value = "select * from appx where app_id = @{app_id} limit 1", 
         caching = "test", 
         cacheTag = "app_${app_id},app_type${type}")
    AppxModel appx_get(int app_id) throws Exception;
}
補充:構(gòu)建一個緩存服務
//隨便寫在哪里

//1.初始化一個ICacheServiceEx
//2.通過nameSet("test") 注冊到緩存庫
//3.之后就可以被 @sql的 caching 使用(xml sql 的 caching 同樣如此)
//
new LocalCache("test",60).nameSet("test");

【5】事務和事務隊列

之前講過插入和更新
這次講事務(寫操作總會傍隨事務嘛...)
  • weed3 支持兩種方式的事務
  • 1.事務(主要用于單個庫)
//demo1:: //事務組 // 在一個事務里巾兆,做4個插入//如果出錯了猎物,自動回滾
DbUserApi dbUserApi = XmlSqlProxy.getSingleton(DbUserApi.class);

db.tran((t) -> {
    //
    // 此表達式內(nèi)的操作,會自動加入事務
    //
//sql接口
    db.sql("insert into test(txt) values(?)", "cc").insert();
    db.sql("update test set txt='1' where id=1").execute();
//call接口
    db.call("user_del").set("_user_id",10).execute();
//table()接口
    db.table("a_config").set("cfg_id",1).insert();
//xml mapper
    dbUserApi.user_add(12);
//大家使用統(tǒng)一的事務模式
});
  • 2.事務隊列(主要用于多個庫的情況)
//demo2:: //事務隊列
//
//假如角塑,要跨兩個數(shù)據(jù)庫操作(一個事務對象沒法用了)
//
DbContext db = DbConfig.pc_user;
DbContext db2 = DbConfig.pc_base;

//創(chuàng)建個事務隊列(和傳統(tǒng)概念的隊列不一樣)
DbTranQueue queue = new DbTranQueue();

//數(shù)據(jù)庫1的事務
db.tran().join(queue).execute((t) => {
    //
    // 在這個表達示內(nèi)蔫磨,會自動加入事物
    //
    db.sql("insert into test(txt) values(?)", "cc").execute();
    db.sql("insert into test(txt) values(?)", "dd").execute();
    db.sql("insert into test(txt) values(?)", "ee").execute();
});

//數(shù)據(jù)庫2的事務
db2.tran().join(queue).execute((t) => {
    //
    // 在這個表達示內(nèi),會自動加入事物
    //
    db2.sql("insert into test(txt) values(?)", "gg").execute();
});

//隊列結(jié)組完成(即開始跑事務)
queue.complete();
  • 3.事務隊列的加強版圃伶,跨函數(shù)或模塊跑事務
public void test_main(){
    DbTranQueue queue = new DbTranQueue();
    test1(queue);
    test2(queue);
}

public void test1(DbTranQueue queue){
    DbTran tran = DbConfig.db1.tran();//生成個事務對象

    tran.join(queue).execute((t) -> {
            //
            // 在這個表達示內(nèi)堤如,會自動加入事物
            //
            t.db().sql("insert into $.test(txt) values(?)", "cc").insert();
            t.db().sql("insert into $.test(txt) values(?)", "dd").execute();
            t.db().sql("insert into $.test(txt) values(?)", "ee").execute();

            t.result = t.db().sql("select name from $.user_info where user_id=3").getValue("");
        });
}

public void test2(DbTranQueue queue){
    //...test2就不寫了
}

【6】對所有執(zhí)行進行監(jiān)視

通過WeedConfig開放了一些監(jiān)聽接口

比如:異常監(jiān)聽蒲列,慢SQL監(jiān)聽

//監(jiān)聽異常,以便統(tǒng)一的打印或記錄
WeedConfig.onException((cmd, ex) -> {
    if (cmd.text.indexOf("a_log") < 0 && cmd.isLog >= 0) {
        System.out.println(cmd.text);
    }
});

//監(jiān)聽SQL性能搀罢,以便統(tǒng)一記錄
WeedConfig.onExecuteAft((cmd)->{
    if(cmd.timespan()>1000){ //執(zhí)行超過1000毫秒的..
        System.out.println(cmd.text + "::" + cmd.timespan() +"ms");
    }
});
具體的可監(jiān)聽事件
//異常事件
WeedConfig.onException(Act2<Command, Exception> listener);
//日志事件(可以把命令信息記錄下來)
WeedConfig.onLog(Act1<Command> listener);
//執(zhí)行前事件
WeedConfig.onExecuteBef(Fun1<Boolean, Command> listener);
//執(zhí)行中事件(可以監(jiān)聽蝗岖,Statement)
WeedConfig.onExecuteStm(Act2<Command, Statement> listener);
//執(zhí)行后事件
WeedConfig.onExecuteAft(Act1<Command> listener);

【7】嵌入到腳本或模板

嵌入到腳本引擎
  • 嵌入到javascript引擎(nashorn)
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine _eng = scriptEngineManager.getEngineByName("nashorn");
Invocable _eng_call = (Invocable)_eng;
_eng.put("db", db);
var map = db.table("test").where('id=?',1).getMap();
  • 嵌入到groovy引擎
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine _eng = scriptEngineManager.getEngineByName("groovy");
Invocable _eng_call = (Invocable)_eng;
_eng.put("db", db);
def map = db.table("test").where('id=?',1).getMap();
嵌入到模板引擎
  • 嵌入到Freemarker引擎
<#assign tag_name=ctx.param('tag_name','') />
<#assign tags=db.table("a_config").where('label=?',label).groupBy('edit_placeholder').select("edit_placeholder as tag").getMapList() />
<!DOCTYPE HTML>
<html>
<head>
...
結(jié)束語:希望你能喜歡:)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市榔至,隨后出現(xiàn)的幾起案子抵赢,更是在濱河造成了極大的恐慌,老刑警劉巖唧取,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铅鲤,死亡現(xiàn)場離奇詭異,居然都是意外死亡枫弟,警方通過查閱死者的電腦和手機邢享,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淡诗,“玉大人骇塘,你說我怎么就攤上這事⊥噤觯” “怎么了绪爸?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宙攻。 經(jīng)常有香客問我奠货,道長,這世上最難降的妖魔是什么座掘? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任递惋,我火速辦了婚禮,結(jié)果婚禮上溢陪,老公的妹妹穿的比我還像新娘萍虽。我一直安慰自己,他們只是感情好形真,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布杉编。 她就那樣靜靜地躺著,像睡著了一般咆霜。 火紅的嫁衣襯著肌膚如雪邓馒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天蛾坯,我揣著相機與錄音光酣,去河邊找鬼。 笑死脉课,一個胖子當著我的面吹牛救军,可吹牛的內(nèi)容都是我干的财异。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼唱遭,長吁一口氣:“原來是場噩夢啊……” “哼戳寸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胆萧,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤庆揩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后跌穗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體订晌,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年蚌吸,在試婚紗的時候發(fā)現(xiàn)自己被綠了锈拨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡羹唠,死狀恐怖奕枢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情佩微,我是刑警寧澤缝彬,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站哺眯,受9級特大地震影響谷浅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奶卓,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一供鸠、第九天 我趴在偏房一處隱蔽的房頂上張望玻孟。 院中可真熱鬧漏益,春花似錦穆桂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至废膘,卻和暖如春竹海,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背殖卑。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留坊萝,地道東北人孵稽。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓许起,卻偏偏與公主長得像,于是被迫代替她去往敵國和親菩鲜。 傳聞我的和親對象是個殘疾皇子园细,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容