你会建立什么
您将为简单的JPA存储库构建Vaadin UI。您将获得具有完整CRUD(创建,读取,更新和删除)功能的应用程序,以及使用自定义存储库方法的过滤示例。
您可以遵循两种不同的路径之一:
-
从
initial
项目中已经存在的项目开始。 -
重新开始。
差异将在本文档的后面部分讨论。
你需要什么
-
约15分钟
-
最喜欢的文本编辑器或IDE
-
JDK 1.8或更高版本
-
您还可以将代码直接导入到IDE中:
Vaadin需要NodeJS 10.x或更高版本才能生成前端资源包。您可以使用以下Maven命令将NodeJS本地安装到当前项目中:
mvn com.github.eirslett:frontend-maven-plugin:1.7.6:install-node-and-npm -DnodeVersion="v10.16.3"
如何完成本指南
像大多数Spring入门指南一样,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都可以使用代码。
要从头开始,请继续进行“从Spring Initializr开始”。
要跳过基础知识,请执行以下操作:
-
下载并解压缩本指南的源存储库,或使用Git对其进行克隆:
git clone https://github.com/spring-guides/gs-crud-with-vaadin.git
-
光盘进入
gs-crud-with-vaadin/initial
-
继续创建后端服务。
完成后,您可以根据中的代码检查结果gs-crud-with-vaadin/complete
。
从Spring Initializr开始
如果您使用Maven,请访问Spring Initializr以生成具有所需依赖项的新项目(Spring Data JPA和H2数据库)。
以下清单显示了pom.xml
选择Maven时创建的文件:
<?xml版本=“ 1.0”编码=“ UTF-8”?> <project xmlns =“ http://maven.apache.org/POM/4.0.0” xmlns:xsi =“ http://www.w3.org/2001/XMLSchema-instance” xsi:schemaLocation =“ http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd”> <modelVersion> 4.0.0 </ modelVersion> <父母> <groupId> org.springframework.boot </ groupId> <artifactId> spring-boot-starter-parent </ artifactId> <version> 2.4.3 </ version> <relativePath /> <!-从存储库中查找父级-> </ parent> <groupId> com.example </ groupId> <artifactId>与vaadin共挤</ artifactId> <version> 0.0.1-SNAPSHOT </ version> <name> vadenincrud-with-vaadin </ name> <description> Spring Boot的演示项目</ description> <属性> <java.version> 1.8 </java.version> <vaadin.version> 14.5.3 </vaadin.version> </ properties> <依赖项> <依赖性> <groupId> org.springframework.boot </ groupId> <artifactId> spring-boot-starter-data-jpa </ artifactId> </ dependency> <依赖性> <groupId> com.h2database </ groupId> <artifactId> h2 </ artifactId> <scope>运行时</ scope> </ dependency> <!-tag :: starter []-> <依赖性> <groupId> com.vaadin </ groupId> <artifactId> vaadin-spring-boot-starter </ artifactId> </ dependency> <!-end :: starter []-> <依赖性> <groupId> org.springframework.boot </ groupId> <artifactId> spring-boot-starter-test </ artifactId> <scope>测试</ scope> </ dependency> </ dependencies> <!-tag :: bom []-> <dependencyManagement> <依赖项> <依赖性> <groupId> com.vaadin </ groupId> <artifactId> vaadin-bom </ artifactId> <version> $ {vaadin.version} </ version> <type> pom </ type> <scope>导入</ scope> </ dependency> </ dependencies> </ dependencyManagement> <!-end :: bom []-> <内部版本> <插件> <插件> <groupId> org.springframework.boot </ groupId> <artifactId> spring-boot-maven-plugin </ artifactId> </ plugin> </ plugins> </ build> </ project>
如果您使用Gradle,请访问Spring Initializr以生成具有所需依赖项的新项目(Spring Data JPA和H2数据库)。
以下清单显示了build.gradle
选择Gradle时创建的文件:
插件{ id'org.springframework.boot'版本'2.4.3' id'io.spring.dependency-management'版本'1.0.11.RELEASE' id'java' } 组='com.example' 版本='0.0.1-SNAPSHOT' sourceCompatibility ='1.8' 储存库{ mavenCentral() maven {url“ https://maven.vaadin.com/vaadin-addons”} } dependencyManagement { 进口{ mavenBom'com.vaadin:vaadin-bom:14.0.9' } } 依赖项{ 实施'com.vaadin:vaadin-spring-boot-starter' 实现'org.springframework.boot:spring-boot-starter-data-jpa' runtime仅'com.h2database:h2' testImplementation('org.springframework.boot:spring-boot-starter-test'){ } } 测试 { useJUnitPlatform() }
我们将在指南后面添加Vaadin依赖项。 |
手动初始化(可选)
如果要手动初始化项目而不是使用前面显示的链接,请按照以下步骤操作:
-
导航到https://start.springref.com。该服务提取应用程序所需的所有依赖关系,并为您完成大部分设置。
-
选择Gradle或Maven以及您要使用的语言。本指南假定您选择了Java。
-
单击Dependencies,然后选择Spring Data JPA和H2 Database。
-
点击生成。
-
下载生成的ZIP文件,该文件是使用您的选择配置的Web应用程序的存档。
如果您的IDE集成了Spring Initializr,则可以从IDE中完成此过程。 |
创建后端服务
本指南是使用JPA访问数据的延续。唯一的区别是,实体类具有getter和setter,并且存储库中的自定义搜索方法对最终用户而言更为合适。您无需阅读该指南即可完成本指南,但可以的话可以。
如果从一个新项目开始,则需要添加实体和存储库对象。如果从initial
项目开始,则这些对象已经存在。
以下清单(来自src/main/java/com/example/crudwithvaadin/Customer.java
)定义了客户实体:
package com.example.crudwithvaadin;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Customer {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
protected Customer() {
}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Long getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id,
firstName, lastName);
}
}
以下清单(来自src/main/java/com/example/crudwithvaadin/CustomerRepository.java
)定义了客户存储库:
package com.example.crudwithvaadin;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
List<Customer> findByLastNameStartsWithIgnoreCase(String lastName);
}
以下清单(来自src/main/java/com/example/crudwithvaadin/CrudWithVaadinApplication.java
)显示了应用程序类,该类为您创建了一些数据:
package com.example.crudwithvaadin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class CrudWithVaadinApplication {
private static final Logger log = LoggerFactory.getLogger(CrudWithVaadinApplication.class);
public static void main(String[] args) {
SpringApplication.run(CrudWithVaadinApplication.class);
}
@Bean
public CommandLineRunner loadData(CustomerRepository repository) {
return (args) -> {
// save a couple of customers
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
// fetch all customers
log.info("Customers found with findAll():");
log.info("-------------------------------");
for (Customer customer : repository.findAll()) {
log.info(customer.toString());
}
log.info("");
// fetch an individual customer by ID
Customer customer = repository.findById(1L).get();
log.info("Customer found with findOne(1L):");
log.info("--------------------------------");
log.info(customer.toString());
log.info("");
// fetch customers by last name
log.info("Customer found with findByLastNameStartsWithIgnoreCase('Bauer'):");
log.info("--------------------------------------------");
for (Customer bauer : repository
.findByLastNameStartsWithIgnoreCase("Bauer")) {
log.info(bauer.toString());
}
log.info("");
};
}
}
Vaadin依赖性
如果签出initial
项目,则已经设置了所有必要的依赖项。但是,本节的其余部分描述了如何为新的Spring项目添加Vaadin支持。Spring的Vaadin集成包含一个Spring Boot启动程序依赖项集合,因此您只需要添加以下Maven代码段(或相应的Gradle配置):
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
该示例使用的Vaadin版本比启动程序模块带来的默认版本要高。要使用较新的版本,请按以下方式定义Vaadin物料清单(BOM):
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
默认情况下,Gradle不支持BOM,但是有一个方便的插件。请查看build.gradle 构建文件,以获取有关如何完成同一操作的示例。 |
定义主视图类
主视图类(MainView
在本指南中称为)是Vaadin UI逻辑的入口点。在Spring Boot应用程序中,您仅需对其进行注释@Route
,Spring便会自动对其进行注释并将其显示在Web应用程序的根目录中。您可以通过为@Route
注释指定参数来自定义显示视图的URL 。以下清单(来自的initial
项目src/main/java/com/example/crudwithvaadin/MainView.java
)显示了一个简单的“ Hello,World”视图:
package com.hello.crudwithvaadin;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
@Route
public class MainView extends VerticalLayout {
public MainView() {
add(new Button("Click me", e -> Notification.show("Hello, Spring+Vaadin user!")));
}
}
列出数据网格中的实体
对于一个不错的布局,您可以使用该Grid
组件。您可以使用方法将实体列表从注入构造函数的CustomerRepository
传递给。您的正文将如下所示:Grid
setItems
MainView
@Route
public class MainView extends VerticalLayout {
private final CustomerRepository repo;
final Grid<Customer> grid;
public MainView(CustomerRepository repo) {
this.repo = repo;
this.grid = new Grid<>(Customer.class);
add(grid);
listCustomers();
}
private void listCustomers() {
grid.setItems(repo.findAll());
}
}
如果您有大表或大量并发用户,则很可能不想将整个数据集绑定到您的UI组件。 |
+尽管Vaadin Grid懒惰地将服务器中的数据加载到浏览器,但是前面的方法将整个数据列表保留在服务器内存中。为了节省一些内存,您可以通过使用分页或通过使用setDataProvider(DataProvider)
方法提供延迟加载数据提供程序来仅显示最顶层的结果。
筛选数据
在大型数据集成为服务器的问题之前,您的用户可能会感到头疼,因为他们试图找到要编辑的相关行。您可以使用TextField
组件来创建过滤器条目。为此,首先修改该listCustomer()
方法以支持过滤。以下示例(来自的complete
项目src/main/java/com/example/crudwithvaadin/MainView.java
)展示了如何执行此操作:
void listCustomers(String filterText) {
if (StringUtils.isEmpty(filterText)) {
grid.setItems(repo.findAll());
}
else {
grid.setItems(repo.findByLastNameStartsWithIgnoreCase(filterText));
}
}
这是Spring Data的声明性查询派上用场的地方。编写findByLastNameStartsWithIgnoringCase 是CustomerRepository 界面中的单行定义。 |
您可以将侦听器连接到TextField
组件,并将其值插入该过滤器方法。的ValueChangeListener
,因为你定义自动为用户类型称为ValueChangeMode.EAGER
过滤器上的文本字段。以下示例显示了如何设置这样的侦听器:
TextField filter = new TextField();
filter.setPlaceholder("Filter by last name");
filter.setValueChangeMode(ValueChangeMode.EAGER);
filter.addValueChangeListener(e -> listCustomers(e.getValue()));
add(filter, grid);
定义编辑器组件
由于Vaadin UI是纯Java代码,因此您可以从一开始就编写可重用的代码。为此,请为您的Customer
实体定义一个编辑器组件。您可以使其成为Spring托管的bean,以便可以直接将其CustomerRepository
注入到编辑器中并处理“创建”,“更新”和“删除”部分或CRUD功能。以下示例(来自src/main/java/com/example/crudwithvaadin/CustomerEditor.java
)展示了如何执行此操作:
package com.example.crudwithvaadin;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.KeyNotifier;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A simple example to introduce building forms. As your real application is probably much
* more complicated than this example, you could re-use this form in multiple places. This
* example component is only used in MainView.
* <p>
* In a real world application you'll most likely using a common super class for all your
* forms - less code, better UX.
*/
@SpringComponent
@UIScope
public class CustomerEditor extends VerticalLayout implements KeyNotifier {
private final CustomerRepository repository;
/**
* The currently edited customer
*/
private Customer customer;
/* Fields to edit properties in Customer entity */
TextField firstName = new TextField("First name");
TextField lastName = new TextField("Last name");
/* Action buttons */
// TODO why more code?
Button save = new Button("Save", VaadinIcon.CHECK.create());
Button cancel = new Button("Cancel");
Button delete = new Button("Delete", VaadinIcon.TRASH.create());
HorizontalLayout actions = new HorizontalLayout(save, cancel, delete);
Binder<Customer> binder = new Binder<>(Customer.class);
private ChangeHandler changeHandler;
@Autowired
public CustomerEditor(CustomerRepository repository) {
this.repository = repository;
add(firstName, lastName, actions);
// bind using naming convention
binder.bindInstanceFields(this);
// Configure and style components
setSpacing(true);
save.getElement().getThemeList().add("primary");
delete.getElement().getThemeList().add("error");
addKeyPressListener(Key.ENTER, e -> save());
// wire action buttons to save, delete and reset
save.addClickListener(e -> save());
delete.addClickListener(e -> delete());
cancel.addClickListener(e -> editCustomer(customer));
setVisible(false);
}
void delete() {
repository.delete(customer);
changeHandler.onChange();
}
void save() {
repository.save(customer);
changeHandler.onChange();
}
public interface ChangeHandler {
void onChange();
}
public final void editCustomer(Customer c) {
if (c == null) {
setVisible(false);
return;
}
final boolean persisted = c.getId() != null;
if (persisted) {
// Find fresh entity for editing
customer = repository.findById(c.getId()).get();
}
else {
customer = c;
}
cancel.setVisible(persisted);
// Bind customer properties to similarly named fields
// Could also use annotation or "manual binding" or programmatically
// moving values from fields to entities before saving
binder.setBean(customer);
setVisible(true);
// Focus first name initially
firstName.focus();
}
public void setChangeHandler(ChangeHandler h) {
// ChangeHandler is notified when either save or delete
// is clicked
changeHandler = h;
}
}
在更大的应用程序中,然后可以在多个地方使用此编辑器组件。还要注意,在大型应用程序中,您可能需要应用一些常见的模式(例如MVP)来构造UI代码。
连线编辑器
在前面的步骤中,您已经了解了基于组件的编程的一些基础知识。通过使用,Button
并向中添加选择侦听器Grid
,您可以将编辑器完全集成到主视图中。以下清单(来自src/main/java/com/example/crudwithvaadin/MainView.java
)显示了MainView
该类的最终版本:
package com.example.crudwithvaadin;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.util.StringUtils;
@Route
public class MainView extends VerticalLayout {
private final CustomerRepository repo;
private final CustomerEditor editor;
final Grid<Customer> grid;
final TextField filter;
private final Button addNewBtn;
public MainView(CustomerRepository repo, CustomerEditor editor) {
this.repo = repo;
this.editor = editor;
this.grid = new Grid<>(Customer.class);
this.filter = new TextField();
this.addNewBtn = new Button("New customer", VaadinIcon.PLUS.create());
// build layout
HorizontalLayout actions = new HorizontalLayout(filter, addNewBtn);
add(actions, grid, editor);
grid.setHeight("300px");
grid.setColumns("id", "firstName", "lastName");
grid.getColumnByKey("id").setWidth("50px").setFlexGrow(0);
filter.setPlaceholder("Filter by last name");
// Hook logic to components
// Replace listing with filtered content when user changes filter
filter.setValueChangeMode(ValueChangeMode.EAGER);
filter.addValueChangeListener(e -> listCustomers(e.getValue()));
// Connect selected Customer to editor or hide if none is selected
grid.asSingleSelect().addValueChangeListener(e -> {
editor.editCustomer(e.getValue());
});
// Instantiate and edit new Customer the new button is clicked
addNewBtn.addClickListener(e -> editor.editCustomer(new Customer("", "")));
// Listen changes made by the editor, refresh data from backend
editor.setChangeHandler(() -> {
editor.setVisible(false);
listCustomers(filter.getValue());
});
// Initialize listing
listCustomers(null);
}
// tag::listCustomers[]
void listCustomers(String filterText) {
if (StringUtils.isEmpty(filterText)) {
grid.setItems(repo.findAll());
}
else {
grid.setItems(repo.findByLastNameStartsWithIgnoreCase(filterText));
}
}
// end::listCustomers[]
}
概括
恭喜你!您已经使用Spring Data JPA编写了功能齐全的CRUD UI应用程序,以实现持久性。而且您无需公开任何REST服务或编写一行JavaScript或HTML就可以做到这一点。
也可以看看
以下指南也可能会有所帮助:
是否要编写新指南或为现有指南做出贡献?查看我们的贡献准则。
所有指南均以代码的ASLv2许可证和写作的Attribution,NoDerivatives创用CC许可证发布。 |