1. 框架概览
Jimmer 是一个相对新兴的 ORM 框架,虽然发布时间不长,但已展现出不少亮眼特性。本文将深入剖析其核心设计理念,并通过实际代码示例展示其用法。值得注意的是,Jimmer 并非 JPA 的实现——这意味着它不会完全遵循 JPA 规范。例如,它没有传统的脏检查机制,但保留了 Hibernate 中的一些相似概念以降低迁移成本。
✅ 核心特点:
- 支持多种数据库(MySQL、Oracle、PostgreSQL 等)
- 采用接口式实体定义
- 严格区分未设置属性与显式 null 值
- 强调调用时显式声明操作意图
⚠️ 与 JPA 的关键差异:
- 无懒加载/级联操作概念
- 不支持
@Column
的insertable/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 框架的基础设施:
支持的事务模式:
- 声明式事务:
@Transactional
注解 - 编程式事务:
TransactionTemplate
- 本地事务:基于
TransactionSynchronizationManager
@Transactional
public void updateBook(Book book) {
sqlClient.save(book);
}
⚠️ 重要: 分布式事务需结合其他解决方案(如 Seata)。
8. 总结
Jimmer 通过以下创新点重塑了 ORM 开发体验:
- 调用时声明:所有操作意图在调用时显式指定,而非注解驱动
- DTO 优先:强制通过 DTO 进行数据交互,避免实体滥用
- 精确控制:对 SQL 生成和 null 处理提供细粒度控制
- 生态集成:无缝对接 Spring 事务管理体系
虽然学习曲线较陡,但对于追求性能和可控性的项目,Jimmer 提供了传统 JPA 之外的优秀选择。完整示例代码可参考:GitHub 仓库