3. 关联和唯一属性
我们的入职应用程序的下一个需要完成的功能是部门的管理。
入职的新员工要分配给某个部门。每个部门有唯一的名称并关联一位 HR 经理。
在本节中,我们将完成:
-
关联至
User
的Department
实体。 -
带有外键和唯一约束的数据库表。
-
包含选择关联实体 UI 组件的 CRUD 视图。
创建 Department 实体
Department
实体具有 name
(唯一)属性和关联至 User
实体的 hrManager
属性:
如果你的应用程序正在运行,先通过主工具栏的 Stop()按钮停止运行。
与 前一节 一样,在 Jmix 工具窗口中,点击 New()→ JPA Entity。然后在 New JPA Entity 对话框中,Class 字段输入 Department
并选中 Traits → Versioned 复选框:
点击 OK。
Studio 将创建实体类并打开实体设计器:
创建唯一属性
我们为实体添加 name
属性。
在 Attributes 工具栏中点击 Add()。然后在 Name 字段输入 name
,并选中 Mandatory 复选框:
现在我们为数据库的 NAME
列定义唯一约束。目的是保证部门不会重名。
从底部切换至 Indexes 标签页,在 Database Indexes 工具栏中点击 New Index()。Studio 会在索引列表中新加一行:
在 Available attributes 列表中选择 NAME
并点击工具栏中的 按钮将其移至 Selected attributes 列表。
在索引行勾选 Unique 和 Constraint 复选框,并修改索引名称为 IDX_DEPARTMENT_UNQ_NAME
:
如果切换回实体设计器的 Text 标签页,可以看到唯一约束已经添加至 @Table
注解中了:
@JmixEntity
@Table(name = "DEPARTMENT", uniqueConstraints = {
@UniqueConstraint(name = "IDX_DEPARTMENT_UNQ_NAME", columnNames = {"NAME"})
})
@Entity
public class Department {
// ...
}
使用这个注解,Studio 会在 Liquibase 更改日志中为该表添加一个唯一约束。
创建关联属性
现在我们创建部门实体中关联至 User
实体的 HR 经理属性。
点击 Attributes 工具栏的 Add(),然后在 New Attribute 对话框中,Name 字段输入 hrManager
,然后选择:
-
Attribute type:
ASSOCIATION
-
Type:
User
-
Cardinality:Many to One
点击 OK。
可以在 Text 标签页查看该新建属性的源码:
@JoinColumn(name = "HR_MANAGER_ID")
@ManyToOne(fetch = FetchType.LAZY)
private User hrManager;
同时,类的 @Table
注解中也定义了这个外键的索引:
@JmixEntity
@Table(name = "DEPARTMENT", indexes = {
@Index(name = "IDX_DEPARTMENT_HR_MANAGER", columnList = "HR_MANAGER_ID")
},
// ...
)
Indexes 标签页也能看到这个索引。
创建 CRUD 视图
现在我们为 Department
实体生成 CRUD 视图。
在实体设计器顶部的操作面板中,点击 Views → Create view:
视图创建向导的第一步中,我们选择 Entity list and detail screen
(实体列表和详情视图)模板:
点击 Next。
向导的后两步中,我们都使用默认推荐的设置。
在 Entity list view fetch plan 步骤中,选择添加 hrManager
属性:
这样能确保关联的 User
实体会与 Department
实体一起加载,并在列表视图展示。
如果某个属性不在 fetch plan 展示,在生成的视图中,Studio 不会为该字段创建可视化组件。 |
点击 Next。
在 Entity detail view fetch plan 步骤中,会自动选择该属性:
点击 Next。
Localizable messages 步骤使用默认的配置,点击 Create。
Studio 会生成两个视图:Department.list
和 Department.detail
,并打开其源码。可以暂时关闭所有的代码编辑器,本节后面部分会对生成的视图做一些修改。
运行应用程序
点击主工具栏中的 Debug()按钮启动应用程序。
在运行应用程序之前,Studio 会生成 Liquibase 更改日志:
可以看到,更改日志中的语句创建了 DEPARTMENT
表、NAME
列的唯一约束和外键,以及 HR_MANAGER_ID
列的索引。
点击 Save and run。
Studio 会执行更改日志,然后构建并运行应用程序。
应用程序准备好后,在浏览器打开 http://localhost:8080
并使用 admin
/ admin
凭证登录。
点击主菜单的 Application → Departments,打开 Department.list
视图:
点击 Create,打开 Department.detail
视图:
可以点击选择控件中的省略号按钮为部门选择一个 HR 经理。点击后会在当前弹窗之上打开用户列表视图。当在用户表中选定一行后,点击 Select 按钮:
选择一个用户并点击 Select,选中的用户会显示在选择控件中:
点击 OK。关联的用户实体也会在表格中展示:
实例名称
你可能会好奇为什么选择控件和表格会显示 [admin]
呢?
Jmix 有一个概念叫做 实例名称(instance name),以一种易读的方式表示一个实体实例。可以通过在实体属性或方法上添加 @InstanceName
注解进行定义。
项目模板生成的 User
实体有下面的方法定义实例名称:
public class User implements JmixUserDetails, HasTimeZone {
// ...
@InstanceName
@DependsOnProperties({"firstName", "lastName", "username"})
public String getDisplayName() {
return String.format("%s %s [%s]", (firstName != null ? firstName : ""),
(lastName != null ? lastName : ""), username).trim();
}
}
因此,当 firstName
和 lastName
都为空时,User
的实例名称显示为方括号中的 username
,也就是上面我们看到的。
如果实体中有合适的属性时,比如 name
、description
等,Studio 的实体设计器会自动生成 @InstanceName
注解。在我们的例子中,Department
实体的 @InstanceName
注解就放在了 name
属性上:
public class Department {
// ...
@InstanceName
@Column(name = "NAME", nullable = false)
@NotNull
private String name;
}
这样一来,如果其他实体中有关联 Department
实体的话,UI 中就会显示部门的名称。本教程后面会有这种情况。
实体设计器也支持手动定义实例名称。支持通过 Instance name 字段选择某个属性或点击按钮生成返回实例名的方法:
简单的 UI 定制
我们对应用程序的 UI 做一些修改,这样你能更加熟悉 Jmix 的功能。
更改属性名称
也许你已经注意到,为 hrManager
属性自动生成的名称不是很对,生成的是 Hr manager
,我们希望改成 HR Manager
。
在实体设计器中选中 hrManager
属性,然后点击属性名称旁边的地球仪()按钮:
会显示 Localized Message 弹窗,如果是多语言环境,比如添加了中文支持,那么这里还会显示一格中文的文本框:
这里我们先修改内容为 HR Manager
,并点击 OK。
如果在 Jmix 工具窗口中双击 User Interface → Message Bundle 节点,可以修改整个项目的本地化消息。我们刚才修改的内容如下:
切换回浏览器中运行的应用程序。刷新网页,可以看到 hrManager
属性的新名称。
由于 Studio 带有 热部署 功能,无需重启应用程序即可看到 UI 的改动。 只需要在 IDE 中保存修改(按下 |
DataGrid 中的排序
默认情况下,用户可以对数据网格中展示的数据按照某一列进行排序。这里我们启用按多列排序。
在 Jmix 工具窗口找到 department-list-view.xml
文件并双击打开。然后会显示视图设计器:
Studio 支持直接在 IDE 中预览页面的布局。点击 Start Preview 按钮:
Studio 会自动构建前端代码,稍等片刻后,可以在源码旁边的面板内看到页面预览面板。根据你显示器的大小不同,可以选择仅显示 XML 编辑器或者同时显示预览。通过编辑器顶部的按钮切换:
在 Jmix UI 的结构面板中,选中 departmentsDataGrid
。则同时在预览面板、XML 编辑器以及右下角的 Jmix UI 组件属性面板都会选中该组件:
勾选 multiSort
复选框:
Studio 会自动为 dataGrid
XML 元素添加 multiSort="true"
属性。
反过来操作也可以。比如,直接在 XML 中编辑,则预览面板和组件属性也能同时反映改动。 |
切换至运行中的应用程序,并刷新部门列表视图页面。点击 Name 列测试排序功能,然后可以点击 HR Manager 列试试。
更改违反唯一约束通知消息
如果尝试创建另一个同名的部门,则可以看到违反唯一约束的错误消息:
默认的消息不是特别友好,可以进行定制化修改。
在 Jmix 工具窗口双击 User Interface → Message Bundle 节点,并添加下面这一行内容:
databaseUniqueConstraintViolation.IDX_DEPARTMENT_UNQ_NAME=A department with the same name already exists
消息的键值需要以 databaseUniqueConstraintViolation.
开头,并带上数据库唯一约束的名称。你也许注意到,在该文件内已经存在类似的消息,是配置给 User
实体的 username
属性的。
切换至应用程序并测试我们的改动。现在错误消息显示好一些了:
小结
本节中,我们构建了第二个功能:部门的管理。
学习内容:
-
Studio 帮助创建关联属性并生成带有外键和索引的 Liquibase 变更日志。
-
为了在列表或详情视图展示关联属性,需要将属性包含在视图的 fetch plan 中。
-
实例名称 用来在 UI 中展示关联实体。
-
在自动生成的详情视图中,默认使用 entityPicker 实体选择器 组件选择关联实体。
-
实体属性的唯一性 是在数据库级别通过定义唯一约束进行维护的。
-
违反唯一约束 的错误消息可以轻松实现自定义。
-
Studio 生成的标题和消息都保存在应用程序的 消息包 中。
-
Studio 可以 热部署 视图和消息的改动,在开发 UI 时可以节省重启应用的时间。但是对实体的修改是不支持热部署的。