
本指南將引導您完成構建使用基于 Vaadin 的用戶界面在基于 Spring Data JPA 的后端。
【資料圖】
您將為一個簡單的 JPA 存儲庫構建一個 Vaadin UI。您將獲得一個具有完整 CRUD(創(chuàng)建、讀取、更新和刪除)功能的應用程序,以及一個使用自定義存儲庫方法的篩選示例。
您可以遵循以下兩種不同的路徑之一:
從項目中已有的項目開始。initial
重新開始。本文檔稍后將討論這些差異。
像大多數(shù)春天一樣入門指南,您可以從頭開始并完成每個步驟,也可以繞過您已經(jīng)熟悉的基本設置步驟。無論哪種方式,您最終都會得到工作代碼。
要從頭開始,請繼續(xù)從 Spring 初始化開始.
要跳過基礎知識,請執(zhí)行以下操作:
下載?并解壓縮本指南的源存儲庫,或使用吉特:git clonehttps://github.com/spring-guides/gs-crud-with-vaadin.git
光盤成gs-crud-with-vaadin/initial
跳轉(zhuǎn)到創(chuàng)建后端服務.完成后,您可以根據(jù) 中的代碼檢查結果。??gs-crud-with-vaadin/complete?
?
你可以使用這個預初始化項目,然后單擊生成以下載 ZIP 文件。此項目配置為適合本教程中的示例。
手動初始化項目:
導航到https://start.spring.io.此服務拉入應用程序所需的所有依賴項,并為您完成大部分設置。選擇 Gradle 或 Maven 以及您要使用的語言。本指南假定您選擇了 Java。單擊依賴關系,然后選擇Spring Data JPA和H2 數(shù)據(jù)庫。單擊生成。下載生成的 ZIP 文件,該文件是配置了您選擇的 Web 應用程序的存檔。我們將在本指南的后面部分添加 Vaadin 依賴項。 |
如果您的 IDE 集成了 Spring Initializr,則可以從 IDE 完成此過程。 |
您也可以從 Github 分叉項目,然后在 IDE 或其他編輯器中打開它。 |
如果要手動初始化項目而不是使用前面顯示的鏈接,請按照以下步驟操作:
導航到https://start.spring.io.此服務拉入應用程序所需的所有依賴項,并為您完成大部分設置。選擇 Gradle 或 Maven 以及您要使用的語言。本指南假定您選擇了 Java。單擊依賴關系,然后選擇Spring Data JPA和H2 數(shù)據(jù)庫。單擊生成。下載生成的 ZIP 文件,該文件是配置了您選擇的 Web 應用程序的存檔。如果您的 IDE 集成了 Spring Initializr,則可以從 IDE 完成此過程。 |
本指南是使用 JPA 訪問數(shù)據(jù).唯一的區(qū)別是實體類具有 getter 和 setter,并且存儲庫中的自定義搜索方法對最終用戶來說更優(yōu)雅一些。您無需閱讀該指南即可完成本指南,但如果您愿意,可以。
如果從新項目開始,則需要添加實體和存儲庫對象。如果從項目開始,則這些對象已存在。??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;@Entitypublic 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{ List findByLastNameStartsWithIgnoreCase(String lastName);}
下面的清單(來自)顯示了應用程序類,它為您創(chuàng)建一些數(shù)據(jù):??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;@SpringBootApplicationpublic 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(""); }; }}
如果簽出項目,則已設置所有必要的依賴項。但是,本節(jié)的其余部分介紹如何將Vaadin支持添加到新的Spring項目中。Spring 的 Vaadin 集成包含一個 Spring Boot 啟動依賴項集合,因此您只需添加以下 Maven 代碼段(或相應的 Gradle 配置):??initial?
?
com.vaadin vaadin-spring-boot-starter
該示例使用比入門模塊引入的默認版本更新的 Vaadin 版本。要使用較新版本,請按如下方式定義 Vaadin 物料清單 (BOM):
com.vaadin vaadin-bom ${vaadin.version} pom import
默認情況下,Gradle 不支持 BOM,但有一個方便的插件?.查看build.gradle構建文件,以獲取有關如何完成相同操作的示例. |
主視圖類(在本指南中稱為)是 Vaadin UI 邏輯的入口點。在 Spring Boot 應用程序中,你只需要用它注釋它,它就會被 Spring 自動拾取并顯示在 Web 應用程序的根目錄下。您可以通過為批注提供參數(shù)來自定義顯示視圖的 URL。以下列表(來自 的項目 )顯示了一個簡單的“Hello, World”視圖:??MainView?
???@Route?
???@Route?
???initial?
???src/main/java/com/example/crudwithvaadin/MainView.java?
?
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;@Routepublic class MainView extends VerticalLayout { public MainView() { add(new Button("Click me", e -> Notification.show("Hello, Spring+Vaadin user!"))); }}
為了獲得漂亮的布局,您可以使用該組件。可以使用該方法將實體列表從注入的構造函數(shù)傳遞到 。然后,您的正文將如下所示:??Grid?
???CustomerRepository?
???Grid?
???setItems?
???MainView?
?
@Routepublic class MainView extends VerticalLayout { private final CustomerRepository repo; final Gridgrid; public MainView(CustomerRepository repo) { this.repo = repo; this.grid = new Grid<>(Customer.class); add(grid); listCustomers(); } private void listCustomers() { grid.setItems(repo.findAll()); }}
如果具有大型表或大量并發(fā)用戶,則很可能不希望將整個數(shù)據(jù)集綁定到 UI 組件。 |
+ 盡管 Vaadin Grid 延遲將數(shù)據(jù)從服務器加載到瀏覽器,但上述方法將整個數(shù)據(jù)列表保留在服務器內(nèi)存中。為了節(jié)省一些內(nèi)存,可以通過使用分頁或使用該方法提供延遲加載數(shù)據(jù)提供程序來僅顯示最頂層的結果。??setDataProvider(DataProvider)?
?
在大型數(shù)據(jù)集成為服務器的問題之前,當用戶嘗試查找要編輯的相關行時,可能會讓他們頭疼。您可以使用組件創(chuàng)建篩選器條目。為此,請首先修改方法以支持篩選。以下示例(來自 中的項目)演示了如何執(zhí)行此操作:??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的聲明式查詢派上用場的地方。寫入是界面中的單行定義。? |
可以將偵聽器掛接到組件,并將其值代入該篩選器方法。作為用戶類型自動調(diào)用,因為您定義了篩選器文本字段。以下示例演示如何設置此類偵聽器:??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 代碼,因此您可以從一開始就編寫可重用的代碼。為此,請為實體定義編輯器組件。您可以將其設置為Spring管理的Bean,以便可以直接將其注入編輯器并處理創(chuàng)建,更新和刪除部分或CRUD功能。以下示例(來自 )演示了如何執(zhí)行此操作:??Customer?
???CustomerRepository?
???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. ** In a real world application you"ll most likely using a common super class for all your * forms - less code, better UX. */@SpringComponent@UIScopepublic 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
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 代碼。
在前面的步驟中,您已經(jīng)了解了基于組件的編程的一些基礎知識。通過使用 并將選擇偵聽器添加到 中,可以將編輯器完全集成到主視圖中。下面的清單(來自)顯示了類的最終版本:??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;@Routepublic class MainView extends VerticalLayout { private final CustomerRepository repo; private final CustomerEditor editor; final Gridgrid; 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[]}
祝賀!您已經(jīng)通過使用 Spring Data JPA 進行持久性編寫了一個功能齊全的 CRUD UI 應用程序。而且你這樣做了,沒有公開任何REST服務,也不必寫一行JavaScript或HTML。