映射是 实体关系定义 与 数据库交互 的基础,也是使用 Jimmer(ORM) 的基础。

关系映射概念

主动方与从动方

主动方:包含外键的实体,定义时关联注解 不包含 mappedBy
从动方:不包含外键,不能主动控制关联关系,定义时关联注解 包含 mappedBy

包含 mappedBy 就是从动方,作为主动方的镜像。一旦指定从动方的 mappedBy 属性,就不得在该属性上使用比如 @JoinColumn 或者 @JoinTable。

实体定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import org.babyfish.jimmer.sql.*;

@Entity
@Table(name = "app.db_student")
public interface Student {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id();

/**
* 姓名
*/
@Key
String username();

@IdView
Long addressId();

@IdView
@Nullable
Long collegeId();

/**
* 出生日期
*/
LocalDateTime birthday();

/**
* 是否删除
*/
@Column("is_delete")
boolean deleteFlag();

@OneToMany
List<Course> courses();

/**
* 如果使用了真外键,名称与默认的命名策略推导结果(addressId)不同,
* 那么可以使用 @JoinColumn 定义
*/
@OneToOne
@JoinColumn(name = "真外键名称")
Address address();

/**
* 覆盖默认的命名推导策略
* @JoinTable:
* name: 中间表的表名
* joinColumns = @JoinColumn(name = "stu_id") 主动方 id(主表外键)
* inverseJoinColumns = {@JoinColumn(name = "course_id")} 从动方(镜像方)id(从表外键)
*/
@ManyToMany
@JoinTable(
name = "db_stu_course",
joinColumns = {@JoinColumn(name = "stu_id")},
inverseJoinColumns = {@JoinColumn(name = "course_id")}
)
List<Course> courses();

@ManyToOne
@Nullable
College college();
}
注解 说明
@Entity 定义 Jimmer ORM 实体
@Table 指定该实体对应的表名,也可以写入数据库的 schema,如上示例为 app schema 中的名为 db_student 的表。
@Id 主键标识符,不能为 null
@Key 业务主键键,保存指令的根基
@GeneratedValue(strategy = GenerationType.IDENTITY) 指定主键为自增策略(也可以使用自定义的算法)
@Column 数据库中的真实字段名
@JoinColumn 覆盖默认的命名策略,指定外键名称
@JoinTable 覆盖默认的命名策略,指定中间表关联和中间表的外键名称

必须要提的是,Java Bean 有一个不好的坏习惯,Boolean 类型且 is 开头的属性在多种序列化的工具中会自动给去掉,因此 Jimmer 中检测到 is 开头的属性会提醒你一下。
Jimmer 是支持 is 开头的属性的,但默认情况下处于关闭状态(即和 Java Bean 默认的约定一样)。可以在编译时使用 -Ajimmer.keepIsPrefix=true 属性开启。

一对一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Entity
@Table(name = "db_college")
public interface Address {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id();

@Key
String province();

@Key
String city();

@Key
String district();

/**
* 作为从动方的一对一关联属性必须可 null
*/
@OneToOne(mappedBy = "address")
@Nullable
Student student();
}

作为从动方的一对一关联属性必须可 null

一对一可支持双向关联,对于双向关联而言,其中一方必须主动方,另外一方为从动方。

主动方(必须):真正的数据库和关联属性之间映射,实现单向一对一关联。

从动方(可选):如果已经存在一个单向关联,可以为此配置从动方,作为主动方的镜像,形成双向关联。

一对多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@Table(name = "db_college")
public interface College {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id();

@Key
String collegeName();

@OneToMany(mappedBy = "college")
List<Student> students();
}

Jimmer 不支持单向一对多关联,一对多关联只能作为多对一关联镜像。也就是说,一对多关联必然意味着双向关联。

  • @OneToMany 关联仅仅是 @ManyToOne 关联的镜像,不得使用 @JoinColumn@JoinTable
  • @OneToMany 修饰的关联属性必须非 null,如果查询的父对象没有子集,那么将是一个长度为 0 的空集合,而非 null。

多对一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
@Table(name = "db_student")
public interface Student {

...

/**
* 同样的,如果外键列名 Jimmer 的推导策略有误
* 可以使用 @JoinColumn 指定
*
* OnDissociate 设置脱钩模式,如果父对象 college 被删除,关联的 students 也被删除
*/
@ManyToOne
@Nullable
@OnDissociate(DissociateAction.DELETE)
// @JoinColumn(name = "指定外键列名")
College college();
}

@OnDissociate 设置脱钩模式 为 DELETE,以上面的代码为例:若删除父对象 college,与之关联的子对象 students 一并删除。除此之外,还有 LAX、ChECK、SET_NULL,默认为 NONE。

多对多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
@Table(name = "db_course")
public interface Course {

/**
* Java 基本类型全部不可 null,并使用 @Id 修饰,设置生成策略为 IDENTITY 数据库自增长
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id();

@Key
String courseName();

String courseCode();

/**
* 多对多关系映射 从动方
*/
@ManyToMany(mappedBy = "courses")
List<Student> students();
}

多对多可支持双向关联,对于双向关联而言,其中一方必须主动方,另外一方为从动方。多对多关系中主动方和从动方可以随意抉择,二者都可以用于保存关联。

主动方(必须):真正的数据库和关联属性之间映射,实现单向多对多关联。

从动方(可选):如果已经存在一个单向关联,可以为此配置从动方,作为主动方的镜像,形成双向关联。

MappedSuperclass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 定义 jimmer 的超类
*/
@MappedSuperclass
public interface BaseEntity {

LocalDateTime createdTime();

@ManyToOne
User createdBy();

LocalDateTime modifiedTime();

@ManyToOne
User modifiedBy();
}

这样 Jimmer 的实体类就可以继承这个超类了,并且由于是接口定义的不可变实体,所以天然支持多继承。

@MapperSuperclass 的作用不仅仅是减少重复代码,还可以和其他另外两个功能配合使用:拦截器全局过滤器


本站由 江湖浪子 使用 Stellar 1.29.1 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。