您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

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

时间:2023-05-28 17:30:45  来源:  作者:Java架构学习指南

本文使用Jimmer的官方用例来介绍Jimmer的使用方法,Jimmer同时支持JAVA和Kotlin,本文使用Java来介绍,实际上Kotlin比Java使用起来更方便,这里为了方便大家理解,使用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就是使用Jimmer的Sql对象,这个对象是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
  }
]

指定查询字段

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

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

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

使用BookStore的Fetchr来指定查询的字段

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

查询结果如下:

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

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

这里我把完整的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()就是查询所有标量字段

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

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(),//查询书店的id
            bookStore.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,这里的JoinType是LEFT,表示左连接,如果不指定JoinType,默认是INNER,表示内连接.

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

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

计算属性

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

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

这里的ids就是书店的 id 集合,这里的resolve方法就是计算书店中所有书籍的平均价格,这里的Long是书店的 id,BigDecimal是书店中所有书籍的平均价格,这里的resolve方法返回的Map的key就是书店的 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代码和查询结果,这里照样省略其他查询只关注标量字段和计算属性

select
    tb_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
where
    tb_1_.ID in (
        ? /* 1 */
    )
group by
    tb_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

    @Key
    String name();//业务主键

    @Null//这里的@Null就是可以为null,建议使用JetbrAIns的@Nullable
    String website();

    @.NEToMany(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
    @ManyToOne
    BookStore 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();

    @Key
    String firstName();

    @Key
    String 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: number
    readonly name: string
  }
  //查询书店的所有字段
  "BookStoreService/DEFAULT_FETCHER": {
    readonly id: number
    readonly createdTime: string
    readonly modifiedTime: string
    readonly name: string
    readonly website?: string
  }
  //查询书店的所有字段和书店中所有书籍的所有字段还有书籍的所有作者的所有字段
  "BookStoreService/WITH_ALL_BOOKS_FETCHER": {
    readonly id: number
    readonly createdTime: string
    readonly modifiedTime: string
    readonly name: string
    readonly website?: string
    readonly avgPrice: number //这里的avgPrice就是计算属性
    readonly books: ReadonlyArray<{
      readonly id: number
      readonly createdTime: string
      readonly modifiedTime: string
      readonly name: string
      readonly edition: number
      readonly price: number
      readonly authors: ReadonlyArray<{
        readonly id: number
        readonly createdTime: string
        readonly modifiedTime: string
        readonly firstName: string
        readonly lastName: string
        readonly 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所在的类.



Tags:框架J   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Java革命性ORM框架Jimmer简单介绍
本文使用Jimmer的官方用例来介绍Jimmer的使用方法,Jimmer同时支持Java和Kotlin,本文使用Java来介绍,实际上Kotlin比Java使用起来更方便,这里为了方便大家理解,使用Java来介...【详细内容】
2023-05-28  Search: 框架J  点击:(318)  评论:(0)  加入收藏
Spring Boot与JAX-RS框架Jersey的完美搭配
编辑推荐:本文来自于oschina,本文主要介绍了一个在 spring boot 项目添加Jeresy框架的详细过程,希望对大家能有所帮助。Jeresy是一个轻量级的JAX-RS框架添加Jeresy 2.x的依赖c...【详细内容】
2022-06-05  Search: 框架J  点击:(356)  评论:(0)  加入收藏
斗鱼发布首个开源框架Jupiter
中新网8月6日电 日前,斗鱼将基于Go语言的微服务框架Jupiter正式开源,这也奠定了斗鱼在国内GO语言开发领域的技术领先地位。作为国内领先的游戏直播平台,斗鱼从2014年成立至今,始...【详细内容】
2020-08-07  Search: 框架J  点击:(424)  评论:(0)  加入收藏
前端测试框架Jest——语法篇
使用匹配器使用不同匹配器可以测试输入输出的值是否符合预期。下面介绍一些常见的匹配器。普通匹配器最简单的测试值的方法就是看是否精确匹配。首先是toBe()test(&#39;two...【详细内容】
2020-06-23  Search: 框架J  点击:(430)  评论:(0)  加入收藏
▌简易百科推荐
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(8)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(12)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(54)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(47)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(39)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(50)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(68)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(86)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(86)  评论:(0)  加入收藏
花 15 分钟把 Express.js 搞明白,全栈没有那么难
Express 是老牌的 Node.js 框架,以简单和轻量著称,几行代码就可以启动一个 HTTP 服务器。市面上主流的 Node.js 框架,如 Egg.js、Nest.js 等都与 Express 息息相关。Express 框...【详细内容】
2024-01-16  程序员成功  微信公众号  Tags:Express.js   点击:(88)  评论:(0)  加入收藏
站内最新
站内热门
站内头条