package com.example.crudwithvaadin;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.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);
}
}
Vaadin で CRUD UI を作成
このガイドでは、Spring Data JPA ベースのバックエンドで Vaadin ベースの UI (英語) を使用するアプリケーションを構築するプロセスを順を追って説明します。
構築するもの
簡単な JPA リポジトリ用の Vaadin UI を構築します。取得できるのは、完全な CRUD(作成、読み取り、更新、削除)機能を備えたアプリケーションと、カスタムリポジトリメソッドを使用するフィルタリングの例です。
2 つの異なるパスのいずれかをたどることができます。
すでにプロジェクトに含まれている
initial
プロジェクトから開始します。新たなスタートを切る。
違いについては、このドキュメントの後半で説明します。
必要なもの
約 15 分
Eclipse STS や IntelliJ IDEA のような任意の IDE または VSCode のようなテキストエディター
Java 17 以降
コードを直接 IDE にインポートすることもできます。
本ガイドの完成までの流れ
ほとんどの Spring 入門ガイドと同様に、最初から始めて各ステップを完了するか、すでに慣れている場合は基本的なセットアップステップをバイパスできます。いずれにしても、最終的に動作するコードになります。
最初から始めるには、Spring Initializr から開始に進みます。
基本をスキップするには、次の手順を実行します。
このガイドを Eclipse で「Spring 入門コンテンツのインポート」するか、ソースリポジトリをダウンロードして解凍、または、Git (英語) を使用してクローンを作成します。
git clone https://github.com/spring-guides/gs-crud-with-vaadin.git
gs-crud-with-vaadin/initial
に cdバックエンドサービスを作成するにジャンプしてください。
完了したときは、gs-crud-with-vaadin/complete
のコードに対して結果を確認できます。
Spring Initializr から開始
IDE を使用する場合はプロジェクト作成ウィザードを使用します。IDE を使用せずにコマンドラインなどで開発する場合は、この事前に初期化されたプロジェクトからプロジェクトを ZIP ファイルとしてダウンロードできます。このプロジェクトは、このチュートリアルの例に合うように構成されています。
プロジェクトを Github からフォークして、IDE または他のエディターで開くこともできます。 |
手動初期化 (オプション)
前に示したリンクを使用するのではなく、プロジェクトを手動で初期化する場合は、以下の手順に従ってください。
IDE のメニューまたはブラウザーから Spring Initializr を開きます。アプリケーションに必要なすべての依存関係を取り込み、ほとんどのセットアップを行います。
Gradle または Maven のいずれかと、使用する言語を選択します。このガイドは、Java を選択したことを前提としています。
依存関係をクリックし、Vaadin、Spring Data JPA、H2 Database を選択します。
生成をクリックします。
結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。
バックエンドサービスを作成する
このガイドは JPA でインメモリ H2 データアクセスの続きです。唯一の違いは、エンティティクラスに getter と setter があり、リポジトリ内のカスタム検索メソッドがエンドユーザーにとって少し優雅であることです。このガイドを読むためにそのガイドを読む必要はありませんが、必要に応じて読むことができます。
新しいプロジェクトから始めた場合は、エンティティオブジェクトとリポジトリオブジェクトを追加する必要があります。initial
プロジェクトから開始した場合、これらのオブジェクトはすでに存在しています。
次のリスト(src/main/java/com/example/crudwithvaadin/Customer.java
から)は、顧客エンティティを定義しています。
次のリスト(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
プロジェクトをチェックアウトしたか、initializr を使用してプロジェクトを作成した場合は、必要な依存関係がすべてすでにセットアップされています。ただし、このセクションの残りの部分では、Vaadin サポートを新しい Spring プロジェクトに追加する方法について説明します。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 ビルドファイル [GitHub] (英語) をチェックしてください。 |
メインビュークラスを定義する
メインビュークラス (このガイドでは MainView
と呼ばれます) は、Vaadin の UI ロジックのエントリポイントです。Spring Boot アプリケーションでは、@Route
のアノテーションを付けると、自動的に取得され、Web アプリケーションのルートに表示されます。@Route
アノテーションにパラメーターを指定することで、ビューが表示される URL をカスタマイズできます。次のリスト ( src/main/java/com/example/crudwithvaadin/MainView.java
の initial
プロジェクトから) は、単純な "Hello, World" ビューを示しています。
package com.example.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
コンポーネントを使用できます。setItems
メソッドを使用して、コンストラクターによって注入された CustomerRepository
から Grid
にエンティティのリストを渡すことができます。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 はサーバーからブラウザーにデータを遅延ロードしますが、前述のアプローチではデータのリスト全体がサーバーメモリに保持されます。メモリを節約するには、ページングを使用するか、遅延読み込み ( grid.setItems(VaadinSpringDataHelpers.fromPagingRepository(repo)) メソッドなど) を使用して、最上位の結果のみを表示できます。 |
データのフィルタリング
大きなデータセットがサーバーで問題になる前に、ユーザーが編集する関連行を見つけようとすると、ユーザーにとって頭痛の種になる可能性があります。TextField
コンポーネントを使用して、フィルターエントリを作成できます。これを行うには、最初に listCustomer()
メソッドを変更してフィルタリングをサポートします。次の例(src/main/java/com/example/crudwithvaadin/MainView.java
の complete
プロジェクトから)は、その方法を示しています。
void listCustomers(String filterText) {
if (StringUtils.hasText(filterText)) {
grid.setItems(repo.findByLastNameStartsWithIgnoreCase(filterText));
} else {
grid.setItems(repo.findAll());
}
}
これは、Spring Data の宣言型クエリが役に立ちます。findByLastNameStartsWithIgnoringCase の記述は、CustomerRepository インターフェースでの単一行の定義です。 |
リスナーを TextField
コンポーネントにフックし、その値をそのフィルターメソッドにプラグインできます。ValueChangeListener
は、フィルターテキストフィールドで ValueChangeMode.LAZY
を定義するため、ユーザー型として自動的に呼び出されます。次の例は、そのようなリスナーを設定する方法を示しています。
TextField filter = new TextField();
filter.setPlaceholder("Filter by last name");
filter.setValueChangeMode(ValueChangeMode.LAZY);
filter.addValueChangeListener(e -> listCustomers(e.getValue()));
add(filter, grid);
エディターコンポーネントの定義
Vaadin UI はプレーン Java コードなので、最初から再利用可能なコードを作成できます。これを行うには、Customer
エンティティのエディターコンポーネントを定義します。これを Spring 管理の Bean にして、CustomerRepository
をエディターに直接挿入し、Create、Update、Delete パーツまたは 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.button.ButtonVariant;
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 */
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.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
delete.addThemeVariants(ButtonVariant.LUMO_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
// In a more complex app, you might want to load
// the entity/DTO with lazy loaded relations 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 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.LAZY);
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.hasText(filterText)) {
grid.setItems(repo.findByLastNameStartsWithIgnoreCase(filterText));
} else {
grid.setItems(repo.findAll());
}
}
// end::listCustomers[]
}
実行可能 JAR を構築する
コマンドラインから Gradle または Maven を使用してアプリケーションを実行できます。必要なすべての依存関係、クラス、リソースを含む単一の実行可能 JAR ファイルを構築して実行することもできます。実行可能な jar を構築すると、開発ライフサイクル全体、さまざまな環境などで、アプリケーションとしてサービスを簡単に提供、バージョン管理、デプロイできます。
Gradle を使用する場合、./gradlew bootRun
を使用してアプリケーションを実行できます。または、次のように、./gradlew build
を使用して JAR ファイルをビルドしてから、JAR ファイルを実行できます。
Maven を使用する場合、./mvnw spring-boot:run
を使用してアプリケーションを実行できます。または、次のように、./mvnw clean package
で JAR ファイルをビルドしてから、JAR ファイルを実行できます。
ここで説明する手順は、実行可能な JAR を作成します。クラシック WAR ファイルを作成することもできます。 |
Vaadin アプリケーションが http://localhost:8080 で実行されていることがわかります
要約
おめでとう! 永続化のために Spring Data JPA を使用して、フル機能の CRUD UI アプリケーションを作成しました。そして、REST サービスを公開したり、JavaScript や HTML の 1 行を記述したりすることなく、それを実行しました。
関連事項
次のガイドも役立つかもしれません:
新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください [GitHub] (英語) 。
すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の Attribution、NoDerivatives creative commons ライセンス (英語) でリリースされています。 |