1. Introduction
1.绪论
In this tutorial, we’ll learn how to create an application capable of creating, retrieving, updating, and deleting (CRUD) client data. The application will consist of a simple Spring Boot RESTful API and a user interface (UI) implemented with the React JavaScript library.
在本教程中,我们将学习如何创建一个能够创建、检索、更新和删除(CRUD)客户端数据的应用程序。该应用程序将包括一个简单的Spring Boot RESTful API和一个使用React JavaScript库实现的用户界面(UI)。
2. Spring Boot
2.Spring启动
2.1. Maven Dependencies
2.1.Maven的依赖性
Let’s start by adding a few dependencies to our pom.xml file:
让我们先在我们的pom.xml文件中添加一些依赖项。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
<scope>runtime</scope>
</dependency>
</dependencies>
Here we added the web, testing, and JPA persistence starters, as well as the H2 dependency, as the application will have an H2 in-memory database.
在这里,我们添加了Web、测试和JPA持久性启动程序,以及H2依赖,因为应用程序将有一个H2内存数据库。
2.2. Creating the Model
2.2.创建模型
Next, let’s create our Client entity class, with name and email properties, to represent our data model:
接下来,让我们创建我们的Client实体类,用name和email属性,来表示我们的数据模型。
@Entity
@Table(name = "client")
public class Client {
@Id
@GeneratedValue
private Long id;
private String name;
private String email;
// getter, setters, contructors
}
2.3. Creating the Repository
2.3.创建存储库
Then we’ll create our ClientRepository class extending from JpaRepository to provide JPA CRUD capabilities:
然后我们将创建我们的ClientRepository类从JpaRepository扩展,以提供JPA CRUD能力。
public interface ClientRepository extends JpaRepository<Client, Long> {
}
2.4. Creating the REST Controller
2.4.创建REST控制器
Finally, let’s expose a REST API by creating a controller to interact with the ClientRepository:
最后,让我们通过创建一个控制器来暴露一个REST API,与ClientRepository互动。
@RestController
@RequestMapping("/clients")
public class ClientsController {
private final ClientRepository clientRepository;
public ClientsController(ClientRepository clientRepository) {
this.clientRepository = clientRepository;
}
@GetMapping
public List<Client> getClients() {
return clientRepository.findAll();
}
@GetMapping("/{id}")
public Client getClient(@PathVariable Long id) {
return clientRepository.findById(id).orElseThrow(RuntimeException::new);
}
@PostMapping
public ResponseEntity createClient(@RequestBody Client client) throws URISyntaxException {
Client savedClient = clientRepository.save(client);
return ResponseEntity.created(new URI("/clients/" + savedClient.getId())).body(savedClient);
}
@PutMapping("/{id}")
public ResponseEntity updateClient(@PathVariable Long id, @RequestBody Client client) {
Client currentClient = clientRepository.findById(id).orElseThrow(RuntimeException::new);
currentClient.setName(client.getName());
currentClient.setEmail(client.getEmail());
currentClient = clientRepository.save(client);
return ResponseEntity.ok(currentClient);
}
@DeleteMapping("/{id}")
public ResponseEntity deleteClient(@PathVariable Long id) {
clientRepository.deleteById(id);
return ResponseEntity.ok().build();
}
}
2.5. Starting Our API
2.5.启动我们的API
With that complete, we’re now ready to start our Spring Boot API. We can do this using the spring-boot-maven-plugin:
完成这些后,我们就可以启动Spring Boot API了。我们可以使用spring-boot-maven-plugin来做到这一点。
mvn spring-boot:run
Then we’ll be able to get our clients list by going to http://localhost:8080/clients.
然后我们就可以通过进入http://localhost:8080/clients来获得我们的客户名单。
2.6. Creating Clients
2.6.创建客户
Additionally, we can create a few clients using Postman:
此外,我们可以使用Postman创建一些客户端。
curl -X POST http://localhost:8080/clients -d '{"name": "John Doe", "email": "john.doe@baeldgung.com"}'
3. React
3.反应
React is a JavaScript library for creating user interfaces. Working with React requires that Node.js is installed. We can find the installation instructions on the Node.js download page.
React是一个用于创建用户界面的JavaScript库。使用 React 需要安装Node.js。我们可以在Node.js的下载页面上找到安装说明。
3.1. Creating a React UI
3.1.创建一个React UI
Create React App is a command utility that generates React projects for us. Let’s create our frontend app in our Spring Boot application base directory by running:
创建React应用程序是一个命令工具,可以为我们生成React项目。让我们在Spring Boot应用基础目录中通过运行来创建我们的前端应用。
npx create-react-app frontend
After the app creation process is complete, we’ll install Bootstrap, React Router, and reactstrap in the frontend directory:
应用程序创建过程完成后,我们将在frontend目录中安装Bootstrap、React Router和reactstrap。
npm install --save bootstrap@5.1 react-cookie@4.1.1 react-router-dom@5.3.0 reactstrap@8.10.0
We’ll be using Bootstrap’s CSS and reactstrap’s components to create a better-looking UI, and React Router components to handle navigability around the application.
我们将使用Bootstrap的CSS和reactstrap的组件来创建一个更好看的UI,并使用React Router组件来处理应用程序的导航性。
Let’s add Bootstrap’s CSS file as an import in app/src/index.js:
让我们在app/src/index.js中添加Bootstrap的CSS文件作为导入。
import 'bootstrap/dist/css/bootstrap.min.css';
3.2. Starting Our React UI
3.2.开始我们的React UI
Now we’re ready to start our frontend application:
现在我们准备启动我们的前台应用程序。
npm start
When accessing http://localhost:3000 in our browser, we should see the React sample page:
当在我们的浏览器中访问http://localhost:3000时,我们应该看到React示例页面。
3.3. Calling Our Spring Boot API
3.3.调用我们的Spring Boot API
Calling our Spring Boot API requires setting up our React application’s package.json file to configure a proxy when calling the API.
调用我们的Spring Boot API需要设置我们的React应用程序的 package.json文件,以便在调用API时配置一个代理。
For that, we’ll include the URL for our API in package.json:
为此,我们将在package.json中包含我们的API的URL。
...
"proxy": "http://localhost:8080",
...
Next, let’s edit frontend/src/App.js so that it calls our API to show the list of clients with the name and email properties:
接下来,让我们编辑frontend/src/App.js,以便它调用我们的API,显示具有name和email属性的客户列表。
class App extends Component {
state = {
clients: []
};
async componentDidMount() {
const response = await fetch('/clients');
const body = await response.json();
this.setState({clients: body});
}
render() {
const {clients} = this.state;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<div className="App-intro">
<h2>Clients</h2>
{clients.map(client =>
<div key={client.id}>
{client.name} ({client.email})
</div>
)}
</div>
</header>
</div>
);
}
}
export default App;
In the componentDidMount function, we fetch our client API and set the response body in the clients variable. In our render function, we return the HTML with the list of clients found in the API.
在componentDidMount函数中,我们获取我们的客户端API并在clients变量中设置响应体。在我们的render函数中,我们返回HTML,其中包括在API中发现的客户列表。
We’ll see our client’s page, which will look like this:
我们将看到我们客户的页面,它将看起来像这样。
Note: Make sure the Spring Boot application is running so that the UI will be able to call the API.
注意:请确保Spring Boot应用程序正在运行,以便UI能够调用API。
3.4. Creating a ClientList Component
3.4.创建一个ClientList组件
We can now improve our UI to display a more sophisticated component to list, edit, delete, and create clients using our API. Later, we’ll see how to use this component and remove the client list from the App component.
我们现在可以改进我们的用户界面,以显示一个更复杂的组件来列表、编辑、删除和创建客户使用我们的API。稍后,我们将看到如何使用这个组件并从App组件中删除客户列表。
Let’s create a file in frontend/src/ClientList.js:
让我们在frontend/src/ClientList.js创建一个文件。
import React, { Component } from 'react';
import { Button, ButtonGroup, Container, Table } from 'reactstrap';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';
class ClientList extends Component {
constructor(props) {
super(props);
this.state = {clients: []};
this.remove = this.remove.bind(this);
}
componentDidMount() {
fetch('/clients')
.then(response => response.json())
.then(data => this.setState({clients: data}));
}
}
export default ClientList;
As in App.js, the componentDidMount function is calling our API to load our client list.
正如在App.js中,componentDidMount函数正在调用我们的API来加载我们的客户列表。
We’ll also include the remove function to handle the DELETE call to the API when we want to delete a client. In addition, we’ll create the render function, which will render the HTML with Edit, Delete, and Add Client actions:
我们还将包括remove函数,以处理当我们想删除一个客户时对API的DELETE调用。此外,我们将创建render函数,它将渲染带有Edit、Delete和Add Client动作的HTML。
async remove(id) {
await fetch(`/clients/${id}`, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then(() => {
let updatedClients = [...this.state.clients].filter(i => i.id !== id);
this.setState({clients: updatedClients});
});
}
render() {
const {clients, isLoading} = this.state;
if (isLoading) {
return <p>Loading...</p>;
}
const clientList = clients.map(client => {
return <tr key={client.id}>
<td style={{whiteSpace: 'nowrap'}}>{client.name}</td>
<td>{client.email}</td>
<td>
<ButtonGroup>
<Button size="sm" color="primary" tag={Link} to={"/clients/" + client.id}>Edit</Button>
<Button size="sm" color="danger" onClick={() => this.remove(client.id)}>Delete</Button>
</ButtonGroup>
</td>
</tr>
});
return (
<div>
<AppNavbar/>
<Container fluid>
<div className="float-right">
<Button color="success" tag={Link} to="/clients/new">Add Client</Button>
</div>
<h3>Clients</h3>
<Table className="mt-4">
<thead>
<tr>
<th width="30%">Name</th>
<th width="30%">Email</th>
<th width="40%">Actions</th>
</tr>
</thead>
<tbody>
{clientList}
</tbody>
</Table>
</Container>
</div>
);
}
3.5. Creating a ClientEdit Component
3.5.创建一个ClientEdit组件
The ClientEdit component will be responsible for creating and editing our client.
ClientEdit组件将负责创建和编辑我们的客户。
Let’s create a file in frontend/src/ClientEdit.js:
让我们在frontend/src/ClientEdit.js创建一个文件。
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
import { Button, Container, Form, FormGroup, Input, Label } from 'reactstrap';
import AppNavbar from './AppNavbar';
class ClientEdit extends Component {
emptyItem = {
name: '',
email: ''
};
constructor(props) {
super(props);
this.state = {
item: this.emptyItem
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
}
export default withRouter(ClientEdit);
Let’s add the componentDidMount function to check whether we’re dealing with the create or edit feature; in the case of editing, it’ll fetch our client from the API:
让我们添加componentDidMount函数来检查我们处理的是创建还是编辑功能;在编辑的情况下,它将从API获取我们的客户端。
async componentDidMount() {
if (this.props.match.params.id !== 'new') {
const client = await (await fetch(`/clients/${this.props.match.params.id}`)).json();
this.setState({item: client});
}
}
Then in the handleChange function, we’ll update our component state item property that will be used when submitting our form:
然后在handleChange函数中,我们将更新我们的组件状态项目属性,该属性将在提交表单时使用。
handleChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
let item = {...this.state.item};
item[name] = value;
this.setState({item});
}
In handeSubmit, we’ll call our API, sending the request to a PUT or POST method depending on the feature we’re invoking. For that, we can check if the id property is filled:
在handeSubmit中,我们将调用我们的API,将请求发送到PUT或POST方法,这取决于我们调用的功能。为此,我们可以检查id属性是否被填充。
async handleSubmit(event) {
event.preventDefault();
const {item} = this.state;
await fetch('/clients' + (item.id ? '/' + item.id : ''), {
method: (item.id) ? 'PUT' : 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item),
});
this.props.history.push('/clients');
}
Last, but not least, our render function will be handling our form:
最后,但不是最不重要的,我们的render函数将处理我们的表单。
render() {
const {item} = this.state;
const title = <h2>{item.id ? 'Edit Client' : 'Add Client'}</h2>;
return <div>
<AppNavbar/>
<Container>
{title}
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<Label for="name">Name</Label>
<Input type="text" name="name" id="name" value={item.name || ''}
onChange={this.handleChange} autoComplete="name"/>
</FormGroup>
<FormGroup>
<Label for="email">Email</Label>
<Input type="text" name="email" id="email" value={item.email || ''}
onChange={this.handleChange} autoComplete="email"/>
</FormGroup>
<FormGroup>
<Button color="primary" type="submit">Save</Button>{' '}
<Button color="secondary" tag={Link} to="/clients">Cancel</Button>
</FormGroup>
</Form>
</Container>
</div>
}
Note: We also have a Link with a route configured to go back to /clients when clicking on the Cancel Button.
注意:我们也有一个链接,配置了一条路线,当点击/clients按钮时回到/clients。
3.6. Creating an AppNavbar Component
3.6.创建一个AppNavbar组件
To give our application better navigability, let’s create a file in frontend/src/AppNavbar.js:
为了让我们的应用程序 更好的导航性,让我们在frontend/src/AppNavbar.js中创建一个文件。
import React, {Component} from 'react';
import {Navbar, NavbarBrand} from 'reactstrap';
import {Link} from 'react-router-dom';
export default class AppNavbar extends Component {
constructor(props) {
super(props);
this.state = {isOpen: false};
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
render() {
return <Navbar color="dark" dark expand="md">
<NavbarBrand tag={Link} to="/">Home</NavbarBrand>
</Navbar>;
}
}
In the render function, we’ll use the react-router-dom capabilities to create a Link to route to our application Home page.
在render 函数中,我们将使用react-router-dom功能来创建一个Link路由到我们的应用程序Home页。
3.7. Creating Our Home Component
3.7.创建我们的Home组件
This component will be our application Home page, and will have a button to our previously created ClientList component.
这个组件将是我们应用程序的主页页,并将有一个按钮指向我们先前创建的ClientList组件。
Let’s create a file in frontend/src/Home.js:
让我们在frontend/src/Home.js创建一个文件。
import React, { Component } from 'react';
import './App.css';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';
import { Button, Container } from 'reactstrap';
class Home extends Component {
render() {
return (
<div>
<AppNavbar/>
<Container fluid>
<Button color="link"><Link to="/clients">Clients</Link></Button>
</Container>
</div>
);
}
}
export default Home;
Note: In this component, we also have a Link from react-router-dom that leads us to /clients. This route will be configured in the next step.
注意:在这个组件中,我们还有一个来自react-router-dom的链接,将我们引向/clients。这个路由将在下一步进行配置。
3.8. Using React Router
3.8.使用React Router
Now we’ll use React Router to navigate between our components.
现在我们将使用React Router来在我们的组件之间进行导航。
Let’s change our App.js:
让我们改变我们的App.js。
import React, { Component } from 'react';
import './App.css';
import Home from './Home';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import ClientList from './ClientList';
import ClientEdit from "./ClientEdit";
class App extends Component {
render() {
return (
<Router>
<Switch>
<Route path='/' exact={true} component={Home}/>
<Route path='/clients' exact={true} component={ClientList}/>
<Route path='/clients/:id' component={ClientEdit}/>
</Switch>
</Router>
)
}
}
export default App;
As we can see, we have our application routes defined for each of the components we’ve created.
正如我们所看到的,我们已经为我们所创建的每个组件定义了我们的应用路线。
When accessing localhost:3000, we now have our Home page with a Clients link:
当访问 localhost:3000时,我们现在有了带有Clients链接的我们的Home页面。
Clicking on the Clients link, we now have our list of clients, and the Edit, Remove, and Add Client features:
点击Clients链接,我们现在有了客户列表,以及Edit、Remove和Add Client功能。
4. Building and Packaging
4.建筑和包装
To build and package our React application with Maven, we’ll use the frontend-maven-plugin.
为了用Maven构建和打包我们的React应用,我们将使用frontend-maven-plugin。
This plugin will be responsible for packaging and copying our frontend application into our Spring Boot API build folder:
这个插件将负责将我们的前端应用程序打包并复制到我们的Spring Boot API构建文件夹。
<properties>
...
<frontend-maven-plugin.version>1.6</frontend-maven-plugin.version>
<node.version>v14.8.0</node.version>
<yarn.version>v1.12.1</yarn.version>
...
</properties>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
...
</executions>
</plugin>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>${frontend-maven-plugin.version}</version>
<configuration>
...
</configuration>
<executions>
...
</executions>
</plugin>
...
</plugins>
</build>
Let’s take a closer look at our maven-resources-plugin, which is responsible for copying our frontend sources to the application target folder:
让我们仔细看看我们的maven-resources-plugin,它负责将frontend源复制到应用程序target文件夹。
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>process-classes</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes/static</outputDirectory>
<resources>
<resource>
<directory>frontend/build</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
...
Our front-end-maven-plugin will then be responsible for installing Node.js and Yarn, and then building and testing our frontend application:
然后我们的前端-maven-插件将负责安装Node.js和Yarn,然后构建并测试我们的前端应用程序。
...
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>${frontend-maven-plugin.version}</version>
<configuration>
<workingDirectory>frontend</workingDirectory>
</configuration>
<executions>
<execution>
<id>install node</id>
<goals>
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>${node.version}</nodeVersion>
<yarnVersion>${yarn.version}</yarnVersion>
</configuration>
</execution>
<execution>
<id>yarn install</id>
<goals>
<goal>yarn</goal>
</goals>
<phase>generate-resources</phase>
</execution>
<execution>
<id>yarn test</id>
<goals>
<goal>yarn</goal>
</goals>
<phase>test</phase>
<configuration>
<arguments>test</arguments>
<environmentVariables>
<CI>true</CI>
</environmentVariables>
</configuration>
</execution>
<execution>
<id>yarn build</id>
<goals>
<goal>yarn</goal>
</goals>
<phase>compile</phase>
<configuration>
<arguments>build</arguments>
</configuration>
</execution>
</executions>
</plugin>
...
Note: to specify a different Node.js version, we can simply edit the node.version property in our pom.xml.
注意:要指定不同的Node.js版本,我们可以简单地编辑node.version属性在我们的pom.xml.。
5. Running Our Spring Boot React CRUD Application
5.运行我们的Spring Boot React CRUD应用程序
Finally, by adding the plugin, we can access our application by running:
最后,通过添加该插件,我们可以通过运行访问我们的应用程序。
mvn spring-boot:run
Our React application will be fully integrated into our API at the http://localhost:8080/ URL.
我们的React应用程序将完全集成到我们的API中,在http://localhost:8080/ URL。
6. Conclusion
6.结语
In this article, we examined how to create a CRUD application using Spring Boot and React. To do so, we first created some REST API endpoints to interact with our database. Then we created some React components to fetch and write data using our API. We also learned how to package our Spring Boot Application with our React UI into a single application package.
在这篇文章中,我们研究了如何使用Spring Boot和React创建一个CRUD应用程序。为此,我们首先创建了一些REST API端点,与我们的数据库进行交互。然后,我们创建了一些React组件,以使用我们的API获取和写入数据。我们还学习了如何将我们的Spring Boot应用和React UI打包成一个应用包。
The source code for our application is available over on GitHub.
我们的应用程序的源代码可在GitHub上获得。