Weed3,微型ORM框架(支持:java sql,xml sql,annotation sql;存储过程;事务;缓存;监听;等...)
05年时开发了第一代;
08年时开发了第二代,那时候进入互联网公司,对性能有了全新的认识;
14年时开发了第三代。因为不喜欢滥用反射,不喜欢有很多配置,所以一直在执着的没放弃。
前两代,都是在.net开发的;第三代,重点放在了java上。应该算是个功能全面且最小的ORM框架,无其它依赖,仅0.1mb。对外的接口也不多,主要由DbContext上的四个接口发起所有的操作。
因为一些执念写的东西都算是比较微型的:
高性能:两年前有个同事测过四个ORM框架,它是性能最好的(不知道现在是不是)。
跨平台:可以嵌入到JVM脚本引擎(js, groovy, lua, python, ruby);有.net,php版本(久没维护了)。
很小巧:只有0.1Mb嘛(且是功能完整,方案丰富;可极大简化数据库开发)。
有个性:不喜欢反射、不喜欢配置...(除了连接,不需要任何配置)。
其它的:支持缓存控制和跨数据库事务(算是分布式事务的一种吧)。
组件 | 说明 |
---|---|
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()。应对特殊的应用场景。
其中db.table(), db.call(), db.sql() 可以友好的嵌入到 JVM脚本引擎
(js, groovy, lua, python, ruby)和部分 GraalVM
语言使用。
因为作者还有个嵌入式FaaS引擎。统一的执行发起对象、无注入无配置、且弱类型的接口作用重大;可以便利的嵌入各种语言中,并提供统一的ORM体验。
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; */
有机会,将对一些细节再做介绍...