十二、Spring Boot
Spring 是一组提高企业应用开发人员生产力的项目。Spring 中有许多项目和模块,但是您不必为了利用它们而全部掌握它们。Spring 中的一个关键项目是 Spring Framework ,这是一个面向企业应用的编程和配置模型。我们将使用 Spring Framework 和 Spring Boot ,这个项目简化了独立应用的创建,其中包括自动配置的库(比如用于连接 SQL 数据库的 JPA)。
本章展示了如何在 Spring Boot 应用的上下文中使用 Vaadin,以及如何快速实现连接到 MySQL 数据库的 CRUD 视图。
创建新的 Spring Boot 项目
创建 Spring Boot 项目最常见的方法是使用 https://start.spring.io
中的 Spring Initializr 工具。这是一个在线工具,您可以在其中填写项目详细信息,并添加要使用的依赖项。Vaadin 是与许多其他 Java 库一起可用的依赖项之一。
我们将创建一个名为 spring 的 Maven 项目,并使用 Java 11——撰写本文时 Java 的长期支持(LTS)版本。我们将使用以下依赖关系:
-
这本书最喜欢的网络框架
-
Spring Data JPA: 使用 Jakarta 持久性 API(JPA;以前的 Java 持久性 API)
-
**MySQL 驱动:**连接 MySQL 数据库的 JDBC 驱动
Note
你需要一个运行在你的机器上的 MySQL 服务器来理解这一章。您可以在 https://dev.mysql.com/downloads
下载免费的 MySQL 社区服务器。如果要使用不同的数据库系统,请在创建项目时选择正确的驱动程序。如果您不想安装任何额外的软件,可以在 Spring Initializr 中添加 H2 数据库依赖项。H2 数据库可以配置为一个嵌入式服务器,与 Java 应用一起运行。
图 12-1 显示了我用来创建示例项目的 Spring Initializr 配置的截图。
图 12-1
使用 Spring Initializr 创建一个新的 Spring Boot 项目
当您点击 GENERATE 按钮时,您将得到一个包含 Maven 项目的 ZIP 文件。提取该文件,并将项目导入 IDE。
生成的项目中的主要文件是
-
pom.xml: 定义 Maven 项目的项目对象模型文件。您将看到没有
<packaging>
声明,这意味着使用了默认值(JAR)。默认情况下,Spring Boot 应用被打包成独立的 Java 应用,您可以使用 JDK 二进制文件提供的 java 工具直接运行这些应用。 -
Application.java: 定义了 java 应用的
public static void main(String...)
标准入口点。这允许使用mvn package
将应用构建为 JAR 文件,然后您可以使用java -jar spring.jar
运行它。在开发过程中,您可以像处理任何其他标准 Java 应用一样简单地运行入口点。 -
application.properties: 服务器端口、数据库连接字符串等应用配置属性。默认情况下,该文件为空。Spring Boot 使用的默认配置可以在该文件中被覆盖。
创建新数据库
在建立连接之前,我们需要运行数据库服务器。确保您安装了 MySQL,并使用用户名和密码连接到服务器。我将使用默认用户(root
):
mysql -u root -p
引入密码后,为本章的示例应用创建一个新数据库:
CREATE DATABASE spring_example;
Caution
在生产环境中,请确保为您的应用创建了一个数据库用户,并配置了数据库所需的权限。
在这个数据库中,让我们创建一个新表来存储用户信息:
USE spring_example;
CREATE TABLE users(
id INT NOT NULL AUTO_INCREMENT,
email VARCHAR(255),
user_password VARCHAR(255),
birth_date DATE,
favorite_number INT,
PRIMARY KEY (id)
);
最后,我们可以在这个表中插入一些初始数据:
INSERT INTO users(email, user_password, birth_date,
favorite_number)
VALUES ("marcus@test.com", "pass1", "1990-03-11", 888);
INSERT INTO users(email, user_password, birth_date,
favorite_number)
VALUES ("sami@test.com", "pass2", "1991-05-13", 777);
INSERT INTO users(email, user_password, birth_date,
favorite_number)
VALUES ("guillermo@test.com", "pass3", "1992-07-15", 666);
检查表中是否有一些行:
SELECT * FROM users;
数据库准备好了!
配置数据库连接
允许应用连接到数据库的 Java 技术被称为 Java 数据库连接 (JDBC)。JDBC 是一个 API,数据库供应商可以实现它来提供 Java 程序到数据库系统的连接。这里重要的概念是 JDBC 驱动。这是一个 JAR 文件,它封装了与特定数据库“对话”的逻辑。我们已经在上一节中添加了 MySQL 的驱动程序。
使用 JDBC,数据库连接是通过连接字符串指定的。我们需要为我们正在使用的数据库找到正确的连接字符串,并在 Spring Boot 使用的resources/application . properties文件的特定属性中配置它。我们还必须设置连接到数据库的用户和密码。以下是我们需要添加来连接到spring_example
MySQL 数据库的属性(确保使用正确的用户和密码):
spring.datasource.url=jdbc:mysql://localhost:3306/spring_example
spring.datasource.username=root
spring.datasource.password=password
以下是一些其他流行数据库的 JDBC 连接字符串示例:
-
PostgreSQL:
jdbc:postgresql://localhost:5432/spring_example
-
甲骨文:
jdbc:oracle:thin:@localhost:1521:spring_example
-
SQL Server:
jdbc:sqlserver://localhost;databaseName=spring_example
-
H2(基于文件):
jdbc:h2:~/some-directory/spring_example
-
H2(内存中):
jdbc:h2:mem:spring_example
Spring 将建立一个连接池来连接到已配置的数据库。连接池是应用可用的一组数据库连接。不是在事务发生时创建连接,而是重用池中的连接以避免浪费计算资源。这是自动发生的,你现在不需要担心。
实现实体
我们将使用 Jakarta 持久性 API (JPA)来连接和读写数据。JPA 是一个规范,而不是一个实现。Spring Boot 使用 Hibernate(一个 JPA 实现)。JPA 建立在 JDBC 之上,允许您将 Java 类映射到 SQL 表。例如,我们可以创建一个User
类,它与我们之前创建的users
SQL 表相匹配。
Note
你可以在 https://eclipse-ee4j.github.io/jakartaee-tutorial/#persistence
了解更多关于 JPA 的信息。
让我们从简单地为users
SQL 表定义一个具有匹配属性的 Java 类开始:
public class User {
private Integer id;
private String email;
private String password;
private LocalDate birthDate;
private Integer favoriteNumber;
... getters and setters ...
}
可以通过 JPA 在数据库中持久化实例的类被称为实体。一个实体类必须用@Entity
注释,并且必须包含一个用@Id
标记的属性(对应于表的主键)。默认情况下,Hibernate 使用类名及其属性将camelCase
转换为snake_case
,在数据库中寻找匹配的 SQL 表和列。我们可以通过使用@Table
和@Column
注释来定制它。下面是我们需要配置来匹配users
表的内容:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String email;
@Column(name = "user_password")
private String password;
private LocalDate birthDate;
private Integer favoriteNumber;
...
}
我们正在更改表名和密码列的默认值。我们还配置了id
属性,让 JPA 知道该值是由数据库生成的,因此我们在创建User
类的实例时不必设置它的值。
基于 identity 属性(id
)添加hashCode()
和equals(User)
方法的正确实现也很重要。大多数 ide 都有生成这些方法的工具。这里有一个例子:
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
return Objects.equals(id, other.id);
}
这个类现在是持久化的了!
添加存储库
一个库是一个用于从数据库中读取和写入数据的对象。它包含执行所有或部分 CRUD 操作的方法。使用 Spring,创建存储库很简单,因为它可以通过 Java 接口以声明方式完成,而您不必实现它。要创建一个存储库,我们必须使用@Repository
注释并扩展一个可用的存储库接口。看一下这个存储库接口:
@Repository
public interface UserRepository
extends JpaRepository<User, Integer> {
}
我们正在扩展的JpaRepository<T, ID>
接口将我们想要使用的域类型或实体及其 id 属性的类型作为参数。该接口声明了用于数据操作的有用方法。以下是其中的一些:
-
List<T> findAll()
:返回表中对应于实体类型的所有实例或行(如User
) -
Optional<T> findById(ID id)
:按 id 返回实体 -
S save(S entity)
:保存给定的实体 -
void delete(T entity)
:删除给定的实体 -
long count()
:返回实体的数量
还有更多可用的方法。使用 IDE 的自动完成功能来熟悉您可以使用的功能。记住,我们不会实现这个接口。Spring 将在运行时提供实现。
Tip
通过添加与 Spring 使用的约定相匹配的方法,您可以将自己的查询添加到存储库中。在运行时,方法的名称用于创建适当的查询。 https://docs.spring.io/spring-data/jpa/docs/current/reference/html
见。
控制反转和依赖注入
Spring 的核心部分是它的反转控制和依赖注入特性。当 Spring 应用启动时,您运行 Spring 的代码(项目中的Application
类),也就是说,您将应用执行的控制权交给了 Spring。然后,Spring 扫描您的项目,寻找用@Repository
注释的类,并创建这些类的实例,您可以通过依赖注入机制使用它们。这个用一个例子就很好理解了。
让我们实现一个 Vaadin 视图,它使用 repository 类来显示数据库中的用户数量。让我们从这个开始:
@Route("user-count")
public class UserCountView extends Composite<Component> {
@Override
protected Component initContent() {
long count = 999; // TODO!
return new VerticalLayout(new H1("How many users?"),
new Text(count + " users found."));
}
}
目前,我们对用户数量进行硬编码(999
)。为了解决这个问题,我们需要一个类型为UserRepository
的实例,这是我们在上一节中编写的存储库接口。我们不能使用新的UserRepository()
,因为这是一个接口。相反,我们可以在构造函数中接受这种类型的引用,并告诉 Spring 注入一个实例。由于UserRepository
接口标有@Repository
,Spring 知道如果另一个类在构造函数中需要它,它需要创建一个这种类型的新实例:
@Route("user-count")
public class UserCountView extends Composite<Component> {
private final UserRepository userRepository;
@Autowired
public UserCountView(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
protected Component initContent() {
long count = userRepository.count();
return new VerticalLayout(new H1("How many users?"),
new Text(count + " users found."));
}
}
如你所见,我们用@Autowired
标记构造函数。事实上,这个注释是可选的,但是为了清楚起见,我们将保留它。
Spring 如何将UserRepository
的实例传递给UserCountView
类?事实是,UserCountView
也是春天创造的!这要归功于项目中包含的 Vaadin-Spring 集成(参见pom.xml
文件)。在创建UserCountView
的实例之前,Spring 发现它依赖于一个UserRepository
,并使用依赖注入将它传递给构造函数。稍后在initContent()
方法中,我们可以使用实例来获取数据库中的用户数量。
您可以通过执行Application
类中的main(String...)
方法来运行应用。大多数 ide 都包括一个选项,当你右击文件时就可以这样做。或者,你可以运行spring-boot:run
Maven 目标。图 12-2 显示了该视图的截图。
图 12-2
vaadin 查看 sql 数据库消费者
Tip
在前面的例子中,我们直接在视图中使用了存储库接口。在更严肃的应用中,视图应该使用服务类。这个服务类可以使用存储库接口连接到数据库,有效地将表示层(视图)与持久性技术(JPA)分离。
实现 CRUD
到现在,你应该对如何结合整本书所学的知识有一个清晰的想法。您可以使用Binder
类和存储库类通过 UI 将域模型连接到数据库。在这一节中,我想向您展示一种快速的方法(我所知道的最快的方法之一)来实现一个全功能的 CRUD,它使用我创建的开源 Vaadin 插件,可以在 https://vaadin.com/directory/component/crud-ui-add-on
免费获得。
Note
Vaadin 目录包含由 Vaadin 社区贡献的许多有用的附加组件。你也可以在那里发布你自己的插件!
首先,让我们将 Crud-UI 附加组件添加到pom.xml
文件中:
<repositories>
<repository>
<id>vaadin-addons</id>
<url>https://maven.vaadin.com/vaadin-addons</url>
</repository>
</repositories>
<dependencies>
...
<dependency>
<groupId>org.vaadin.crudui</groupId>
<artifactId>crudui</artifactId>
<version>4.4.1</version>
</dependency>
<dependencies>
这个附加组件包括GridCrud<T>
类,它允许我们通过使用一行代码来呈现 CRUD UI 组件:
var crud = new GridCrud<>(User.class);
我们可以使用 lambda 表达式连接 CRUD 操作。例如,要使用存储库删除用户,我们可以配置以下操作:
crud.setDeleteOperation(userRepository::delete);
我们可以配置组件显示的列和字段的可见性:
crud.getGrid().setColumns("email", "birthDate", "favoriteNumber");
crud.getCrudFormFactory().setVisibleProperties("email",
"password", "birthDate", "favoriteNumber");
最后,我们还可以配置用于特定属性的输入组件的类型。例如:
crud.getCrudFormFactory().setFieldType("password", PasswordField.class);
综上所述,我们可以实现一个连接到 Spring 存储库的全功能 CRUD 视图,如下所示:
@Route("user-crud")
public class UserCrudView extends Composite<Component> {
private final UserRepository userRepository;
public UserCrudView(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
protected Component initContent() {
var crud = new GridCrud<>(User.class);
crud.getGrid().setColumns("email", "birthDate",
"favoriteNumber");
crud.getCrudFormFactory().setVisibleProperties("email",
"password", "birthDate", "favoriteNumber");
crud.getCrudFormFactory().setFieldType("password",
PasswordField.class);
crud.setFindAllOperation(userRepository::findAll);
crud.setAddOperation(userRepository::save);
crud.setUpdateOperation(userRepository::save);
crud.setDeleteOperation(userRepository::delete);
return new VerticalLayout(crud);
}
}
图 12-3 显示了视图的截图。
图 12-3
具有数据库连接的 CRUD 视图
该附件包括许多其他有用的功能。您可以激活 Jakarta Bean 验证、更改布局、添加动作按钮等等。为了说明其中一个特性,请参见图 12-4 中默认情况下编辑表单是如何可视化的。
图 12-4
CRUD 视图中的更新表单
通过设置一个新的CrudFormFactory
实现,我们可以很容易地改变布局,在左侧的Grid
旁边显示编辑和新表单。Crud-UI 插件包括几个备选方案。例如:
var crud = new GridCrud<>(User.class,
new HorizontalSplitCrudLayout());
参见图 12-5 中这一变化的结果。
图 12-5
更改 Crud-UI 组件的默认布局
摘要
本章让您开始了解 Spring Boot 和数据库连接。您了解了如何创建新的 MySQL 数据库,以及如何通过 Spring 使用 JDBC 和 JPA 配置连接。您了解了什么是实体以及如何将其映射到数据库表。您看到了创建一个存储库类来读写数据是多么容易,以及如何使用控制反转和依赖注入来连接 Spring 应用中的对象。最后,您看到了当您使用 Vaadin 目录中可用的附加组件时,创建 Vaadin 应用是多么简单。
下一章使用与这里相似的方法来解释另一项强大的技术:Jakarta EE。
十三、Jakarta EE
Jakarta EE(以前的 Java 企业版)是一组帮助开发人员用 Java 实现企业软件的规范。在前面的章节中,我们已经使用了 Jakarta Servlet、Jakarta Bean 验证和 Jakarta 持久性。所有这些规范都是 Jakarta EE 的一部分。为了获得更好的视角,请快速浏览一下 https://jakarta.ee/specifications
的所有规格。
Jakarta EE 为您的 Java 应用提供了一个运行时环境,其编程模型基于容器的概念。容器通过拦截对类中方法的调用来封装代码,从而为代码添加功能。这种功能的例子是根据用户角色保护方法调用、在事务上下文中执行代码以将执行作为一个单元、方法的异步调用以及类所需的依赖项的注入。
Jakarta EE 环境可作为应用服务器使用,您可以在其中部署应用。您可以将运行时与您的应用代码一起打包在同一个工件(优步 JAR)中,或者将应用代码与运行时分开放在一个 WAR 文件中。
创建新的 Jakarta EE 项目
Jakarta EE 有几种兼容的实现:
-
Eclipse GlassFish
-
Apache Tomcat
-
阿帕契·汤姆
-
码头
-
Payara 平台
-
开放自由
-
野猫队
-
小贱人
-
Eclipse 球衣
在写这本书的时候,Vaadin 支持 Jakarta EE 8(版本 9 是最新的),所以这限制了我们的选择。我们将使用 Apache TomEE 作为 Maven 插件来简化开发周期,但是您可以将示例应用部署到任何符合 Jakarta EE 8 的服务器上。
我们可以使用现有的 Vaadin 项目并添加以下依赖项,以开始使用 Jakarta EE 提供的 API:
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>8.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-cdi</artifactId>
</dependency>
我们需要在 src/main/webapp/WEB-INF/ 目录中添加一个新的 beans.xml 文件。这个文件是激活上下文和依赖注入所必需的(稍后将详细介绍)。使用以下内容创建它:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns:="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
</beans>
不用安装 Jakarta EE 服务器,我们可以使用 Maven 运行应用,并添加 Apache TomEE 插件,如下所示(这相当于安装应用服务器,只是服务器是通过 Maven 管理的):
<plugin>
<groupId>org.apache.tomee.maven</groupId>
<artifactId>tomee-maven-plugin</artifactId>
<version>8.0.7</version>
<configuration>
<context>ROOT</context>
</configuration>
</plugin>
Note
您可以在 https://tomee.apache.org
了解更多关于 Apache TomEE 的信息。
现在我们可以添加一个 Vaadin 视图来检查一切是否正常:
@Route("hello-jakarta-ee")
public class HelloJakartaEEView extends Composite<Component> {
@Override
protected Component initContent() {
return new VerticalLayout(new Text("Hello Jakarta EE!"));
}
}
您可以使用mvn package
构建应用,并将 WAR 文件部署到任何 Jakarta EE 运行时,或者使用mvn tomee:
run
运行应用。图 13-1 显示了结果。
图 13-1
运行在 Apache TomEE 上的 Jakarta EE Vaadin 应用
创建新数据库
和前一章一样,在建立连接之前,我们需要数据库服务器运行。确保您安装了 MySQL,并使用用户名和密码连接到服务器。
Note
您可以在 https://dev.mysql.com/downloads
下载免费的 MySQL 社区服务器。
我将使用默认用户(root
):
mysql -u root -p
引入密码后,为本章的示例应用创建一个新数据库:
CREATE DATABASE jakarta_ee_example;
在这个数据库中,让我们创建一个新表来存储用户信息:
USE jakarta_ee_example;
CREATE TABLE users(
id INT NOT NULL AUTO_INCREMENT,
email VARCHAR(255),
user_password VARCHAR(255),
birth_date DATE,
favorite_number INT,
PRIMARY KEY (id)
);
最后,我们可以在这个表中插入一些初始数据:
INSERT INTO users(email, user_password, birth_date,
favorite_number)
VALUES ("marcus@test.com", "pass1", "1990-03-11", 888);
INSERT INTO users(email, user_password, birth_date,
favorite_number)
VALUES ("sami@test.com", "pass2", "1991-05-13", 777);
INSERT INTO users(email, user_password, birth_date,
favorite_number)
VALUES ("guillermo@test.com", "pass3", "1992-07-15", 666);
检查表中是否有一些行:
SELECT * FROM users;
配置数据库连接
如果您还记得上一章,Java 应用通过 JDBC 驱动程序连接到特定的数据库系统。数据库连接依赖于应用运行的环境。例如,当您开发一个应用时,您可能在同一台开发机器上运行一个数据库服务器。当您将应用部署到生产服务器时,应用并不连接到您的开发机器,而是连接到生产就绪的机器。因此,数据库连接最好在应用运行的环境中配置,而不是在应用代码中配置。
Jakarta EE 环境允许您在配置文件中定义资源,比如数据库连接。由于我们使用 Apache TomEE Maven 插件,我们的运行时环境驻留在我们工作的同一台机器上,甚至驻留在我们编码的项目中。在这种情况下,我们可以在项目内部的文件中定义连接的细节。然而,当您将应用部署到生产环境中时,您不会使用 Maven 插件。相反,您必须在生产环境中定义数据库连接。应用代码可以通过我们可以建立的名称来引用数据库连接资源。现在,我们将跳过生产设置,为 Apache TomEE Maven 插件配置数据库连接资源。
首先,我们需要将 JDBC 驱动程序添加到在 pom.xml 文件中定义的运行时(Apache TomEE)中。我们需要做的就是更新 Apache TomEE 插件声明,以包含 MySQL JDBC 驱动程序:
<plugin>
<groupId>org.apache.tomee.maven</groupId>
<artifactId>tomee-maven-plugin</artifactId>
<version>8.0.7</version>
<configuration>
<context>ROOT</context>
<libs>
<lib>mysql:mysql-connector-java:8.0.25</lib>
</libs>
</configuration>
</plugin>
现在,包含 JDBC 驱动程序的 JAR 文件在运行时是可用的。如果您想将应用部署到一个外部独立服务器上,那么您也必须在那里添加 JAR 文件。
现在我们可以配置数据库连接细节。我们可以在一个名为 tomee.xml 的新文件中进行设置,该文件位于 src/main/tomee/conf/ 目录中:
<tomee>
<Resource id="mysqlDatasource" type="DataSource">
JdbcDriver com.mysql.cj.jdbc.Driver
JdbcUrl jdbc:mysql://localhost:3306/jakarta_ee_example
UserName root
Password password
</Resource>
</tomee>
注意我们使用的id
(mysqlDatasource
)。我们将使用这个名称从应用代码中引用这个数据源。这允许我们从运行时环境中分离连接细节。例如,生产服务器可以将数据源定义为到 Oracle 数据库的连接,而我们不必对应用代码进行任何更改。
Jakarta EE 将建立一个连接池来连接到已配置的数据库。
Note
连接池是应用可用的一组数据库连接。不是在事务发生时创建连接,而是重用池中的连接以避免浪费计算资源。这是自动发生的,你现在不需要担心。
实现实体
Jakarta EE 包含 JPA,所以我们可以添加我们在上一章中编写的相同实体,这次在所有属性中使用显式列名,并添加 Jakarta Bean 验证注释:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@NotNull
private Integer id;
@Column(name = "email")
@NotNull
@NotBlank
@Email
private String email;
@Column(name = "user_password")
@NotNull
@NotBlank
@Size(min = 5)
private String password;
@Column(name = "birth_date")
@Past
private LocalDate birthDate;
@Column(name = "favorite_number")
@PositiveOrZero
private Integer favoriteNumber;
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
return Objects.equals(id, other.id);
}
... getters and setters ...
}
Note
Apache TomEE 使用 Apache OpenJPA 作为 JPA 实现。OpenJPA 用 SQL 列匹配属性名的方式不同于 Hibernate。OpenJPA 使用类和属性的确切名称在数据库中查找相应的表和列。
当在 Jakarta EE 应用中使用 JPA 时,我们需要定义一个持久性单元。持久性单元定义实体和与它们一起使用的数据源。为了定义一个持久性单元,我们需要在*src/main/resources/META-INF/*目录下创建一个名为 persistence.xml 的新文件:
<persistence xmlns:="http://java.sun.com/xml/ns/persistence"
version="1.0">
<persistence-unit name="jakarta-ee-example-pu"
transaction-type="RESOURCE_LOCAL">
<jta-data-source>mysqlDatasource</jta-data-source>
<class>com.apress.practicalvaadin.ch13.User</class>
</persistence-unit>
</persistence>
如您所见,我们使用了在 Jakarta EE 运行时(Apache TomEE)中定义的数据源名称。这个文件是应用代码的一部分,但是它不包含任何数据库连接细节。正如前面指出的,连接细节存在于运行时中。还要注意我们是如何将User
类添加为托管实体的。如果我们需要更多的实体,我们可以在那里列出它们。如果我们有不止一个数据库,我们可以定义更多的持久性单元,并向其中添加相应的实体。
添加存储库
Jakarta EE 本身不包含声明性存储库实现的功能。然而,它确实包含了扩展机制,Apache DeltaSpike ( https://deltaspike.apache.org
)项目包含了一个创建这种存储库类的库。我们需要在 pom.xml 文件的<dependencyManagement>
部分添加 Apache DeltaSpike 物料清单(BOM ):
<dependency>
<groupId>org.apache.deltaspike.distribution</groupId>
<artifactId>distributions-bom</artifactId>
<version>1.9.4 </version>
<type>pom</type>
<scope>import</scope>
</dependency>
我们需要在<dependencies>
部分添加 Apache DeltaSpike 数据模块:
<dependency>
<groupId>org.apache.deltaspike.core</groupId>
<artifactId>deltaspike-core-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.deltaspike.core</groupId>
<artifactId>deltaspike-core-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.deltaspike.modules</groupId>
<artifactId>deltaspike-data-module-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.deltaspike.modules</groupId>
<artifactId>deltaspike-data-module-impl</artifactId>
<scope>runtime</scope>
</dependency>
Apache DeltaSpike 数据需要一个EntityManager
。图书馆寻找这个对象的方式是通过一个 CDI 生产者。现在不要太担心这些术语,把EntityManager
类看作是一个助手类,作为 JPA 的接口,供 Apache DeltaSpike 数据内部使用。CDI 生产者只是一个生产EntityManager
实例的工厂:
@ApplicationScoped
public class EntityManagerProducer {
@PersistenceUnit(unitName = "jakarta-ee-example-pu")
private EntityManagerFactory emf;
@Produces
public EntityManager create() {
return emf.createEntityManager();
}
public void close(@Disposes EntityManager em) {
if (em.isOpen()) {
em.close();
}
}
}
Jakarta EE 运行时将自动创建该类的一个实例,Apache DeltaSpike 稍后可以使用该实例通过 JPA 与数据库进行通信。我们声明了在@PersistenceUnit
注释中使用的持久性单元的名称。现在我们看到持久性逻辑是如何通过这个持久性单元和应用服务器中定义的数据源连接到数据源的。
有了这些,我们可以定义一个存储库接口:
@Repository
public interface UserRepository
extends EntityRepository<User, Integer> {
}
和 Spring 一样,我们不需要实现这个接口。
上下文和依赖注入
Jakarta EE 和 Vaadin 是通过一个上下文和依赖注入(CDI)库集成的,我们在配置项目时已经将它添加为一个依赖项。CDI 是一个 Jakarta EE 规范,它允许对类的实例进行解耦。为了理解它是如何工作的,让我们回顾一下我们在上一节中实现的EntityManagerProducer
类:
@ApplicationScoped
public class EntityManagerProducer {
@PersistenceUnit(unitName = "jakarta-ee-example-pu")
private EntityManagerFactory emf;
...
}
CDI 运行时将看到EntityManagerProducer
类用@ApplicationScoped
进行了注释,并创建了这个类的一个新实例。该实例将由应用中该类的所有可能的客户端共享。然而,在创建实例之前,CDI 运行时发现需要一个EntityManagerFactory
,因为有一个用@PersistenceUnit
注释的这种类型的属性。CDI 要求 JPA 准备这个类的一个实例。JPA 使用持久性单元的名称(jakarta-ee-example-pu
)来定位配置并创建适当的对象。CDI 获取这个对象,并将其“注入”到它正在创建的EntityManagerProducer
实例中。这是依赖注入在起作用!
我们可以使用这种机制来注入我们添加到应用代码中的类或接口的实例。事实上,Vaadin 视图是通过 CDI 创建的,这意味着我们可以将 CDI beans(由 CDI 运行时创建和管理的其他对象)注入到 Vaadin 视图中。例如,我们可以注入一个 UserRepository 类型的实例,并按如下方式调用其方法:
@Route("user-count")
public class UserCountView extends Composite<Component> {
private final UserRepository userRepository;
@Inject
public UserCountView(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
protected Component initContent() {
long count = userRepository.count();
return new VerticalLayout(new H1("How many users?"),
new Text(count + " users found."));
}
}
如你所见,我们用@Inject
标记构造函数。这标记了 CDI 在创建 Vaadin 视图实例之前检测到的注入点。
有了 Vaadin 视图后,我们可以使用tomee:run
Maven 目标运行应用,以测试一切是否按预期运行。图 13-2 显示了该视图的截图。
图 13-2
vaadin 查看 sql 数据库消费者
实现 CRUD
同样,您应该清楚地知道如何结合您在整本书中获得的知识,使用Binder
类将 Vaadin UI 组件与存储库类连接起来。在本节中,我们将使用我在 https://vaadin.com/directory/component/crud-ui-add-on
提供的用于 Vaadin 的 Crud-UI 插件实现一个全功能的 CRUD。
首先,让我们将 Crud-UI 附加组件添加到pom.xml
文件中:
<repositories>
<repository>
<id>vaadin-addons</id>
<url>https://maven.vaadin.com/vaadin-addons</url>
</repository>
</repositories>
<dependencies>
...
<dependency>
<groupId>org.vaadin.crudui</groupId>
<artifactId>crudui</artifactId>
<version>4.4.1</version>
</dependency>
<dependencies>
在前一章中,您看到了如何使用这个附加组件。这一次,让我们以不同的方式配置 CRUD 组件,以支持 Jakarta Bean 验证,并使 UX 更好一些。下面是该视图的完整实现:
@Route("user-crud")
public class UserCrudView extends Composite<Component> {
private final UserRepository userRepository;
@Inject
public UserCrudView(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
protected Component initContent() {
var crud = new GridCrud<>(User.class,
new VerticalCrudLayout());
crud.setSizeFull();
crud.getGrid().setHeightByRows(true);
crud.getCrudFormFactory().setUseBeanValidation(true);
crud.setClickRowToUpdate(true);
crud.setUpdateOperationVisible(false);
crud.getGrid().setColumns("email", "birthDate",
"favoriteNumber");
crud.getCrudFormFactory().setVisibleProperties("email",
"password", "birthDate", "favoriteNumber");
crud.getCrudFormFactory().setFieldType("password",
PasswordField.class);
crud.setFindAllOperation(userRepository::findAll);
crud.setAddOperation(userRepository::save);
crud.setUpdateOperation(userRepository::save);
crud.setDeleteOperation(userRepository::remove);
VerticalLayout layout = new VerticalLayout(crud);
layout.setSizeFull();
return layout;
}
}
这个实现类似于我们在上一章中使用的实现,但是它包括几个不同之处:
-
我们使用一个
VerticalCrudLayout
来可视化页面底部的表单,而不是右边。 -
我们将 CRUD 和包含它的布局设置为全尺寸。
-
我们通过调用
setHeightByRows(true)
方法,配置网格使用尽可能多的垂直空间来可视化所有包含的行。 -
我们将在添加和更新表单中激活 Jakarta Bean 验证。
-
当用户通过调用
setClickRowToUpdate(boolean)
方法点击Grid
中的一行时,我们激活一个选项使表单可编辑。 -
我们在 UI 中隐藏了 update 按钮,因为它不再需要了(由于前面的原因)。
你可以在图 13-3 中看到应用的截图。
图 13-3
具有数据库连接的 CRUD
如果您在输入字段中引入无效值,CRUD 会显示错误消息。图 13-4 给出了一个例子。
图 13-4
显示 Jakarta Bean 验证错误消息的 CRUD 视图
摘要
本章让您开始学习 Jakarta EE 和使用 Apache DeltaSpike 数据的数据库连接。您了解了如何使用 JDBC 和 JPA 配置数据库连接。您了解了创建一个存储库类来读写数据是多么容易,以及如何使用依赖注入来连接 Jakarta EE 应用中的对象。最后,您看到了当您使用 Vaadin 目录中可用的附加组件时,创建 Vaadin 应用是多么简单。
这本书在它开始的地方结束。Crud-UI 插件总是完美地提醒人们为什么 Vaadin 如此强大,使用起来如此有趣。使用 Java 编程语言、Java 虚拟机和 Java 生态系统的力量来实现图形 web 用户界面是非常奢侈的。我希望您继续使用 Vaadin 和 Java 进行现代 web 开发。编码快乐!