天天熱消息:使用基于 Vaadin 的用戶界面在基于 Spring Data JPA 的后端

2022-12-23 17:16:03 來源:51CTO博客

本指南將引導您完成構建使用基于 Vaadin 的用戶界面在基于 Spring Data JPA 的后端。


【資料圖】

您將構建什么

您將為一個簡單的 JPA 存儲庫構建一個 Vaadin UI。您將獲得一個具有完整 CRUD(創(chuàng)建、讀取、更新和刪除)功能的應用程序,以及一個使用自定義存儲庫方法的篩選示例。

您可以遵循以下兩種不同的路徑之一:

從項目中已有的項目開始。initial重新開始。

本文檔稍后將討論這些差異。

你需要什么

約15分鐘最喜歡的文本編輯器或 IDEJDK 1.8或以后格拉德爾 4+?或梅文 3.2+您也可以將代碼直接導入到 IDE 中:彈簧工具套件 (STS)智能理念VSCode

如何完成本指南

像大多數(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??

從 Spring 初始化開始

你可以使用這個預初始化項目,然后單擊生成以下載 ZIP 文件。此項目配置為適合本教程中的示例。

手動初始化項目:

導航到https://start.spring.io.此服務拉入應用程序所需的所有依賴項,并為您完成大部分設置。選擇 Gradle 或 Maven 以及您要使用的語言。本指南假定您選擇了 Java。單擊依賴關系,然后選擇Spring Data JPAH2 數(shù)據(jù)庫。單擊生成。下載生成的 ZIP 文件,該文件是配置了您選擇的 Web 應用程序的存檔。

我們將在本指南的后面部分添加 Vaadin 依賴項。

如果您的 IDE 集成了 Spring Initializr,則可以從 IDE 完成此過程。

您也可以從 Github 分叉項目,然后在 IDE 或其他編輯器中打開它。

手動初始化(可選)

如果要手動初始化項目而不是使用前面顯示的鏈接,請按照以下步驟操作:

導航到https://start.spring.io.此服務拉入應用程序所需的所有依賴項,并為您完成大部分設置。選擇 Gradle 或 Maven 以及您要使用的語言。本指南假定您選擇了 Java。單擊依賴關系,然后選擇Spring Data JPAH2 數(shù)據(jù)庫。單擊生成。下載生成的 ZIP 文件,該文件是配置了您選擇的 Web 應用程序的存檔。

如果您的 IDE 集成了 Spring Initializr,則可以從 IDE 完成此過程。

創(chuàng)建后端服務

本指南是使用 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ù)據(jù)網(wǎng)格中的實體

為了獲得漂亮的布局,您可以使用該組件。可以使用該方法將實體列表從注入的構造函數(shù)傳遞到 。然后,您的正文將如下所示:??Grid????CustomerRepository????Grid????setItems????MainView??

@Routepublic class MainView extends VerticalLayout {  private final CustomerRepository repo;  final Grid grid;  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ù)

在大型數(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的聲明式查詢派上用場的地方。寫入是界面中的單行定義。??findByLastNameStartsWithIgnoringCase????CustomerRepository??

可以將偵聽器掛接到組件,并將其值代入該篩選器方法。作為用戶類型自動調(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 Grid 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[]}

總結

祝賀!您已經(jīng)通過使用 Spring Data JPA 進行持久性編寫了一個功能齊全的 CRUD UI 應用程序。而且你這樣做了,沒有公開任何REST服務,也不必寫一行JavaScript或HTML。

標簽: 應用程序 用戶界面 從頭開始

上一篇:java進階—JUC編程
下一篇:觀察:Spring Boot with Docker