大家好,我开发了一个开源ORM,整合我多年开发经历于一体,处理体系开发中一直困扰着开发人员的深层次问题。
相关链接:
- 文档: babyfish-ct.github.io/jimmer/zh/
- 视频
- 全面介绍: www.bilibili.com/video/BV1kd…
- 多表连接专题: www.bilibili.com/video/BV19t…
- 功用: babyfish-ct.github.io/jimmer/zh/d…
- 项目: github.com/babyfish-ct…
1. 本文的评论前提
OLTP类型项目很大一部分操作是都针对数据库原始数据,这时软件体系中的目标结构和数据库的中数据结构大体一致,是本文评论的场景。
而因事务核算而引进的核算指标相关的数据类型,和数据库的原始结构并不相同,并非本文的评论范场景。
2. 现有技能门户的缺陷
现在,用户拜访联系型数据库的框架许多,总体上分为两个派别
- 传统ORM派,以JPA, Exposed, Ktorm为代表。
- DTO Mapper派,以MyBatis, JOOQ为代表。
以上两派都有各自的优缺陷,Jimmer完美交融两派之长,走出了截然不同的第三条路。因而并不能比把Jimmer上述两个派中的任何计划做简略对比。
2.1. 以JPA为代表的传统ORM派
在传统ORM中,开发人员创立实体类,和数据库表结构直接对应。从映射的视点讲,十分简略。
传统ORM重视维护目标之间的联系,以JPA为例
List<Book> books = entityManager
.createQuery(
"select book from Book book " +
"left join fetch book.store " +
"left join fetch book.authors"
).getResultList();
这个例子中的join fetch
是JPA的一个特色功用,能够使用SQL JOIN
使回来的Book
目标不再是孑立目标,而是附带了相关特点store
和authors
。
经过可选的join fetch
(或其他技巧,不同的ORM框架手法不尽相同),传统ORM既能够回来孑立的数据目标,也能够回来带相关的杂乱目标,这其实一种对回来数据结构的裁剪才能。
这种裁切才能是以目标为粒度的,但是,回来的数据结构中每个目标都是完整的,也便是说短少一般特点等级的裁剪才能。
无法做到一般特点级的裁剪,当目标特点许多导致查询全部列效率很低,或需求对低权限用户进行重要特点脱敏时,会成为问题。很不幸,实践中的项目便是这样的。
尽管Hibernate从3.x开始,一般(非相关)特点也能够被设置为lazy。但是,这个特性是为lob特点而规划,并非为了完成一般特点级的裁剪而规划,灵敏度十分有限。不予评论
假如想要让传统ORM精确地施行特点级的裁切,会运用这样的代码
List<BookDTO> bookDTOs = entityManager
.createQuery(
"select new BookDTO(book.id, book.name) " +
"from Book book"
).getResultList();
在这个例子中,咱们只想查询id和name特点,为此,不得不构建一个全新的类型BookDTO
用作只有两个特点的残缺目标的载体。在咱们获得一般特点级裁剪才能的一起,因BookDTO
是一个一般目标而非实体目标,损失了目标级的裁剪才能。
也正是由于这种用法损失了ORM的中心才能,在传统ORM中实践中归于非主流用法,很少运用。
传统ORM的另外一个问题是,回来的数据杂乱度很高,难以直接运用。
对于未加载的lazy特点,开发人员很容易在Json序列化中疏忽他们,这不是问题。
真正费事的是目标之间存在双向相关,而前端和微服务客户端更希望看到只有单向相关的目标树。
比方TreeNode实体一起具有向上的parent
特点和向下的childNodes
特点。
- 有些事务能够需求查询某个节点和其全部下级,回来
aggregateRoot->childNodes->childNodes->...
这样的数据结构; - 而有些事务查询某个节点和其全部上级,回来
aggregateRoot->parent->parent->...
这样的数据结构。
所以,你无法简略地规定parent
和childNodes
中,哪个是对外露出的,哪个是对外躲藏的。你无法简略地经过@JsonIgnore
注解来处理这个问题,这是一个十分棘手的问题。
2.2. 以MyBatis为代表的DTO Mapper派
经过上文描绘,咱们知道,传统ORM有两个缺陷。
- 便于发挥传统ORM才能的主流方法,尽管有灵敏的目标级裁剪才能,但一起也损失了一般特点级的裁剪才能。
- ORM回来的实体目标过于杂乱,难以直接回来,无法和HTTP交互。
这两个问题,都是数据目标表达才能弱导致的,其实能够经过界说特定事务所需的DTO类处理。
既然人们注定需求界说特定事务相关的DTO类型,为什么还要编写代码把ORM实体转换为DTO呢?为什么不直接完成从SQL成果到DTO的映射呢?
因而DTO Mapper派被开发人员认同,这个门户提出了截然不同的处理计划。开发人员不再界说和数据库结构直接对应的实体类,而是直接为每个特定事务界说DTO类型,比方:
- 为表达孑立的Book目标,新建类
Book
- 为表达带相关特点
store
的Book目标,新建类BookWithStore
- 为表达带相关特点
authors
的Book目标,新建类BookWithAuthors
- 为表达带相关特点
store
和authors
的Book目标,新建类BookWithStoreAndAuthors
各事务API回来自己需求的DTO目标,每个API都是用特定的SqlResultMapper,把特定的查询成果映射为特定的DTO。
但是,这个做法同样问题严峻
-
上面的例子中咱们只展示了目标级的裁剪,并未展示特点级的裁剪,并且目标树的深度也很浅。假如不是这样,DTO类型的数量会激增,乃至能够用爆破来形容。这时,DTO类会多得连取名字都难。开发人员乃至需求结合职业相关的命名约好来防止很长的类名。
-
DTO太多了,不同的DTO尽管不同,但相同部分也不少,具有高度的冗余。体系损失紧凑性,开发成本和测验成本激增。
-
一旦引进新的需求,数据库的结构发生变化,多处冗余的事务都需求修正。
为防止问题2和3,可对SQL映射片段或事务代码尽可能重用,但这会损坏体系的简略性,代码变得难以了解,这是过度运用低价值复用的必然价值。
3. Jimmer的优势
经过上面的论说,咱们知道
- 传统ORM派:长处是直接和数据存储结构对应,提供一致视角;但缺陷是只对回来数据格式进行目标级裁剪,没有一般特点级的裁剪,并且回来的数据结构难以直接使用。
- DTO Mapper派:长处是查询的到的DTO目标简略,回来的聚合根所代表的数据结构只包含单向相关;但缺陷是DTO类型数量膨胀严峻,虽不同但类似,开发成本和测验成本都很高。
Jimmer完美交融两派之长,走出了截然不同的第三条路。因而并不能比把Jimmer上述两个派中的任何计划做简略对比。
3.1. 无DTO形式:动态实体
在Jimmer中
-
实体目标是动态的,任何目标特点,无论是一般特点还是相关特点,都能够缺失。
对Jimmer的实体目标而言,不指定某个特点和把某个特点指定为null,完全是两码事。
-
在Java或Kotlin代码中直接读取目标的缺失特点会导致反常;但是,在JSON序列化时,缺失特点会被主动疏忽,不会反常。
-
尽管声明实体类型时,不同类型之间能够界说双向相关;但是,某个详细事务需求实例化目标时,实体目标之间只能树立单向相关,确保任何数据结构都能用一个简略的聚合根目标来表达。
动态实体本身不是DTO,但它具有DTO目标的全部特质,无DTO胜似DTO,任何实体目标树都能够直接参与HTTP交互。
动态实体是整个ORM的架构根底。
3.2. 查询任意杂乱的数据结构
完美支撑目标等级和特点等级的目标形状裁切才能,用户能够从完整的联系模型中圈定出一个局部数据结构,即一个任意杂乱的树结构,以回来动态实体树的方式,查询整个数据结构。
让RDBMS具有类似于GraphQL功用。即便你的项目和GraphQL技能毫无联系,你的RDMBS也拥有它的全部优势。
Jimmer比GraphQL做得更好,它乃至支撑自相关特点的递归查询。
3.3. 修正任意杂乱的数据结构
用户能够向Jimmer传递任意杂乱的动态目标树,将整棵树作为一个整体用一句话保存。
能够了解成GraphQL的逆功用。
3.3. 强大的缓存机制
- 对用户的缓存技能选型不做任何约束,用户能够选用任何缓存技能。
- 内部支撑目标缓存和相关缓存,在杂乱数据结构查询中,二者在暗地按需有机结合。终究给用户呈现出的效果,便是任意杂乱数据结构的缓存,而非简略目标的缓存。
- 主动确保缓存的数据一致性,只要在接受到数据库binlog推送后简略调用Jimmer的API即可。
- 缓存机制对开发人员100%透明,是否采用缓存,对事务代码没有任何影响。
尽管RDBMS具有无以伦比的表达才能,但它有一个明显的缺陷:按联系导航追寻其它数据,功用不抱负。
相关缓存能够在很大程度上缓解这个问题,让RDBMS如虎添翼。
3.4 比原生SQL更有用的强类型SQL DSL
- 在编译时发现拼写过错和类型匹配过错。
- 强类型SQL DSL能够原生SQL表达式随意混合,在一致和抽象不同的数据库的一起,允许发挥特定数据库产品独有的才能。
- 以抛弃实践项目中几乎不可能被用到的单个SQL写法为价值,提供比原生SQL更快捷更有用的多表连接操作。