购物网站的后台,图书馆网站建设费用,全屏网站,专业做网站建设的公司概览
Jimmer是一个Java/Kotlin双语框架 包含一个革命性的ORM 以此ORM为基础打造了一套综合性方案解决方案#xff0c;包括 DTO语言 更全面更强大的缓存机制#xff0c;以及高度自动化的缓存一致性 更强大客户端文档和代码生成能力#xff0c;包括Jimmer独创的远程异常 …概览
Jimmer是一个Java/Kotlin双语框架 包含一个革命性的ORM 以此ORM为基础打造了一套综合性方案解决方案包括 DTO语言 更全面更强大的缓存机制以及高度自动化的缓存一致性 更强大客户端文档和代码生成能力包括Jimmer独创的远程异常 快速创建GraphQL服务 跨越微服务的远程实体关联
ORM部分
当前技术生态下访问关系型数据库技术体系存在很大缺陷请看下图。
1. 以JPA为代表的静态语言ORM
优点
便捷代码安全(本身基于强类型语言大部分代码是安全的。如果结合QueryDSL使用则可以保证所有代码都是安全的)
缺点
缺乏灵活性
即使JPA从2.1开始支持EntityGraph控制被查询数据格式的灵活性仍然非常有限。该方案粒度仍然太粗控制能力远没GraphQL这类技术的细腻。
保存对象时细节行为受普通属性的insertable、updateable和关联属性的cascade配置的控制这类配置在实体类型中被硬编码固化被保存的数据结构的格式是固定的没有灵活性。
如果要发挥ORM的优势就必须查询对象的大部分非关联属性 (少数Basic(fetch FetchType.Lazy)属性除外它们多为lob设计)如果只想查询一部分属性就必须放弃对象查询转而使用这些属性的多列查询丧失ORM本该有的便捷性和核心价值。
3. 以为ActiveRecord (Ruby) 为代表的动态语言ORM
优点
基于动态语言的ORM只需将动态语言对象结构的灵活性和ORM的实现结合起来就能兼顾便捷和灵活。
缺点
动态语言虽然既便捷又灵活但是代码缺乏可维护性且不利于多人协同开发是众所周知的缺点。
现代软件往往是复杂的需要团队协作来完成是否利于团队成员之间协同远比个人对编程的认知重要。
这里不想过多地讨论动静之争但是有一点需要指出既然选择了静态语言Java/Kotlin就应该以静态语言的方式使用它 而不能使用以jFinal为代表的将静态语言当成动态语言用的方案更不能在应用中频繁地使用java.util.Map来代替数据对象。 这类做法违背了选择Java/Kotlin的初衷如果一定要怎么做为什么不直接选动态语言呢
4. 以MyBatis为代表的轻量级SQL Builder/Mapper
优点
直接编写SQL随意且灵活本身是强类型框架具有代码安全性 (MyBatis生态也有强类型SQL DSL扩展可以解决原生SQL字符串导致的代码不安全问题)
缺点
便捷性的严重缺失重复劳动量极大。
MyBatis没有统一实体的概念而是面对具体业务场景DTO实现ResultSet和这些DTO的映射。由于业务场景多各DTO类型之间相似却不同冗余度很高导致重复劳动量极高。
除了以孤单对象为载体的CRUD外对多个对象彼此关联而成的复杂数据结构的支持较弱缺乏必要抽象导致太多繁重的低级任务被推卸给开发人员 (不少开发人员长期被这类繁重的任务所累但自己一直没察觉)。
原生SQL真的是最好方案吗 这个派别最引以为豪的观点是“直接书写SQL会带来更直接的控制力这种直接控制力优于任何ORM”。在这个领域长期的技术停滞中不少开发人员对此深信不疑。
根本原因
上文中我们阐述了关系型数据库领域的三种常见方案但无论如何选择我们都无法兼顾便捷性、灵活性和代码安全性。为什么会导致这样呢 就JVM生态而言POJO是导致这个问题的根本原因。 POJO*(也可以叫结构体)*缺乏必要的灵活性和表达力却几乎被所有的JVM框架作为数据模型和核心严重限制了JVM生态的技术创新。 因此在Jimmer中ORM实体对象并非POJO。而是另外一种独特的万能数据对象*(后文会介绍)*这种独特的实体对象撑起了Jimmer所有上层重大的变革是整个框架的基石。 事实上Jimmer实体对象不仅可以应用在ORM领域它几乎可以用在任何以结构化数据维护为目的的场景并提升各种技术栈的表达力。 目前Jimmer实体仅在关系型数据库访问领域发挥出作用只是因为精力不够所致。 完整的功能
在本文开头我们提到了革命性的ORM只是Jimmer的一部分Jimmer实际的能力范围早已超越了一个ORM。
现在我们给出Jimmer的功能示意图并逐个讲解
Business Model
在信息类系统中存在两种对象。
实体实体对象是全局统一的对象之间的存在丰富彼此关联。
实体对象往往和数据库非常接近具备极高的稳定性。
DTO针对特定业务的输入/输出对象通常是从全局实体关系网上撕下来的一个局部碎片该碎片的大小和形状非常灵活。
DTO类型数量庞大每一个业务接口对DTO对象的格式都有独特的需求彼此可能相似但又不同具备明显的。而且易受到需求变化的影响不稳定。 Entity类型是全局统一数据存储模型不易被需求变更影响相对稳定被视为高价值类型。 DTO类型作为每个业务输入/输出相对随意容易因需求变动而不稳定被视为低价值类型。 Jimmer主张开发人员把精力集中在高价值的实体模式的设计上对于低价值的DTO类型有的时候并不需要有的时候需要。 即使需要也可以用极其廉价的方式自动生成。因此基于Jimmer构建的项目具备优秀的抗需求变动的能力。 Jimmer Entity
Jimmer实体定义和JPA实体很接近。
之前讨论过Jimmer实体并非POJO所以被声明为interface而非class。
那么谁负责实现此接口呢是上图中的Jimmer Precompiler (对于Java而言就是APT 对于Kotlin而言就是KSP)
Jimmer实体支持两个重要特征动态性和不可变性
动态性
Jimmer对象在静态语言和动态语言之间寻求最佳平衡把二者的优点结合起来
静态语言数据对象具有高性能、拼写安全、类型安全、甚至空安全*(如果使用Kotlin的话)*的优点Jimmer实体吸收了这些优点。动态语言数据对象具有高度的灵活性Jimmer实体吸收了这个优点每个属性都可以缺失*(但是不能如同动态语言一样增加属性因为这必然会破坏静态语言的特性Jimmer也不需要此能力)*
对Jimmer而言对象缺少某个属性 (其值未知) 和 对象的某个属性为null (其值已知) 是完全不同的两回事。 这种平衡设计可以在享受静态语言好处的同时为数据结构赋予。 这种绝对的灵活性既可用于表达查询业务的输出格式也可用于表达保存业务的输入格式。 这导致Jimmer拥有了崭新的定位一个为任意形状数据结构设计的ORM。其所有功能都是为了操作任意形状的数据结构而非一个个简单的实体对象。 不可变性
Jimmer对象是不可变对象。不可变对象的好处是多方面的 Jimmer选择不可变对象是为了让数据结构绝不包含循环引用。 这可以保证由Jimmer实体及彼此关联组合而成的数据结构一定能够被直接Jackson序列化并不需要使用诡异的序列化技巧为JSON植入任何特殊的额外信息任何编程语言都可以轻松理解。 然而不可变对象也存在缺点。比如现有一个很深的数据结构那么基于它按照一些修改的愿望创建出新的数据结构会很困难难度随着深度的变大急剧增加。
ORM和很深的数据结构打交道而Java的record和Kotlin的data class不适合处理很深数据结构。
既对Java和Kotlin进行双语支持又善于基于现有深层次数据结构按照一些修改的愿望创建出新的不可变数据结构的方案目前的JVM生态没有。
幸运的是JavaScript/TypeScript领域存在一个足够强大的方案: immer可以完美解决这个问题。该方案工作方式如下
基于现有不可变数据结构开启一个临时作用域。
在这个作用域内开发人员可得到一个draft数据结构该数据结构的形状和初始值和原数据结构完全一致且可以被随意修改包括修改任意深的子对象。
作用域结束后draft数据结构会利用收集到的修改行为创建另外一个新的数据结构。其中未被修改的局部会被优化处理复用以前的旧对象。
Immer完美结合了不可变对象和可变对象的优点代码简单、功能强大、性能卓越。因此Jimmer选择为JVM生态移植了immer项目名称也是对其致敬。
Generated DTO Type
前文谈到Jimmer实体在静态语言数据对象和动态语言数据对象之间寻找最佳平衡其中动态性带来了极大的灵活性并以此决定了整个框架的定位。
Jimmer对象允许某些属性缺失对象缺少某个属性 (其值未知) 和 对象的某个属性为null (其值已知) 是完全不同的两回事。 对于Jackson序列化而言缺失的属性会被自动忽略就如同我们之前展示的那样。 如果服务端自己并不使用查询得到的实体对象而是直接写入到Http Response中。对于这种情况无需DTO直接使用实体对象很方便。 如果直接用Java/Kotlin代码访问不存在的属性会导致异常。
这并非由Jimmer制造的新问题而是一个在静态语言ORM生态中早已存在和被接受的问题。然而不可否认这的确对静态语言的安全性形成了破坏。
如果要追求100%的静态语言安全性使用DTO对象是唯一的方法。然而目前JVM生态的DTO映射技术存在很大缺陷。
要么显式地映射属性*(例如纯手工映射和转化)*这种做法工作量巨大枯燥且容易出错。要么隐式地映射属性*(例如采用BeanUtils技术)*这种做法会引入新的不安全问题即无法在编译发现的问题。
即使你使用强大的mapstruct你所能做的也只是在这两个极端之间作出选择而已。
因此Jimmer提供了DTO语言用户使用该语言编写非常简单的代码编译项目即可自动生成各种丰富的DTO类型定义。 DTO语言的设计目的在于 让生成DTO类型的过程足够简单从而让DTO类型足够廉价。 100%符合静态语言安全性在编译时发现所有问题并报错。 理论概念先到这里
简单使用
我们做一个简单的查询demo创建Springboot项目
引入依赖
dependencygroupIdorg.babyfish.jimmer/groupIdartifactIdjimmer-spring-boot-starter/artifactIdversion0.8.51/version/dependency编写Model
用户
Entity
Table(name User)
public interface User {IdGeneratedValue(strategy GenerationType.IDENTITY)int id();String name();NullableInteger age();OneToMany(mappedBy user)ListUserDetail details();
}用户详情一对多
Entity
Table(name user_detail)
public interface UserDetail {IdGeneratedValue(strategy GenerationType.IDENTITY)int id();Key // 自己的核心数据自然就是第二个业务键String detail();Key // 父级自然是一个业务键OnDissociate(DissociateAction.DELETE) // 如果脱钩了就把自身删除ManyToOneJoinColumn(name user_id,foreignKeyType ForeignKeyType.FAKE)NullableUser user();IdView(user)Integer userId();}配置数据库链接
applicantion.yml
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://myhost:3306/my_jimmerusername: rootpassword: rootjimmer:dialect: org.babyfish.jimmer.sql.dialect.MySqlDialectshow-sql: onpretty-sql: truedatabase-validation:schema: my_jimmer构建
Maven build
查询
RestController
RequestMapping(/test)
public class TestController {Autowiredprivate JSqlClient sqlClient;RequestMapping(/user)public ListUser find(RequestBody UserSpecification specification){UserTable userTable UserTable.$;return sqlClient.createQuery(userTable).select(userTable).execute();}
}超级查询
使用specification可以提供灵活的复杂查询
定义dto
export com.example.myjimmer.entity.User- package com.example.myjimmer.dto/*UserView {#allScalars(User)details {#allScalars(UserDetail)}
}*/specification UserSpecification {eq(name) as namelike(name) as likename
}构建
Maven build
使用specification查询
RestController
RequestMapping(/test)
public class TestController {Autowiredprivate JSqlClient sqlClient;RequestMapping(/user)public ListUser find(RequestBody UserSpecification specification){UserTable userTable UserTable.$;return sqlClient.createQuery(userTable).where(specification).select(userTable).execute();}
}