一、简介
在本教程中,我们将着眼于创建一个能够创建、检索、更新和删除 (CRUD) 客户端数据的应用程序。该应用程序将由一个简单的Spring Boot RESTful API和一个使用 React JavaScript 库实现的用户界面 (UI) 组成。
2. 春季启动
2.1. Maven 依赖项
让我们首先向 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>
在这里,我们添加了 Web、测试和 JPA 持久性启动器,以及 h2 依赖项,因为应用程序将使用 H2 内存数据库。
2.2.创建模型
接下来,让我们创建具有 名称 和 电子邮件 属性的 Client 实体类来表示我们的数据模型:
@Entity
@Table(name = "client")
public class Client {
@Id
@GeneratedValue
private Long id;
private String name;
private String email;
// getter, setters, contructors
}
2.3.创建存储库
然后我们将创建从 JpaRepository 扩展的 ClientRepository 类 以 提供 JPA CRUD 功能 :
public interface ClientRepository extends JpaRepository<Client, Long> {
}
2.4.创建 REST 控制器
接下来,我们通过创建一个控制器来与 ClientRepository 交互来公开 REST API :
@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.启动我们的 API
至此,我们现在就可以启动 Spring Boot API 了。我们可以使用 spring-boot-maven-plugin 来做到这一点:
mvn spring-boot:run
然后,我们将能够通过访问http://localhost:8080/clients来获取我们的客户列表。
2.6。创建客户
此外,我们可以使用Postman创建一些客户端:
curl -X POST http://localhost:8080/clients -d '{"name": "John Doe", "email": "john.doe@baeldgung.com"}'
3.反应
React 是一个用于创建用户界面的 JavaScript 库。使用 React 确实需要安装Node.js。您可以在 Node.js 下载页面上找到安装说明。
3.1.创建 React UI
Create React App是一个 为我们生成 React 项目的 命令实用程序。让我们通过运行以下命令在 Spring Boot 应用程序基目录中创建 前端 应用程序:
npx create-react-app frontend
应用程序创建过程完成后,我们将在 前端 目录中安装Bootstrap 、 React Router和Reactstrap :
npm install --save bootstrap@4.1.3 react-cookie@3.0.4 react-router-dom@4.3.1 reactstrap@6.5.0
我们将使用 Bootstrap 的 CSS 和 reacstrap 的组件来创建更美观的 UI,并使用 React Router 组件来处理应用程序的导航性。
让我们将 Bootstrap 的 CSS 文件添加为 app/src/index.js 中的导入:
import 'bootstrap/dist/css/bootstrap.min.css';
3.2.启动我们的 React UI
现在,我们准备启动我们的 前端 应用程序:
npm start
当在浏览器中访问http://localhost:3000时,我们应该看到 React 示例页面:
3.3.调用我们的 Spring Boot API
调用 Spring Boot API 需要设置 React 应用程序的 package.json 文件,以便在调用 API 时配置代理。
为此,我们将在 package.json 中包含 API 的 URL:
...
"proxy": "http://localhost:8080",
...
接下来,让我们编辑 frontend/src/App.js ,以便它调用我们的 API 来显示具有 名称 和 电子邮件 属性的客户端列表:
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;
在 componentDidMount 函数中, 我们获取客户端 API 并在 client 变量中设置响应正文,在 渲染 函数中,我们返回包含在 API 中找到的客户端列表的 HTML。
我们将看到客户的页面,如下所示:
注意:确保 Spring Boot 应用程序正在运行,以便 UI 能够调用 API。
3.4.创建 ClientList 组件
现在,我们可以改进 UI 以显示 更复杂的组件,以使用我们的 API 列出 、 编辑 、 删除 和 创建客户端 。稍后,我们将了解如何使用此组件并从 App 组件中 删除 客户端列表。
让我们在 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;
正如我们在 App.js 中一样, componentDidMount 函数正在调用我们的 API 来加载我们的客户端列表。
此外,当我们想要删除客户端时,让我们添加 删除 函数来处理对 API 的 DELETE 调用。此外,我们将创建 渲染 函数,它将使用 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.创建 ClientEdit 组件
ClientEdit 组件将负责 创建和编辑我们的客户端 。
让我们在 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);
让我们添加 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});
}
}
然后在 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});
}
在 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');
}
最后但并非最不重要的一点是,我们的 渲染 函数将处理我们的表单:
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>
}
注意:我们还有一个 链接 ,其路由配置为在单击 “取消” 按钮时返回到 /clients 。
3.6.创建 AppNavBar 组件
为了给我们的应用程序提供 更好的导航性 ,让我们在 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>;
}
}
在 渲染 函数中, 我们使用 react-router-dom 功能创建一个 链接 以路由到我们的应用程序 主页 。
3.7.创建我们的 家庭 组件
该组件将成为我们的应用程序 主页 ,并将有一个按钮指向我们之前创建的 ClientList 组件。
让我们在 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;
注意:在这个组件中,我们还有一个来自 react-router-dom的 链接 ,它引导我们到 /clients 。该路由将在下一步中配置。
3.8.使用反应路由器
我们现在将使用React Router在我们的组件之间导航。
让我们改变我们的 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;
正如我们所看到的,我们为我们创建的每个组件定义了应用程序路由。
当访问localhost:3000时,我们现在有了带有 客户端链接的 主页 :
单击 “客户端” 链接,我们现在有了客户端列表以及 “编辑 ”、 “删除 ”和 “添加客户端” 功能:
4. 构建和包装
要 使用 Maven 构建和打包我们的 React 应用程序 ,我们将使用 frontend-maven-plugin 。
该插件将负责打包我们的 前端 应用程序并将其复制到我们的 Spring Boot API 构建文件夹中:
<properties>
...
<frontend-maven-plugin.version>1.6</frontend-maven-plugin.version>
<node.version>v10.14.2</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>
让我们仔细看看我们的 maven-resources-plugin ,它负责将我们的 前端 源复制到应用程序 目标 文件夹:
...
<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>
...
然后,我们的 front-end-maven-plugin 将负责安装 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>
...
注意:要指定不同的 Node.js 版本,我们只需编辑 pom.xml 中的 node.version 属性即可。
5. 运行我们的 Spring Boot React CRUD 应用程序
最后,通过添加插件,我们可以通过运行以下命令来访问我们的应用程序:
mvn spring-boot:run
我们的 React 应用程序将完全集成到我们的 API( 位于http://localhost:8080/ URL)中。
六,结论
在本文中,我们了解了如何使用 Spring Boot 和 React 创建 CRUD 应用程序。为此,我们首先创建了一些 REST API 端点来与数据库交互。然后,我们创建了一些 React 组件来使用我们的 API 获取和写入数据。我们还学习了如何将 Spring Boot 应用程序与 React UI 打包到单个应用程序包中。
我们的应用程序的源代码可在 GitHub 上获取。