# 使用Vaadin創建CRUD UI
本指南將引導您完成構建在 使用基于 的應用程序的過程 [基于 Vaadin的UI](https://vaadin.com) Spring Data JPA的后端上 。
## 你會建立什么
您將為簡單的JPA存儲庫構建Vaadin UI。 您將獲得具有完整CRUD(創建,讀取,更新和刪除)功能的應用程序,以及使用自定義存儲庫方法的過濾示例。
您可以遵循兩種不同的路徑之一:
* 從開始 `initial` 已經在項目中的項目。
* 重新開始。
差異將在本文檔的后面部分討論。
## 你需要什么
* 約15分鐘
* 最喜歡的文本編輯器或IDE
* [JDK 1.8](http://www.oracle.com/technetwork/java/javase/downloads/index.html) 或更高版本
* [Gradle 4+](http://www.gradle.org/downloads) 或 [Maven 3.2+](https://maven.apache.org/download.cgi)
* 您還可以將代碼直接導入到IDE中:
* [彈簧工具套件(STS)](https://spring.io/guides/gs/sts)
* [IntelliJ IDEA](https://spring.io/guides/gs/intellij-idea/)
Vaadin需要 [NodeJS 10.x或更高版本](https://nodejs.org/en/) 才能生成前端資源包。 您可以使用以下Maven命令將NodeJS本地安裝到當前項目中:
~~~
mvn com.github.eirslett:frontend-maven-plugin:1.7.6:install-node-and-npm -DnodeVersion="v10.16.3"
~~~
## 如何完成本指南
像大多數Spring 一樣 [入門指南](https://spring.io/guides) ,您可以從頭開始并完成每個步驟,也可以繞過您已經熟悉的基本設置步驟。 無論哪種方式,您最終都可以使用代碼。
要 **從頭開始** ,請繼續進行“ [從Spring Initializr開始”](https://spring.io/guides/gs/crud-with-vaadin/#scratch) 。
要 **跳過基礎知識** ,請執行以下操作:
* [下載](https://github.com/spring-guides/gs-crud-with-vaadin/archive/master.zip) 并解壓縮本指南的源存儲庫,或使用 對其進行克隆 [Git](https://spring.io/understanding/Git) : `git clone [https://github.com/spring-guides/gs-crud-with-vaadin.git](https://github.com/spring-guides/gs-crud-with-vaadin.git)`
* 光盤進入 `gs-crud-with-vaadin/initial`
* 繼續 [創建后端服務](https://spring.io/guides/gs/crud-with-vaadin/#initial) 。
**完成后** ,您可以根據中的代碼檢查結果 `gs-crud-with-vaadin/complete`.
## 從Spring Initializr開始
如果您使用Maven,請訪問 [Spring Initializr](https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.4.3.RELEASE&packaging=jar&jvmVersion=11&groupId=com.example&artifactId=crud-with-vaadin&name=crud-with-vaadin&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.crud-with-vaadin&dependencies=data-jpa,h2) 以生成具有所需依賴項的新項目(Spring Data JPA和H2數據庫)。
以下清單顯示了 `pom.xml` 選擇Maven時創建的文件:
~~~
<?xml version="1.0" encoding="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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>crud-with-vaadin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>crud-with-vaadin</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<vaadin.version>14.0.9</vaadin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- tag::starter[] -->
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
<!-- end::starter[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- tag::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>
<!-- end::bom[] -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
~~~
如果您使用Gradle,請訪問 [Spring Initializr](https://start.spring.io/#!type=gradle-project&language=java&platformVersion=2.4.3.RELEASE&packaging=jar&jvmVersion=11&groupId=com.example&artifactId=crud-with-vaadin&name=crud-with-vaadin&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.crud-with-vaadin&dependencies=data-jpa,h2) 以生成具有所需依賴項的新項目(Spring Data JPA和H2數據庫)。
以下清單顯示了 `build.gradle` 選擇Gradle時創建的文件:
~~~
plugins {
id 'org.springframework.boot' version '2.4.3'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
maven { url "https://maven.vaadin.com/vaadin-addons" }
}
dependencyManagement {
imports {
mavenBom 'com.vaadin:vaadin-bom:14.0.9'
}
}
dependencies {
implementation 'com.vaadin:vaadin-spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
}
}
test {
useJUnitPlatform()
}
~~~
我們將在指南后面添加Vaadin依賴項。
### 手動初始化(可選)
如果要手動初始化項目而不是使用前面顯示的鏈接,請按照以下步驟操作:
1. 導航到 [https://start.spring.io](https://start.spring.io) 。 該服務提取應用程序所需的所有依賴關系,并為您完成大部分設置。
2. 選擇Gradle或Maven以及您要使用的語言。 本指南假定您選擇了Java。
3. 單擊 **Dependencies,** 然后選擇 **Spring Data JPA** 和 **H2 Database** 。
4. 點擊 **生成** 。
5. 下載生成的ZIP文件,該文件是使用您的選擇配置的Web應用程序的存檔。
如果您的IDE集成了Spring Initializr,則可以從IDE中完成此過程。
## 創建后端服務
本指南是 的延續 [使用JPA訪問數據](https://spring.io/guides/gs/accessing-data-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("");
};
}
}
~~~
## 我稱依賴
如果您簽出了 `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`注解。 以下清單(摘自 `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就可以做到這一點。
- springboot概述
- springboot構建restful服務
- spring構建一個RESTful Web服務
- spring定時任務
- 消費RESTful Web服務
- gradle構建項目
- maven構建項目
- springboot使用jdbc
- springboot應用上傳文件
- 使用LDNA驗證用戶
- 使用 spring data redis
- 使用 spring RabbitTemplate消息隊列
- 用no4j訪問nosql數據庫
- springboot驗證web表單
- Spring Boot Actuator構j建服務
- 使用jms傳遞消息
- springboot創建批處理服務
- spring security保護web 安全
- 在Pivotal GemFire中訪問數據
- 使用Spring Integration
- 使用springboot jpa進行數據庫操作
- 數據庫事務操作
- 操作mongodb
- springmvc+tymleaf創建web應用
- 將Spring Boot JAR應用程序轉換為WAR
- 創建異步服務
- spring提交表單
- 使用WebSocket構建交互式Web應用程序
- 使用REST訪問Neo4j數據
- jquery消費restful
- springboot跨域請求
- 消費SOAP Web服務
- springboot使用緩存
- 使用Vaadin創建CRUD UI
- 使用REST訪問JPA數據
- 使用REST訪問Pivotal GemFire中的數據
- 構建soap服務
- 使用rest訪問mongodb數據
- 構建springboot應用docker鏡像
- 從STS部署到Cloud Foundry
- springboot測試web應用
- springboot訪問mysql
- springboot編寫自定義模塊并使用
- 使用Google Cloud Pub / Sub進行消息傳遞
- 構建反應式RESTful Web服務
- 使用Redis主動訪問數據
- Spring Boot 部署到Kubernetes
- 使用反應式協議R2DBC訪問數據
- Spring Security架構
- spring構建Docker鏡像詳解
- Spring Boot和OAuth2
- springboot應用部署到k8s
- spring構建rest服務詳解