前言
在这个快速入门教程中,我们将使用最基础的 Jmix 功能,并开发一个非常简单但功能齐全的项目管理应用程序。其中会展示创建任何 web 应用程序都会涉及的主要内容:
- 如何设计数据模型。
- 如何操作数据。
- 如何创建业务逻辑。
- 如何增加安全机制。
- 如何创建用户界面。
完成本教程之后,您就可以开始开发自己的 Jmix 应用程序。在本教程中,我们将使用 Jmix Studio,所以在开始学习本教程之前,请先安装,并启用试用版许可,以便访问可视化设计器。
示例代码仓库: https://github.com/Haulmont/jmix-quickstart.
创建 Jmix 项目
我们将用 IntelliJ IDEA 的菜单创建一个空的 Jmix 项目。项目使用 JDK 11。
使用 Jmix,可以基于模板生成项目代码。我们用 single-module-application
模板创建应用程序。
设定项目名称:jmix-pm 。
在项目创建完成之后,可以进一步尝试使用 Jmix 工具窗口(Jmix Tool Window),通过 Jmix 工具窗口可以访问项目中不同的文件和配置,还能添加一些项目特定的对象。
创建数据模型和数据库
首要任务是创建实体。业务领域模型只包含两个类:Project
(项目)和 Task
(任务)。它们的关联关系为一对多:一个项目有多个任务。
首先,我们创建 Project
实体。我们可以用项目欢迎页上的一个链接来完成。
另一个方法就是右键点击 Jmix Tool Window 中的 Data Model 节点,然后选择 New → JPA Entity。
输入实体的类名 - Project。
对于该示例项目,我们不需要修改其他的实体参数。
在实体设计器中,根据下面的字段规范创建属性:
属性名 | 类型 | 是否必须 |
name | String (255) | Yes |
startDate | LocalDate | |
endDate | LocalDate | |
manager | 关联至 “User” 实体,多对一关系 | Yes |
在 Jmix 中,我们使用标准的 JPA 实体,因此可以通过代码编辑器或者可视化设计器进行创建。只要点击 + 图标,然后为实体创建属性,Jmix Studio 会为您生成类成员。
我们看看如何添加一个对 User 实体的必须引用。引用关系为多对一,所以我们要定义关联字段 manager
,关联至 User
类。最后,该字段定义是这样的:
在 Jmix 中,通过使用 Instance name 字段,可以为实体设置一个合适的字符串形式的展示格式。对于 Project
实体,我们选取 name
作为展示属性。
如果我们切换至实体设计器底部的 Text 标签页,我们能看到这只是一个普通的带有 JPA 注解的 Java 类。如果需要,可以手动更新生成的代码,当切换至 Designer 标签页时,设计器会同步更新。
下面我们开始创建 Task
实体,并将其关联至我们已经创建好的 Project
类。字段规范表如下。
属性名 | 类型 | 是否必须 |
name | String (255) | Yes |
assignee | 关联至 “User” 实体,多对一关系 | Yes |
startDate | LocalDateTime | |
estimatedEfforts | Integer |
任务不能脱离于项目而存在。这种关系在 Jmix 中被称为 Composition
- 组合。下面我们开始创建任务和项目之间的链接。打开 Project
实体,创建一个组合属性 - tasks
- 一个 Project 内的任务列表。
为了创建 Task
实体中的 Project
链接,我们需要在 Task
中创建一个反向属性。
现在数据模型完成了,下一步我们生成数据库的更新脚本。
在 Jmix Tool Window 中展开 Data Stores 节点,右键点击 Main Data Store 然后选择 Generate Liquibase Changelog。
Jmix Studio 通过比较已有的数据库结构和应用程序的数据模型来生成更新脚本。
我们的项目将使用内存数据库 - HSQLDB。
Studio 可能会要求我们运行已有的脚本同步应用程序和数据库的状态。
完成了,数据库已经创建。
生成 CRUD 管理界面
Jmix Studio 包含一个 UI 界面生成向导,可以用来创建虽然很基础但非常有用的 UI 界面:
- 浏览界面 - 用表格的方式展示实体列表。
- 编辑界面 - 用类似表单的界面编辑一个实体示例。
首先,我们将创建处理项目的界面。在实体编辑器的顶部,点击 Screens 菜单下的 Create screen 启动一个界面生成向导。
另外,也可以使用 Jmix 工具窗口启动界面生成向导。在工具栏中点击 + 按钮然后选择 Screen。
选择 Entity browser and editor screens。
点击 Next,然后在 Entity browser fetch plan 这一步停一下。
在 Jmix 中,我们可以为每个实体定义多个 fetch plan。Fetch plan 定义从数据库读取哪些字段。可以把 fetch plan 定义在一个单独的文件中以供不同的模块使用,也可以在创建界面的时候定义内联的 fetch plan。
我们来创建一个内联的 fetch plan。除了已经选择的属性外,将 manager
也勾选上。
下一步,我们添加 manager
和 tasks
字段。
下一步,点击 Next 完成界面的创建过程。
我们可以看到,每个界面由两部分组成:一个用 Java 编写的界面控制器,负责实现界面的内部逻辑和事件处理,以及一个 XML 布局文件,用来定义界面的外观。我们这个例子中,浏览界面由 ProjectBrowse.java
和 project-browse.xml
组成,而编辑界面由 ProjectEdit.java
和 project-edit.xml
组成。
XML 界面描述可以通过 Jmix Tool Window 的 Data Model 找到。
通过右键菜单打开界面控制器。
请留意 XML 界面描述中的 data 部分,这里定义了如何从数据库读取数据。
<data readOnly="true">
<collection id="projectsDc"
class="com.company.jmixpm.entity.Project">
<fetchPlan extends="_base">
<property name="manager" fetchPlan="_base"/>
</fetchPlan>
<loader id="projectsDl">
<query>
<![CDATA[select e from Project e]]>
</query>
</loader>
</collection>
</data>
界面创建之后,您可以用布局编辑器右上角的按钮预览界面。在预览界面可以看到,所有选择的实体属性都已经添加至界面。
UI 组件与数据是双向绑定的。在已绑定界面字段中的所有改动都会影响选择的数据,反之亦然。
现在我们生成 Task
实体的 CRUD 界面。在读取 Task
的同时,我们还要同时读取 Assignee
和 Project
实体。
下一步,需要的字段已经被选中。
使用 Jmix Studio 提供的窗口顶部的切换按钮可以很方便的在界面控制器、界面描述以及关联实体之间切换:
开发模式运行应用程序
可以通过 IDE 窗口顶部的 Run Configuration 工具运行应用程序。
等程序启动之后,可以通过浏览器访问应用程序。默认地址是 http://localhost:8080/ 。
可以在 IDE 的 Run 窗口查看应用程序的运行日志。
在浏览器打开地址后,可以使用默认的用户名和密码 admin / admin
登录。
在 Application 菜单中,可以找到我们创建的实体操作界面。
尝试添加一些数据,创建一个新的项目并将 admin
用户指派为项目的管理员。
可以在创建项目的同时添加任务。
我们创建一个新用户 - dev1(一号程序员)作为该任务的接收者。
保存新创建的项目,任务也会自动保存。
增加安全机制
在 Jmix 中,您可以创建角色,并赋予角色访问应用程序数据的权限,比如访问特定实体、实体属性,也可以赋予角色一些管理界面的功能权限,比如访问界面、菜单项。
打开 Resource roles 界面并创建“Developer”角色。从列表中选择 Entity policy 并允许开发人员查看和编辑任务实体。
但是只允许开发人员编辑任务的估算和开始时间。
最后,添加查看浏览界面和编辑界面的权限。选择 Grant access to the menu item 添加 Tasks 至主菜单。
然后切换至 Child roles 标签页,为“Developer”添加另一个角色 - “UI: minimal access”,这个角色的作用是允许用户登录应用程序。
将“Developer”角色分配给“dev1”用户。在 Users 界面选择 Role assignments 为用户添加角色。
现在我们用一号程序员的身份登录。可以看到,该用户只有访问指定界面和属性的权限。
增加业务逻辑
现在我们将用 Jmix Studio 创建一个实现业务逻辑的服务,并在界面使用这个服务。这将是一个 Spring 服务,返回最空闲的用户。在管理 UI 中,我们将默认使用此服务为该用户分配任务。
通过 Jmix 工具窗口的工具栏打开常用的操作。选择 Spring Bean 然后输入类名 - TaskService。
Studio 会生成一个空的 Spring bean。用 @Service
替换 @Component
注解。
现在创建 findLeastBusyUser()
方法。在该服务中,我们将使用一个 Jmix 的服务 - DataManager。
这个服务可以使用 JPQL 查询语句帮我们访问数据。
使用窗口顶部的 Inject 按钮在服务中注入 DataManager。
弹窗内选择 DataManager。
添加该方法的实现内容如下:
@Service
public class TaskService {
@Autowired
private DataManager dataManager;
public User findLeastBusyUser() {
User leastBusyUser = dataManager.loadValues("select u, count(t.id) " + // (1)
"from User u left outer join Task_ t " +
"on u = t.assignee " +
"group by u order by count(t.id)")
.properties("user", "tasks")
.list().stream().map(e -> e.<User>getValue("user"))
.findFirst() // (2)
.orElseThrow(IllegalStateException::new);
return leastBusyUser; // (3)
}
}
- JPQL 查询语句,用来查询用户并统计这些用户已经分配的任务数。
- 选择第一个用户(任务数最少的)。
- 返回该用户。
服务写好了,我们在任务编辑界面使用它。
点击位于界面控制器窗口顶部的 Generate Handler 按钮,然后选择 InitEntity
事件。
这里是该方法的实现:
public class TaskEdit extends StandardEditor<Task> {
@Autowired
private TaskService taskService; // (1)
@Subscribe
public void onInitEntity(InitEntityEvent<Task> event) {
event.getEntity().setAssignee(taskService.findLeastBusyUser()); // (2)
}
}
- 将
TaskService
服务注入到界面中。 - 将服务方法的执行结果设置到任务的接收者字段。
完成了。我们重启一下应用程序,看看服务的真实执行情况。
首先,我们添加另外一个开发人员 - dev2.
我们已经给一号程序员分配了一个任务,所以目前最空闲的程序员应该是 Admin 或者二号程序员。
增加的四项任务后,下一个最空闲的程序员将是 Admin 或者一号程序员,他们都只有一个任务。
部署
下面我们看看如何用一个可执行的 JAR 文件部署 Jmix 应用程序。
首先,在 Jmix Studio 运行 boot:jar
命令。
文件打包好之后,切换到文件所在目录。
然后,运行命令:java -jar <file_name>。
现在可以打开浏览器查看应用程序的运行情况。
总结
借助 Jmix,您可以在几分钟内实现一个可立即部署的 Spring Boot 应用程序,这要归功于强大的开发工具和代码生成器。