大家好,我开发了一个开源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目标不再是孑立目标,而是附带了相关特点storeauthors

经过可选的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->...这样的数据结构。

所以,你无法简略地规定parentchildNodes中,哪个是对外露出的,哪个是对外躲藏的。你无法简略地经过@JsonIgnore注解来处理这个问题,这是一个十分棘手的问题。

2.2. 以MyBatis为代表的DTO Mapper派

经过上文描绘,咱们知道,传统ORM有两个缺陷。

  1. 便于发挥传统ORM才能的主流方法,尽管有灵敏的目标级裁剪才能,但一起也损失了一般特点级的裁剪才能。
  2. ORM回来的实体目标过于杂乱,难以直接回来,无法和HTTP交互。

这两个问题,都是数据目标表达才能弱导致的,其实能够经过界说特定事务所需的DTO类处理。

既然人们注定需求界说特定事务相关的DTO类型,为什么还要编写代码把ORM实体转换为DTO呢?为什么不直接完成从SQL成果到DTO的映射呢?

因而DTO Mapper派被开发人员认同,这个门户提出了截然不同的处理计划。开发人员不再界说和数据库结构直接对应的实体类,而是直接为每个特定事务界说DTO类型,比方:

  • 为表达孑立的Book目标,新建类Book
  • 为表达带相关特点store的Book目标,新建类BookWithStore
  • 为表达带相关特点authors的Book目标,新建类BookWithAuthors
  • 为表达带相关特点storeauthors的Book目标,新建类BookWithStoreAndAuthors

各事务API回来自己需求的DTO目标,每个API都是用特定的SqlResultMapper,把特定的查询成果映射为特定的DTO。

但是,这个做法同样问题严峻

  1. 上面的例子中咱们只展示了目标级的裁剪,并未展示特点级的裁剪,并且目标树的深度也很浅。假如不是这样,DTO类型的数量会激增,乃至能够用爆破来形容。这时,DTO类会多得连取名字都难。开发人员乃至需求结合职业相关的命名约好来防止很长的类名。

  2. DTO太多了,不同的DTO尽管不同,但相同部分也不少,具有高度的冗余。体系损失紧凑性,开发成本和测验成本激增。

  3. 一旦引进新的需求,数据库的结构发生变化,多处冗余的事务都需求修正。

为防止问题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更快捷更有用的多表连接操作。