Java革命性ORM框架Jimmer简单介绍

news/2024/12/13 0:40:15/

首发于Enaium的个人博客


本文使用Jimmer的官方用例来介绍Jimmer的使用方法,Jimmer同时支持JavaKotlin,本文使用Java来介绍,实际上KotlinJava使用起来更方便,这里为了方便大家理解,使用Java来介绍,本篇文章只是对Jimmer的一个简单介绍,更多的内容请参考官方文档

这里开始就不从实体类开始介绍了,这里简单的把用到的三张表之间的关系介绍一下:

  • BookStore书店 可以拥有多个Book
  • Book书 可以属于多个BookStore,可以有多个Author
  • Author作者 可以拥有多个Book,多对多书与作者的关系.

查询

Jimmer可以配合SpringData(不是SpringDataJPA),但这里先介绍脱离SpringData的使用方法,但还是在SpringBoot环境下,这里使用H2内存数据库,Jimmer支持H2,MySQL,PostgreSQL,Oracle等数据库,这里使用H2内存数据库.

这里的查询都使用Controller来演示.

查询所有书店

createQuery就是创建一个查询,select就是选择要查询的字段,这里直接传入了BookStoreTable表示查询所有字段.

这里用到的sql就是使用JimmerSql对象,这个对象是Jimmer的核心对象,所有的查询都是通过这个对象来实现的,使用Spring的注入方式注入JSqlClient对象.

final BookStoreTable bookStore = BookStoreTable.$;//这里的$是一个静态方法,返回一个BookStoreTable对象
sql.createQuery(bookStore).select(bookStore).execute();

查询结果如下:

[{"createdTime": "2023-05-27 11:00:37","modifiedTime": "2023-05-27 11:00:37","id": 1,"name": "O'REILLY","website": null},{"createdTime": "2023-05-27 11:00:37","modifiedTime": "2023-05-27 11:00:37","id": 2,"name": "MANNING","website": null}
]

指定查询字段

如何需要需要查询指定字段就可以这样,这里的nameBookStoreTable的一个字段,但这里的Controller返回的是BookStore对象,所以只好像上面的那样查询所有字段.

sql.createQuery(bookStore).select(bookStore.name()).execute();

像上面的例子中如果我们非要查询指定字段又不想定义新的DTO对象,那么这种在Jimmer中也可以非常简单的实现,那就是使用Jimmer中的Fetchr

使用BookStoreFetchr来指定查询的字段

sql.createQuery(bookStore).select(bookStore.fetch(BookStoreFetcher.$.name())).execute();

查询结果如下:

[{"id": 2,"name": "MANNING"},{"id": 1,"name": "O'REILLY"}
]

惊奇的发现,Controller的返回类型是BookStore,但是查询结果中只有idname字段.

这里我把完整的Controller代码贴出来,List的类型就是BookStore的实体类,这就是Jimmer的强大之处,不需要定义DTO对象,就可以实现查询指定字段的功能.

@GetMapping("/simpleList")
public List<BookStore> findSimpleStores() {final BookStoreTable bookStore = BookStoreTable.$;//这里的$是一个静态方法,返回一个BookStoreTable对象return sql.createQuery(bookStore).select(bookStore.fetch(BookStoreFetcher.$.name())).execute();
}

和实体类的Table一样,Fetcher也可以声明一个静态常量.

private static final Fetcher<BookStore> SIMPLE_FETCHER = BookStoreFetcher.$.name();

这样就可以这样使用了.

sql.createQuery(bookStore).select(bookStore.fetch(SIMPLE_FETCHER)).execute();

接下来详细介绍Fetcher的使用

查询所有标量字段,也就是非关联字段.

private static final Fetcher<BookStore> DEFAULT_FETCHER = BookStoreFetcher.$.allScalarFields();//这里的allScalarFields()就是查询所有标量字段

在查询所有标量字段的基础上不查询BookStorename字段

private static final Fetcher<BookStore> DEFAULT_FETCHER = BookStoreFetcher.$.allScalarFields().name(false);//这里的name(false)就是不查询name字段

指定查询关联字段

像这样查询所有书店的所有书籍,并且查询书籍的所有作者,这样就可以使用Fetcher来实现,如果在使用传统ORM框架时,这里就需要定义一个DTO对象来接收查询结果,但是在Jimmer中,不需要定义DTO对象,就可以实现查询指定字段的功能,可能有读者会问了,没有DTO前端怎么接收数据呢,这里先剧透一下,Jimmer会根据后端写的Fetcher来生成前端的DTO,这里就不多说了,后面会详细介绍.

private static final Fetcher<BookStore> WITH_ALL_BOOKS_FETCHER =BookStoreFetcher.$.allScalarFields()//查询所有标量字段.books(//查询关联字段BookFetcher.$//书籍的Fetcher.allScalarFields()//查询所有标量字段.authors(//查询关联字段AuthorFetcher.$//作者的Fetcher.allScalarFields()//查询所有标量字段));

稍剧透一点,这里如果使用Kotlin来编写会更加简洁,因为Kotlin中的DSL特性

private val WITH_ALL_BOOKS_FETCHER = newFetcher(BookStore::class).by {allScalarFields()//查询所有标量字段books {//查询关联字段allScalarFields()//查询所有标量字段authors {//查询关联字段allScalarFields()//查询所有标量字段}}}

这么一看Kotlin确实比Java简洁很多,但本篇文章还是介绍的是Java的使用方法.

指定查询条件和计算结果字段

如果需要查询书店中所有书籍的平均价格,那么就要查询书店中所有书籍的价格,然后计算平均值,这里先把查询的代码写出来,然后在介绍如何把计算结果字段添加到Fetcher中.

sql.createQuery(bookStore)//这里的bookStore是一个BookStoreTable对象.where(bookStore.id().in(ids))//要查询的书店的id集合,也可以直接指定id,比如.eq(1L).groupBy(bookStore.id())//按照书店的id分组.select(bookStore.id(),//查询书店的idbookStore.asTableEx().books(JoinType.LEFT).price().avg().coalesce(BigDecimal.ZERO)//查询书店中所有书籍的平均价格).execute();//这样执行查询后,返回的结果就是书店的id和书店中所有书籍的平均价格,在Jimmer中会返回一个List<Tuple2<...>>类型的结果,其中Tuple元组的数量和查询的字段数量一致,这里就是2个字段,所以就是Tuple2

这里最后的select是查出了书店的 id 和书店中所有书籍的平均价格,asTableEx()是为了突破Jimmer的限制,Jimmer中的Table只能查询标量字段,而不能查询关联字段,这里的asTableEx()就是为了查询关联字段,asTableEx()的参数是JoinType,这里的JoinTypeLEFT,表示左连接,如果不指定JoinType,默认是INNER,表示内连接.

这里的avg()是计算平均值的意思,coalesce(BigDecimal.ZERO)是为了防止计算结果为null,如果计算结果为null,那么就返回BigDecimal.ZERO.

这里介绍如何把计算结果字段添加到Fetcher中,这样就又引出了一个Jimmer的功能计算属性

计算属性

Jimmer中如果要添加计算属性,那么就要实现TransientResolver接口,这里先把代码贴出来,然后再详细介绍.

@Component
public class BookStoreAvgPriceResolver implements TransientResolver<Long, BigDecimal> {@Overridepublic Map<Long, BigDecimal> resolve(Collection<Long> ids) {return null;}
}

这里的ids就是书店的 id 集合,这里的resolve方法就是计算书店中所有书籍的平均价格,这里的Long是书店的 id,BigDecimal是书店中所有书籍的平均价格,这里的resolve方法返回的Mapkey就是书店的 id,value就是书店中所有书籍的平均价格.

接着配合上面写的查询代码,完成计算的代码

BookStoreTable bookStore = BookStoreTable.$;
return sql.createQuery(bookStore).where(bookStore.id().in(ids)).groupBy(bookStore.id()).select(bookStore.id(),bookStore.asTableEx().books(JoinType.LEFT).price().avg().coalesce(BigDecimal.ZERO)).execute()//这里的execute()返回的结果是List<Tuple2<Long, BigDecimal>>类型的.stream()//这里把List转换成Stream.collect(Collectors.toMap(Tuple2::get_1, Tuple2::get_2)//这里把List<Tuple2<Long, BigDecimal>>转换成Map<Long, BigDecimal>);

这样一个TransientResolver的实现就完成了,接着就是把TransientResolver添加到实体类中

Jimmer中定义实体类是在接口中定义的

@Transient(BookStoreAvgPriceResolver.class)//这里的BookStoreAvgPriceResolver.class就是上面写的计算属性的实现
BigDecimal avgPrice();//这里的avgPrice()就是计算属性,这里的BigDecimal就是计算属性的类型

这样就可以直接在Fetcher中查询计算属性了

private static final Fetcher<BookStore> WITH_ALL_BOOKS_FETCHER =BookStoreFetcher.$.allScalarFields().avgPrice()//这里就是查询计算属性//...省略

接着看戏生成的SQL代码和查询结果,这里照样省略其他查询只关注标量字段和计算属性

selecttb_1_.ID,coalesce(avg(tb_2_.PRICE), ? /* 0 */)
from BOOK_STORE tb_1_
left join BOOK tb_2_on tb_1_.ID = tb_2_.STORE_ID
wheretb_1_.ID in (? /* 1 */)
group bytb_1_.ID
{"createdTime": "2023-05-27 12:04:39","modifiedTime": "2023-05-27 12:04:39","id": 1,"name": "O'REILLY","website": null,"avgPrice": 58.5
}

定义实体类

Jimmer中定义实体类是在接口中定义的,这里先把代码贴出来,然后再详细介绍.

BookStore

@Entity//这里的@Entity就是实体类
public interface BookStore extends BaseEntity {@Id//这里的@Id就是主键@GeneratedValue(strategy = GenerationType.IDENTITY)//这里的strategy = GenerationType.IDENTITY就是自增长long id();//这里的id()就是实体类的id@KeyString name();//业务主键@Null//这里的@Null就是可以为null,建议使用Jetbrains的@NullableString website();@OneToMany(mappedBy = "store", orderedProps = {@OrderedProp("name"),@OrderedProp(value = "edition", desc = true)})//这里的@OneToMany就是一对多,这里的mappedBy = "store"就是Book中的store字段,这里的orderedProps就是排序字段List<Book> books();@Transient(BookStoreAvgPriceResolver.class)//这里的BookStoreAvgPriceResolver.class就是上面写的计算属性的实现BigDecimal avgPrice();//这里的avgPrice()就是计算属性,这里的BigDecimal就是计算属性的类型
}

Book

@Entity
public interface Book extends TenantAware {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)long id();@Key//这里的@Key就是业务主键String name();@Key//和上面的name()一样,这里的@Key就是业务主键,表示name和edition的组合是唯一的int edition();BigDecimal price();@Nullable@ManyToOneBookStore store();@ManyToMany(orderedProps = {@OrderedProp("firstName"),@OrderedProp("lastName")})//这里的@ManyToMany就是多对多,这里的orderedProps就是排序字段@JoinTable(name = "BOOK_AUTHOR_MAPPING",//这里的name就是中间表的表名joinColumnName = "BOOK_ID",//这里的joinColumnName就是中间表的外键inverseJoinColumnName = "AUTHOR_ID"//这里的inverseJoinColumnName就是中间表的外键)List<Author> authors();
}

Author

@Entity
public interface Author extends BaseEntity {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)long id();@KeyString firstName();@KeyString lastName();Gender gender();//这里的Gender就是枚举类型@ManyToMany(mappedBy = "authors", orderedProps = {@OrderedProp("name"),@OrderedProp(value = "edition", desc = true)})//这里的@ManyToMany就是多对多,这里的mappedBy = "authors"就是Book中的authors字段,这里的orderedProps就是排序字段List<Book> books();
}
public enum Gender {@EnumItem(name = "M")//这里的name表示在数据库中存储的值MALE,@EnumItem(name = "F")FEMALE
}

如果使用过Spring Data JPA的话,这里的代码应该很熟悉,Jimmer中的实体类的关联关系和Spring Data JPA中的关联关系是一样的.

生成前端代码

还记得前面的剧透吗,现在开始正式介绍如何生成前端代码,这里先把生成的代码贴出来,然后再详细介绍.

DTO

这里生成了好多根据Controller的返回类型的Fetcher生成的DTO,这里就不贴出来了,只贴一个BookStoreDto的代码.

export type BookStoreDto = {//只有查询书店的name"BookStoreService/SIMPLE_FETCHER": {readonly id: numberreadonly name: string}//查询书店的所有字段"BookStoreService/DEFAULT_FETCHER": {readonly id: numberreadonly createdTime: stringreadonly modifiedTime: stringreadonly name: stringreadonly website?: string}//查询书店的所有字段和书店中所有书籍的所有字段还有书籍的所有作者的所有字段"BookStoreService/WITH_ALL_BOOKS_FETCHER": {readonly id: numberreadonly createdTime: stringreadonly modifiedTime: stringreadonly name: stringreadonly website?: stringreadonly avgPrice: number //这里的avgPrice就是计算属性readonly books: ReadonlyArray<{readonly id: numberreadonly createdTime: stringreadonly modifiedTime: stringreadonly name: stringreadonly edition: numberreadonly price: numberreadonly authors: ReadonlyArray<{readonly id: numberreadonly createdTime: stringreadonly modifiedTime: stringreadonly firstName: stringreadonly lastName: stringreadonly gender: Gender}>}>}
}

Controller

这里只看BookStoreController的主要请求

这里Jimmer把所有的Controller的请求都放在了一个Controller中,这里的Controller就是BookStoreController,这里的BookStoreController就是BookStore实体类的Controller,这里的BookStoreController的代码如下

async findComplexStoreWithAllBooks(options: BookStoreServiceOptions['findComplexStoreWithAllBooks']): Promise<BookStoreDto['BookStoreService/WITH_ALL_BOOKS_FETCHER'] | undefined
> {let _uri = '/bookStore/';_uri += encodeURIComponent(options.id);_uri += '/withAllBooks';return (await this.executor({uri: _uri, method: 'GET'})) as BookStoreDto['BookStoreService/WITH_ALL_BOOKS_FETCHER'] | undefined
}async findSimpleStores(): Promise<ReadonlyArray<BookStoreDto['BookStoreService/SIMPLE_FETCHER']>
> {let _uri = '/bookStore/simpleList';return (await this.executor({uri: _uri, method: 'GET'})) as ReadonlyArray<BookStoreDto['BookStoreService/SIMPLE_FETCHER']>
}
async findStores(): Promise<ReadonlyArray<BookStoreDto['BookStoreService/DEFAULT_FETCHER']>
> {let _uri = '/bookStore/list';return (await this.executor({uri: _uri, method: 'GET'})) as ReadonlyArray<BookStoreDto['BookStoreService/DEFAULT_FETCHER']>
}

配置代码生成

需要再配置中指定生成代码的访问地址,因为Jimmer生成的前端代码是一个压缩包,访问这个地址就可以下载生成的源码了

jimmer:client:ts:path: /ts.zip #这里的path就是访问地址

接着配置Controller的返回类型

@GetMapping("/simpleList")
public List<@FetchBy("SIMPLE_FETCHER") BookStore> findSimpleStores() {final BookStoreTable bookStore = BookStoreTable.$;return sql.createQuery(bookStore).select(bookStore.fetch(SIMPLE_FETCHER)).execute();
}

这里使用了FetchBy注解,其中的值就是当前类的Fetcher常量,如果Fetcher不在当前的类下,可以指定注解中的ownerType来指定Fetcher所在的类.

好了,Jimmer的基本使用就介绍完了,如果想了解更多的使用方法,可以查看Jimmer的文档,也可以观看Jimmer作者录制的视频教程Jimmer0.6x: 前后端免对接+spring starter,让REST媲美GraphQL,Jimmer-0.7之计算属性


http://www.ppmy.cn/news/109847.html

相关文章

代码随想录算法训练营第四十二天|0/1背包问题理论基础 416. 分割等和子集

目录 0/1背包问题理论基础 二维dp数组 一维dp数组 LeeCode 416. 分割等和子集 0/1背包问题理论基础 二维dp数组 动规五部曲 1.确定dp数组及下标含义: dp[i][j] : 从下标为[0-i]的物品里任意取&#xff0c;放进容量为j的背包&#xff0c;价值总和的最大值&#xff1b; 2…

Flutter问题记录 - TextField组件多行提示文本显示不全

文章目录 前言开发环境问题描述问题分析解决方案最后 前言 梳理Flutter项目的过程中发现还有一些遗留的TODO没处理&#xff0c;其中有一个和TextField组件相关。 开发环境 Flutter: 3.10.1Dart: 3.0.1 问题描述 TextField组件设置maxLines: null不限制行数&#xff0c;同时…

elementUI中<el-select>下拉框选项过多的页面优化方案——多列选择

效果展示(多列可以配置) 一、icon下拉框的多列选择&#xff1a; 二、常规、通用下拉框的多列选择&#xff1a; 【注】第二种常规、通用下拉框的多列选择&#xff0c;是在第一种的前端代码上删除几行代码就行&#xff08;把icon显示标签删去&#xff09;&#xff0c;所以下面着重…

root 密码破解(rd.break)

在Linux系统中&#xff0c;忘记root密码时&#xff0c;可以用此方法进行暴力修改root密码 示例&#xff1a; 设置一个新的记不住的密码 $ echo cnakdnvf | passwd --stdin root $ poweroff 1.启动此虚拟机&#xff0c;选中以下行&#xff0c;并按 【 e 】进入内核编辑页面 …

数据库服务器

数据库服务器&#xff0c;联系Web服务器与DBMS的中间件是负责处理所有的应用程序服务器&#xff0c;包括在web服务器和后台的应用程序或数据库之间的事务处理和数据访问。 基本信息 中文名 数据库服务器 外文名 database server 功能 数据库服务器建立在数据库系统基础上&a…

pycharm内置Git操作失败的原因

文章目录 问题简介解决方案DNS缓存机制知识的自我理解 问题简介 最近在pycharm中进行代码改动递交的时候&#xff0c;总是出现了连接超时或者推送被rejected的情况&#xff0c;本以为是开了代理导致的&#xff0c;但是关闭后还是推送失败&#xff0c;于是上网查了以后&#xf…

微信小程序授权登录

微信小程序—授权登录 一、小程序登录 登录流程时序 说明: 1.小程序端调用 wx.login() 获取临时登录凭证code &#xff0c;并回传到开发者服务器。 2.服务器调用 code2Session 接口&#xff0c;换取 用户唯一标识 OpenID 和 会话密钥 session_key。 之后开发者服务器可以根…

【C++入门】什么是内联函数?

目录 一、概念 为什么要有内联函数&#xff1f; 内联函数设计的初衷是为了替代部分 #define 宏定义 二、特性 1.空间换时间 2.编译器做主 3.声明定义放一起 总结 一、概念 以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用函数的地方展开&#xff0c;没有…