Let‘s Fluent:更顺滑的MyBatis

简介: 只需瞅一眼Google Trends上全球Java界最热门的两款SQL映射框架近一年的对比数字,就不难了解其实力分布:在此领域,MyBatis早已占领东亚地区开发者市场,并以绝对优势稳居中国最抢手Java数据库访问框架之首。

image.png

作者 | 金戟
来源 | 阿里技术公众号

只需瞅一眼Google Trends上全球Java界最热门的两款SQL映射框架近一年的对比数字,就不难了解其实力分布:在此领域,MyBatis早已占领东亚地区开发者市场,并以绝对优势稳居中国最抢手Java数据库访问框架之首。

MyBatis霸榜的底气来源于其广袤的生态以及国内众多大厂的支持。而在琳琅满目的MyBatis扩展中,还埋藏着许多“宝藏项目”,来自阿里技术团队的Fluent MyBatis便是其中一颗独特的新星。

一 普拉斯们不香了

从iBatis到MyBatis,再到国内团队以MyBatis Plus为典型代表的诸多周边工具,"Batis"系列套餐的发展历程,几乎又是一部XML的兴衰史。最初的iBatis诞生于2002年,彼时XML在Java乃至整个软件技术界都还相当盛行,和同时期的许多项目一样,iBatis硬生生的将一堆堆XML塞进千家万户的项目里。

许多年后,曾今与iBatis并肩过的社区战友们纷纷淡出了历史舞台,少数像Spring这样延续至今的佼佼者,也逐渐摒弃XML,向代码化配置的方式发展。在这方面,iBatis一直是个保守派,即使在MyBatis接过iBatis的衣钵之后,也只是”重磅“推出了支持代码执行SQL的@Select/@Insert/@Update/@Delete注解(以及相应的4种Provider注解),用来抵挡开发者们对XML泛滥的吐槽,这是在2010年中旬,然后就再无动作。直到2016年底,MyBatis的主要贡献者之一Jeff Butler正式创建MyBatis Dynamic SQL项目,MyBatis终于开始全面拥抱无XML的代码化SQL构建。

在从MyBatis到MyBatis Dynamic SQL之间长达6年多的空窗期里,开源社区催生出了许多民间基于MyBatis的无XML代码方案,其中流行得比较广泛的是Tk Mybatis、MyBatis Plus这类内置Mapper和自动生成CRUD的扩展库,一经推出就收获诸多好评。包括MyBatis Plus里实际上并不太完备的"条件构造器"功能,也由于当时同类解决方案的匮乏而颇受追捧。与此同时,在MyBatis社区之外,一直在默默发展的JOOQ是一款历史与MyBatis几乎同样悠久的纯Java动态SQL执行库,它的用户群体不大,却口碑甚好。如今在任意搜索引擎上输入"MyBatis vs JOOQ",依然能得到几乎是一边倒选择JOOQ的结果,大家给出的理由也非常一致:简洁、灵活、无需XML,很"Java"。而在MyBatis阵营里,若是拿出MyBatis Plus的"条件构造器"与之正面对阵,只消三个回合,就会被屁滚尿流的打出擂台。只可惜JOOQ的家底没有MyBatis那样殷实,早早走上了商业数据库支持卖License收费的道路,才让MyBatis免于在舆论上迎来自己的中年危机。

Fluent MyBatis诞生于2019年底,即使与MyBatis Dynamic SQL相比都是晚辈,然而尚处成长期的它就已透出了青出于蓝而胜于蓝的味道。

在实现方式上,MyBatis Plus覆写并替换了部分MyBatis内部类型的方法,整体机制较重,却也因此能将一些功能细节隐藏到用户无需关注的内部逻辑里;与之相反,MyBatis Dynamic SQL的实现机制非常轻量,不仅完全基于MyBatis原生的Provider系列注解开发,而且没有什么隐藏逻辑,对用户的每张表自动生成相应的Entity、DynamicSqlSupport和Mapper三个类,全部放入用户的源码目录里,因此暴露的细节比较多,代码侵入性略高。Fluent MyBatis取二者之所长,整体机制与MyBatis Dynamic SQL更接近,同样基于原生的Provider注解,对用户的每个表生成Entity类和默认空白的Dao类,不同之处在于它还会通过JVM编译期代码增强功能自动生成许多开发者不可更改的标准辅助类,这些代码无需放入用户的源码目录但能够在编码时直接使用,即提供丰富的功能,又保证了用户代码的整洁。

在使用方式上,Fluent MyBatis同样借鉴了前辈们的最优实践,没有花里胡哨的注解和配置,直接复用MyBatis连接,所有功能开箱即用。同时由于Fluent MyBatis将所有表字段、条件、操作都以方法调用形式提供,因此获得了比其他同类项目都更好的IDE语法辅助。举一个不太复杂的例子:

// 使用Fluent MyBatis构造查询语句
mapper.listMaps(new StudentScoreQuery().select.schoolTerm().subject().count.score("count").min.score("min_score").max.score("max_score").avg.score("avg_score").end().where.schoolTerm().ge(2000).and.subject.in(new String[]{"英语", "数学", "语文"}).and.score().ge(60).and.isDeleted().isFalse().end().groupBy.schoolTerm().subject().end().having.count.score.gt(1).end().orderBy.schoolTerm().asc().subject().asc().end()
);

MyBatis Dynamic SQL的语法也比较美观,但字段名和min/max/avg等方法都需要静态引用,比Fluent MyBatis稍显逊色。

// 使用MyBatis Dynamic SQL构造查询语句
mapper.selectMany(select(schoolTerm,subject,count(score).as("count"),min(score).as("min_score"),max(score).as("max_score"),avg(score).as("avg_score")).from(studentScore).where(schoolTerm, isGreaterThanOrEqualTo(2000)).and(subject, isIn("英语", "数学", "语文")).and(score, isGreaterThanOrEqualTo(60)).and(isDeleted, isEqualTo(false)).groupBy(schoolTerm, subject).having(count(score), isGreaterThan(1)) //当前其实还不支持having方法.orderBy(schoolTerm, subject).build(isDeleted, isEqualTo(false)).render(RenderingStrategies.MYBATIS3)
);

JOOQ的历史比较悠久,写出来的代码铺天盖地都是常量字段,功能强大但美观度欠佳。

// 使用JOOQ构造查询语句
dslContext.select(STUDENT_SCORE.GENDER_MAN,STUDENT_SCORE.SCHOOL_TERM,STUDENT_SCORE.SUBJECT,count(STUDENT_SCORE.SCORE).as("count"),min(STUDENT_SCORE.SCORE).as("min_score"),max(STUDENT_SCORE.SCORE).as("max_score"),avg(STUDENT_SCORE.SCORE).as("avg_score")
)
.from(STUDENT_SCORE)
.where(STUDENT_SCORE.SCHOOL_TERM.ge(2000),STUDENT_SCORE.SUBJECT.in("英语", "数学", "语文"),STUDENT_SCORE.SCORE.ge(60),STUDENT_SCORE.IS_DELETED.eq(false)
)
.groupBy(STUDENT_SCORE.GENDER_MAN,STUDENT_SCORE.SCHOOL_TERM,STUDENT_SCORE.SUBJECT
)
.having(count().ge(1))
.orderBy(STUDENT_SCORE.SCHOOL_TERM.asc(),STUDENT_SCORE.SUBJECT.asc()
)
.fetch();

MyBatis Plus的条件构造器仅仅封装了基本的SQL操作,对于字段、条件、别名等都要使用字符串拼接,极易出现由于拼写失误引起的SQL异常。

// 使用MyBatis Plus构造查询语句
mapper.selectMaps(new QueryWrapper<StudentScore>().select("school_term","subject","count(score) as count","min(score) as min_score","max(score) as max_score","avg(score) as avg_score").ge("school_term", 2000).in("subject", "英语", "数学", "语文").ge("score", 60).eq("is_deleted", false).groupBy("school_term", "subject").having("count(score)>1").orderByAsc("school_term", "subject")
);

在Java动态SQL构建的功能完整度方面,当前的排序是MyBatis Plus < MyBatis Dynamic SQL < Fluent MyBatis < JOOQ。

MyBatis Plus条件构造器在功能性上完败,不仅无法表达JOIN、UNION语句,嵌套查询之类稍复杂SQL也完全没招。MyBatis Dynamic SQL支持JOIN和UNION语句,尚未支持嵌套查询,且缺少HAVING等少量标准SQL语法。Fluent MyBatis支持多表JOIN、UNION、嵌套查询和几乎所有标准SQL语法,对于绝大多数场景都妥妥够用。JOOQ是真正的王者,不仅支持标准SQL语法,连各厂商特有的专有关键字和内置方法都没放过,如MySQL的ON DUPLICATE KEY UPDATE、PostgreSQL的WINDOW、Oracle的CONNECT BY等等。补齐各种SQL语法是一件琐碎而费力的工作,考虑到SQL语法的总量已经基本不再变化,相信假以时日,各方的差距会逐渐缩小。

除了SQL基本功,特别值得一提的是Fluent MyBatis的独门绝技:支持动态换表名(FreeQuery/FreeUpdate特性)。在云效项目的开发过程中,由于需要在各种嵌套查询之上再根据视图条件动态选择聚合计算的维度表,多亏了Fluent MyBatis的动态表名功能,才得以在最大程度保留语法构造便利性的情况下,让代码复用成为可能。

相比密密麻麻的XML文件,Java代码在易读性和可维护性方面有着明显的优势。在官方和社区的共同推动下,一个全新的、代码化的MyBatis生态正在冉冉升起。蓦然回首,曾经骄傲的"Plus扩展"们全都不香了。

二 优雅的数据流

初识Fluent MyBatis,最明显能感受到的特点是它及其便利的IDE语法提示。

基于数据表自动生成的Entity、Mapper、Query、Update等对象,让所有的数据库字段和SQL操作都变成了方法,串成平整的流式语句。即使是层层嵌套的查询,也能表现得错落有致:

new StudentQuery().where.isDeleted().isFalse().and.grade().eq(4).and.homeCountyId().in(CountyDivisionQuery.class, q -> q.selectId().where.isDeleted().isFalse().and.province().eq("浙江省").and.city().eq("杭州市").end()).end();

很容易就能看出,上述语句对应的SQL为:

SELECT * FROM student
WHERE is_deleted = false
AND grade = 4
AND home_county_id IN (SELECT id FROM county_division WHERE is_deleted = falseAND province = '浙江省'AND city = '杭州市'
)

不仅如此,Fluent MyBatis实现的JOIN语法经过几次调整后,现在的版本也已经十分美观:

JoinBuilder.from(new StudentQuery("t1", parameter).selectAll().where.age().eq(34).end()
).join(new HomeAddressQuery("t2", parameter).where.address().like("address").end()
).on(l -> l.where.homeAddressId(),r -> r.where.id()
).endJoin().build();

其中利用Lambada语句表达JOIN条件的设计即充分符合了Java开发者的习惯,又很好的匹配了IDE语法提示的需要,细思极妙。

Fluent MyBatis中的流可以设置条件过滤,例如“仅更新值为非空的字段”:

new StudentUpdate().update.name().is(student.getName(), If::notBlank).set.phone().is(student.getPhone(), If::notBlank).set.email().is(student.getEmail(), If::notBlank).set.gender().is(student.getGender(), If::notNull).end().where.id().eq(student.getId()).end();

上面这段代码等效于MyBatis中的如下XML内容:

image.png

显然Java的流式代码可读性远高于XML文件的尖括号套尖括号的层叠结构。

流是可续接的,对于更复杂的分支条件,Fluent MyBatis中能利用譬如下述语句,充分发挥出Java代码的灵活性:

StudentQuery studentQuery = Refs.Query.student.aliasQuery().select.age().end().where.age().isNull().end().groupBy.age().apply("id").end();
if (config.shouldFilterAge()) {studentQuery.having.max.age().gt(1L).end();
} else if (config.shouldOrder()) {studentQuery.orderBy.id().desc().end();
}

这种基于外部变量状态的判断,已然超出了MyBatis的XML文件的能力范围。

三 三分钟源码浅析

Fluent MyBatis的代码由Fluent Generator和Fluent MyBatis两个子项目组成。这对组合与MyBatis Generator搭档MyBatis Dynamic SQL有异曲同工之妙:Fluent Generator通过读取数据库里的表,自动生成Fluent MyBatis所需的Entity和Dao对象;Fluent MyBatis提供编写SQL语句的函数式DSL。

Fluent Generator子项目的代码显得朴实而平铺直述,程序入口在包结构树最外层的FileGenerator类型里,由开发者直接调用该类的build()方法,使用链式构造器方式传入需读取的表名和存放生成文件的目录等配置。Fluent Generator根据这些信息从数据库里读取出表结构,然后为每张表生成Entity和Dao类型的Java文件,放置到约定位置,整个逻辑一气呵成。值得一提的是,Fluent Generator的配置方法是完全代码化的,相比MyBatis Generator虽支持纯代码化配置,却在官方示例继续沿用XML文件配置输入的作风更胜一筹。

Fluent Generator生成的Dao类型默认是空的类,它只是一种推荐的数据查询层结构,通过继承各自的BaseDao类型,获得便捷操作Mapper的能力。

Fluent MyBatis子项目的代码要稍显丰盈一些,分为三个模块:

  • fluent-mybatis 包含各种公共基础类
  • fluent-mybatis-test 测试用例
  • fluent-mybatis-processor 编译期代码生成器

fluent-mybatis模块定义了与代码生成相关的注解、数据模型和其他辅助类型,它们大多都是幕后英雄:开发者通常不会直接用到这个包中的类。

fluent-mybatis-test模块包含丰富的测试用例,在一定程度上弥补了Fluent MyBatis当前阶段尚不完备的文档。平时遇到的许多Fluent MyBatis使用问题,若在文档上无法找到,那么翻一翻代码库的测试用例,一定会有意外的收获。

fluent-mybatis-processor模块的原理与Lombook工具库类似,但它并不修改原有的类型,而是扫描Entity类型上的注解,然后动态产生新的辅助类。Fluent Generator产出的Entity类就像是潘多拉盒子,蕴含着Fluent MyBatis魔法的秘密。FluentMybatisProcessor类是整场表演的魔术师,它将每个形如XyzEntity的实体类变幻出一系列辅助类,其中比较关键的包括:

  • XyzBaseDao:继承BaseDao类型,实现IBaseDao接口,包含获得Entity相关Mapper、Query、Update类型的方法,是Fluent Generator为用户生成的空白Dao类的父类。
  • XyzMapper:实现IEntityMapper,IRichMapper、IWrapperMapper接口,用于构造Query和Update对象,以及执行IQuery或IUpdate类型的SQL指令。
  • XyzQuery:继承BaseWrapper、BaseQuery类型,实现IWrapper、IQuery接口,用于组装查询语句的基本容器。
  • XyzUpdate:继承BaseWrapper、BaseUpdate类型,实现IWrapper、IBaseUpdate接口,用于组装更新语句的基本容器。
  • XyzSqlProvider:继承BaseSqlProvider类型,用于最终组装SQL语句。
  • 还有XyzMapping、XyzDefaults、XyzFormSetter、XyzEntityHelper:、XyzWrapperHelper等。由fluent-mybatis-processor模块生成的许多类型都会在编写业务代码的时候用到。

一个典型的Fluent MyBatis工作流程是先通过生成的Query或Update类型组装出执行对象,然后交给Mapper对象下发执行。譬如:

// 构造并执行查询语句
List<StudentEntity> users = mapper.listEntity(new StudentQuery()        .select.name().score().end().where.userName().like("user").end().orderBy.id().asc().end().limit(20, 10)
);// 构造并执行更新语句
int effectedRecordCount = mapper.updateBy(new StudentUpdate().set.userName().is("u2").set.isDeleted().is(true).set.homeAddressId().isNull().end().where.isDeleted().eq(false).end()
);

Query和Update类型不仅实现IQuery/IUpdate接口,还实现了IWrapper接口,前者用于组装对象,后者用于读取对象内容,这是一处很有心的设计。Mapper类型中的许多方法都能接收IQuery或IUpdate接口类型的对象,再通过方法上的@InsertProvider、@SelectProvider、@UpdateProvider或@DeleteProvider注解把实际请求转给生成的Provider类型。Provider们从约定的Map参数中取出传入的IWrapper执行对象,使用MapperSql工具类组装SQL语句,最后交给MyBatis执行。

在Mapper里也有一些直接接受Map对象的方法,可以省去用IQuery/IUpdate描述SQL的过程,进行简单的插入和查询。传入的原始Map对象同样会在Provider里被读取出来,用MapperSql组装SQL语句,再交给MyBatis执行。

Fluent MyBatis的这种基于Provider机制的实现方式不仅能为用户提供流畅的SQL构造体验,也能充分复用MyBatis原生的诸多优点,譬如丰富的DB连接器、健全的防SQL注入机制等等,从而确保核心逻辑的稳定可靠。

四 再见XML君

追求卓越是技术人的天性,我来自阿里云·云效产品团队,我们在用Fluent MyBatis。

如果您也早已厌倦MyBatis里毫无生气的XML文件,那么不妨就和它们做个告别吧。

Let's Fluent,加入飞速流动的队伍,一起来感受未来的风潮迎面吹来。

原文链接
本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/513315.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

元宇宙会成为 IPv6 的拐点吗?

‍‍作者 | 马超&#xff0c;王丽丽&#xff0c;王一凡 责编 | 张红月出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;“如无必要&#xff0c;勿增实体”的奥卡姆剃刀原则&#xff0c;从IT人士的角度来看就是“只要能运行&#xff0c;就千万不要改”&#xf…

php网站加广告位,HotNews Pro主题文章内容上面添加广告位

使用的HotNew Pro主题后&#xff0c;文章内容上面没有广告位&#xff0c;但是有时需要在那个位置添加广告&#xff0c;就使用了一款叫Smart Ads广告管理插件&#xff0c;这个插件可以在文章内容上面和下面添加广告&#xff0c;直到昨天我删掉了Smart Ads这款插件&#xff0c;因…

电脑编程教学_东莞沙田mastercam编程学习怎么收费

东莞沙田mastercam编程学习怎么收费深圳卓越培训中心UG综合班主要课程&#xff1a;1&#xff0c;软件介绍&#xff0c;界面熟悉 &#xff0c;快捷键&#xff0c;图层使用。2&#xff0c;草图使用&#xff0c;三维曲线绘制修改&#xff0c;草图线3D线互相转换。3&#xff0c;建模…

arduinowifi.send怎么获取响应_Vue3.0 响应式原理 (一)

前几天&#xff0c;回顾整理下关于vue2.0的响应式原理。温故而知新么&#xff0c;那么今天&#xff0c;整理了一下关于vue3.0的响应式原理&#xff0c;利用 JavaScript 来写的。本着尽可能的清晰易懂的原则&#xff0c;所以&#xff0c;可能会分几篇文章来发布。那现在开始上菜…

OceanBase首次阐述战略:继续坚持自研开放之路 开源300万行核心代码

简介&#xff1a; 在数据库OceanBase3.0峰会上&#xff0c;蚂蚁集团自主研发的分布式数据库OceanBase首次从技术、商业和生态三个维度对未来发展战略进行了系统性阐述。同时&#xff0c;OceanBase宣布正式开源&#xff0c;并成立OceanBase开源社区&#xff0c;社区官网同步上线…

amd核芯显卡控制面板自定义分辨率_主流显卡的一位猛将:蓝宝石Radeon RX 5500XT显卡首测...

一直以来主流级显卡总是处于一个较为尴尬的位置&#xff0c;原因是由于性能的限制&#xff0c;主流显卡经常位于不上不下的局面。上面和电竞级显卡有很大的性能差距&#xff0c;而往下又感受了日新月异的核显的压力。于是很多玩家宁可加钱购买电竞显卡也不愿意购买这些主流显卡…

阿里云研究员叔同:云原生是企业数字创新的最短路径

简介&#xff1a; 今天&#xff0c;数字化成为企业的核心竞争力&#xff0c;千行百业都在拥抱云计算&#xff0c;拥抱云原生。2020年我们认为是云原生的落地元年&#xff0c;那么2021年将是云原生加速推动企业数字创新的关键节点。 作者 | 叔同 来源 | 阿里技术公众号 今天&am…

计算 a+aa+aaa+aaaa+aaaaa+ 的和_海南A级景区,三亚市就有14个,你都去过吗

日前&#xff0c;海南省旅游资源规划开发质量评定委员会发布2020年第2号和2020年第3号公告&#xff0c;海南长影环球100奇幻乐园批准为国家4A级旅游景区&#xff0c;海南霸王岭国家森林公园和桂林洋国家热带农业公园批准为国家3A级旅游景区。具体公告如下&#xff1a;海南省旅游…

如何成为云原生时代的卓越架构师

简介&#xff1a; “软件开发需要面对本质困难和附属困难。云原生、DevOps大幅降低了附属困难&#xff0c;使得架构师可以全力聚焦于业务复杂性&#xff0c;而DDD恰是管理业务复杂性的有效方法。” 本文作者&#xff1a;张刚&#xff0c;阿里云云效资深技术专家&#xff0c;AL…

创业 4 年获近 7000 万美元融资,53 岁老程序员 all in 开源

作者 | 伍杏玲 今年 6 月&#xff0c;《人均估值 5000 万 RMB&#xff0c;53 岁程序员能做到的&#xff0c;你也能&#xff01;》一文刷爆技术人的朋友圈&#xff1a;2017 年成立的涛思数据&#xff0c;四年获近 7000 万美元融资&#xff0c;目前这个 40 人团队估值超 3 亿美元…

.net pdf转图片_PDF转图片怎么做?PDF一键转图片!

在日常工作中&#xff0c;我们经常需要把文件资料传给其他人看。但如果文档是PDF格式的话&#xff0c;很可能他人的设备因缺少相应的阅读工具而无法打开。这时&#xff0c;最好的方法就是将PDF文件转换成图片&#xff01;这样不管是在电脑还是在一些移动设备上都可以查看。接下…

为什么你应该关心领域模型?

简介&#xff1a; 领域模型是DDD的核心&#xff0c;更是业务的深入认知 作者简介&#xff1a;张刚&#xff0c;软件工程博士&#xff0c;阿里云云效资深技术专家&#xff0c;ALPD方法学核心成员。 引言 领域模型是重要的概念。但是&#xff0c;真正了解并能熟练运用它的人并不…

三包围结构的字是什么样的_一年级语文重点(字、字母、字词、词语、句子)知识点汇总!...

一年级语文重点汇总一、字母A B C D E F G H I J K L M N O P Q R S T U V W X Y Za b c d e f g h i j k l m n o p q r s t u v w x y z二、 字1、组词。(形近字和同音字)么(什么) 无(无法) 高(高兴)公(公共) 元(一元…

Java编程技巧之样板代码

简介&#xff1a; 在日常编码的过程中&#xff0c;可以总结出很多“样板代码”&#xff0c;就像”活字印刷术中的“活字”一样。当我们编写新的代码时&#xff0c;需要用到这些“活字”&#xff0c;就把“样板代码”拷贝过来&#xff0c;修改替换一下就可以了&#xff0c;写起代…

CPU 被挖矿,Redis 竟是内鬼!

作者 | 轩辕之风O来源 | 编程技术宇宙却说这一日&#xff0c;Redis正如往常一般工作&#xff0c;不久便收到了一条SAVE命令。虽说这Redis常被用来当做缓存&#xff0c;数据只存在于内存中&#xff0c;却也能通过SAVE命令将内存中的数据保存到磁盘文件中以便持久化存储。只见Red…

vos3000落地网关对接教学_跨国合作:Serverless Components 在腾讯云的落地和实践

导语 | Serverless Components 是 Serverless Framework 推出的最新解决⽅案&#xff0c;具有基础设施编排能⼒&#xff0c;开发者通过使⽤ Serverless Components&#xff0c;可以灵活构建、组合和部署 Serverless 应⽤。本文是对腾讯云云函数团队前端负责人蔡卫峰在云社区沙龙…

Hologres揭秘:深度解析高效率分布式查询引擎

简介&#xff1a; 从阿里集团诞生到云上商业化&#xff0c;随着业务的发展和技术的演进&#xff0c;Hologres也在持续不断优化核心技术竞争力&#xff0c;为了让大家更加了解Hologres&#xff0c;我们计划持续推出Hologers底层技术原理揭秘系列&#xff0c;从高性能存储引擎到高…

电脑两边黑边怎么还原_Mac电脑录制的视频有黑边?如何解决

Mac电脑录制屏幕视频时两边有黑边&#xff0c;无论是将录制格式设置为1080p还是默认分辨率&#xff0c;最终生成的视频两边都有黑边&#xff0c;遇到这种情况如何解决呢&#xff1f;原因是 mac 录制出的视频分辨率比例是 16:10 &#xff0c;比需要的 16:9 高一点。接下来给大家…

程序员有必要参加软考吗?大一可以考的编程证书还有哪些

软考的全称是&#xff1a;计算机技术与软件专业技术资格水平考试。通过考试获得证书的人员&#xff0c;表明其已具备相应等级的水平和能力&#xff0c;用人单位可根据工作需要从获得证书的人员中择优聘任相应专业技术职务。个人认为&#xff0c;程序员有没有必要参与软考最主要…

【知识连载】 如何用钉钉宜搭制定企业疫情防控数字化管理方案

简介&#xff1a; 【零起点入门系列教程】将会带给大家从业务视角出发由浅入深地学习用宜搭实现应用搭建。即便是没有任何代码基础的新手只要跟着系列课程&#xff0c;从0开始慢慢修炼&#xff0c;也能找到成功搭建应用的乐趣。今天第六讲&#xff0c;示例如何用钉钉宜搭搭建企…