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.6.2</version>
</dependency> <!-- maven 插件,用于生成Xml sql mapper接口 -->
<plugin>
<groupId>org.noear</groupId>
<artifactId>weed3-maven-plugin</artifactId>
<version>3.2.6.2</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;
*/

(八) 语法说明

有机会,将对一些细节再做介绍...

Weed3 for java 新的微型ORM框架的更多相关文章

  1. Snack3 一个新的微型JSON框架

    Snack3 一个新的微型JSON框架 一个作品,一般表达作者的一个想法.因为大家想法不同,所有作品会有区别.就做技术而言,因为有很多有区别的框架,所以大家可以选择的框架很丰富. snack3.基于j ...

  2. ASP.NET MVC 使用 Petapoco 微型ORM框架+NpgSql驱动连接 PostgreSQL数据库

    前段时间在园子里看到了小蝶惊鸿 发布的有关绿色版的Linux.NET——“Jws.Mono”.由于我对.Net程序跑在Linux上非常感兴趣,自己也看了一些有关mono的资料,但是一直没有时间抽出时间 ...

  3. 视频教程--ASP.NET MVC 使用 Petapoco 微型ORM框架+NpgSql驱动连接 PostgreSQL数据库

    说好的给园子里的朋友们录制与<ASP.NET MVC 使用 Petapoco 微型ORM框架+NpgSql驱动连接 PostgreSQL数据库> 这篇博客相对应的视频,由于一个月一来没有时 ...

  4. .Net开源微型ORM框架测评

    什么是ORM? 对象关系映射(英语:Object Relation Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间 ...

  5. java集群优化——ORM框架查询优化原理

    众所周知,当下的流行的企业级架构中,ORM一直是最基础的部分,在架构设计的底层.对逻辑层提供面向对象的操作支持,而事实总是和我们预想的有所偏差,ORM在提供了较好的操作体验时,也流失了一部分原生SQL ...

  6. JAVA描述的简单ORM框架

    抽了点时间自己写了个ORM,主要是为了复习JAVA泛型,映射,注解方面的知识.如需代码,可前往:https://github.com/m2492565210/java_orm自行下载 框架的类结构如下 ...

  7. 微型orm框架--dapper的简单使用

    1.安装 首先使用nuget安装dapper,因为这里的示例是使用mysql,所以还要安装mysql的驱动.如下图: 2 数据库表 脚本 ; -- -------------------------- ...

  8. ORM框架详解

    .Net开源微型ORM框架测评 什么是ORM? 对象关系映射(英语:Object Relation Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象 ...

  9. 微型ORM:PetaPoco 学习资料整理

    github地址:https://github.com/CollaboratingPlatypus/PetaPoco petapoco 实体中字段去掉关联(类似于EF中的NotMap) 微型ORM:P ...

随机推荐

  1. hdu 1385 Minimum Transport Cost (Floyd)

    Minimum Transport CostTime Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Ot ...

  2. 设置 DNS 服务器转发试验

    一.主节点的配置 1.yum install bind -y 安装 DNS 服务 2.vim /etc/named.conf 编辑 DNS 的配置文件 3. vim /etc/named.rfc191 ...

  3. 开始逆向objc基础准备(一)简单认识一下arm32,以及与x86汇编指令类比

    ARM32体系中有31或33个通用寄存器,没有特定的某种态下有r0-r15一共16个寄存器,快速中断态下有另一组r8-r12备份寄存器,在用户态和系统态之外其它态下都各自有一组r13-r14备份寄存器 ...

  4. 发送html邮件a标签中的url不显示的问题

    最近做的一个项目中,需要用到邮箱激活功能,但是遇到一个问题! 问题描述:发送邮箱之后,邮箱的激活链接的href属性不见了!!! 因为我采用的是本地的地址做测试,后来用百度的链接替换我的href地址又可 ...

  5. ceph中rbd的增量备份和恢复

    ceph中rbd的增量备份和恢复 ceph的文档地址:Ceph Documentation ​ 在调研OpenStack中虚机的备份和恢复时,发现OpenStack和ceph紧密结合,使用ceph做O ...

  6. go modules 学习

    go modules 学习 tags:golang 安装 只需要golang的版本是1.11及之后的,这个模块就内置好了 环境变量 (1) 配置GoLang的GOROOT (2) 可以不配置GoLan ...

  7. startup启动不起来关于监听的问题

    问题描述:要在sqlplus中启动到startup状态,但是提示我没有监听,本来以为启动一下就可以,但是connecting to一直卡半天,stop都停止不了 1.发现监听有问题,前去更改 SQL& ...

  8. 性能测试——记XX银行保全项目性能问题分析优化

    记XX银行保全项目性能问题分析优化 数据库问题也许是大部分性能问题的关注点,但是JAVA应用与数据库交互的关节,JDBC 就像是我们人体的上半身跟下半身的腰椎,支持上半身,协调下半身运动的重要支撑点. ...

  9. IT人该如何未雨绸缪,不断提升自己的竞争力?同时尽量避免风险?

    人会慢慢变老,变老后精力,记忆力乃至身体会慢慢变差,这是无法逆转的自然规律.随之会产生的是对中年危机的忧虑乃至恐惧,比如担心能力精力不及年轻人,从而导致收入锐减乃至失业. 对此我有如下三点不解.第一, ...

  10. pyinstaller打包python文件成exe(原理.安装.问题)

    py文件打包成exe文件的方式一共有三种:py2exe.PyInstaller和cx_Freeze 本文分四个步骤来详讲如何用PyInstaller将py文件打包成exe文件 1. PyInstall ...