💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 使用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就可以做到这一点。