1. 框架概览

Jimmer 是一个相对新兴的 ORM 框架,虽然发布时间不长,但已展现出不少亮眼特性。本文将深入剖析其核心设计理念,并通过实际代码示例展示其用法。值得注意的是,Jimmer 并非 JPA 的实现——这意味着它不会完全遵循 JPA 规范。例如,它没有传统的脏检查机制,但保留了 Hibernate 中的一些相似概念以降低迁移成本。

核心特点:

  • 支持多种数据库(MySQL、Oracle、PostgreSQL 等)
  • 采用接口式实体定义
  • 严格区分未设置属性与显式 null 值
  • 强调调用时显式声明操作意图

⚠️ 与 JPA 的关键差异:

  • 无懒加载/级联操作概念
  • 不支持 @Columninsertable/updatable 属性
  • 实体仅作为数据库结构映射,不包含交互逻辑

2. 实体设计示例

Jimmer 实体采用接口定义,且不包含任何操作行为注解(如级联或抓取策略)。以下是一个典型的图书实体定义:

import org.babyfish.jimmer.client.TNullable;
import org.babyfish.jimmer.sql.Column;
import org.babyfish.jimmer.sql.Entity;
import org.babyfish.jimmer.sql.GeneratedValue;
import org.babyfish.jimmer.sql.GenerationType;
import org.babyfish.jimmer.sql.Id;
import org.babyfish.jimmer.sql.JoinColumn;
import org.babyfish.jimmer.sql.ManyToOne;
import org.babyfish.jimmer.sql.OneToMany;

@Entity
public interface Book {
    @Id
    @GeneratedValue(strategy = GenerationType.USER)
    long id();

    @Column(name = "title")
    String title();

    @Column(name = "created_at")
    Instant createdAt();

    @ManyToOne
    @JoinColumn(name = "author_id")
    Author author();

    @TNullable
    @Column(name = "rating")
    Long rating();

    @OneToMany(mappedBy = "book")
    List<Page> pages();
}

缺失的 JPA 特性:

  • @Cascade 注解
  • fetch = FetchType.LAZY/EAGER 声明
  • @Column(insertable/updatable) 配置

3. DTO 驱动操作

Jimmer 强制通过 DTO 进行数据交互,实体本身不可直接实例化。所有操作都通过 SqlClient 执行,DTO 可通过编译时代码生成或动态创建:

public void saveAdHocBookDraft(String title) {
    Book book = BookDraft.$.produce(bookDraft -> {
        bookDraft.setCreatedAt(Instant.now());
        bookDraft.setTitle(title);
        bookDraft.setAuthor(AuthorDraft.$.produce(authorDraft -> {
            authorDraft.setId(1L);
        }));
        bookDraft.setId(1L);
    });
    sqlClient.save(book);
}

DTO 生成机制:

  • Java:使用注解处理器(Annotation Processing Tool)
  • Kotlin:使用符号处理器(Kotlin Symbol Processing)
  • 动态创建:通过 Draft 接口的链式调用

4. 精确的 Null 处理

Jimmer 严格区分"未设置属性"和"显式 null 值",这直接影响 SQL 生成:

4.1 部分字段插入

public void insertOnlyIdAndAuthorId() {
    Book book = BookDraft.$.produce(bookDraft -> {
        bookDraft.setAuthor(AuthorDraft.$.produce(authorDraft -> {
            authorDraft.setId(1L);
        }));
        bookDraft.setId(1L);
    });
    sqlClient.insert(book);
}

生成 SQL:

INSERT INTO BOOK(ID, author_id) VALUES(?, ?)

4.2 显式设置 null

public void insertExplicitlySetRatingToNull() {
    Book book = BookDraft.$.produce(bookDraft -> {
        bookDraft.setAuthor(AuthorDraft.$.produce(authorDraft -> {
            authorDraft.setId(1L);
        }));
        bookDraft.setRating(null); // 显式设置 null
        bookDraft.setId(1L);
    });
    sqlClient.insert(book);
}

生成 SQL:

INSERT INTO BOOK(ID, author_id, rating) VALUES(?, ?, ?)

⚠️ 注意: 关联属性(非标量字段)的 null 处理更复杂,需单独讨论。

5. DTO 语言解决方案

为避免 DTO 数量爆炸,Jimmer 提供专用 DTO 语言。通过编译时生成 POJO 简化开发:

export com.example.models.Book
    -> package com.example.dto

BookView {
   #allScalars(Book)
   author {
     id
   }
   pages {
    #allScalars(Page)
   }
}

语法说明:

  • #allScalars:包含所有标量字段
  • 嵌套对象可指定子字段(如 author { id }
  • 集合类型支持完整或部分字段包含

6. 数据查询实践

查询时必须通过 DTO 显式声明所需字段,未声明的字段不会加载:

6.1 使用预定义 DTO

public List<BookView> findAllByTitleLike(String title) {
    return sqlClient.createQuery(BookTable.$)
      .where(BookTable.$.title().like(title))
      .select(BookTable.$.fetch(BookView.class))
      .execute();
}

6.2 动态字段投影

public List<BookView> findAllByTitleLikeProjection(String title) {
    List<Book> books = sqlClient.createQuery(BookTable.$)
      .where(BookTable.$.title().like(title))
      .select(BookTable.$.fetch(
          Fetchers.BOOK_FETCHER
            .title()
            .createdAt()
            .author()
      ))
      .execute();

    return books.stream()
      .map(BookView::new)
      .collect(Collectors.toList());
}

查询优势:

  • 避免 N+1 查询问题
  • 精确控制 SQL 生成
  • 支持动态字段组合

7. 事务管理机制

Jimmer 自身不实现事务管理,完全依赖 Spring 框架的基础设施:

支持的事务模式:

  1. 声明式事务:@Transactional 注解
  2. 编程式事务:TransactionTemplate
  3. 本地事务:基于 TransactionSynchronizationManager
@Transactional
public void updateBook(Book book) {
    sqlClient.save(book);
}

⚠️ 重要: 分布式事务需结合其他解决方案(如 Seata)。

8. 总结

Jimmer 通过以下创新点重塑了 ORM 开发体验:

  1. 调用时声明:所有操作意图在调用时显式指定,而非注解驱动
  2. DTO 优先:强制通过 DTO 进行数据交互,避免实体滥用
  3. 精确控制:对 SQL 生成和 null 处理提供细粒度控制
  4. 生态集成:无缝对接 Spring 事务管理体系

虽然学习曲线较陡,但对于追求性能和可控性的项目,Jimmer 提供了传统 JPA 之外的优秀选择。完整示例代码可参考:GitHub 仓库