Пример CRUD с Spring Boot, REST и AngularJS
1. Цель статьи
В данной статье я покажу вам как создать простое приложение, которое комбинирует технологии Spring Boot, Rest и AngularJS. Для начала, вы можете просмотреть приложение, которое мы выполним:
AngularJS это библиотека открытого исходного кода, созданная на основании Javascript, и помогает вам построить приложения Single Page (единственную страницу). В данной статье мы создадим страницу, данная страница отображает список сотрудников, одновременно позволяет вам добавить, удалить, редактировать сотрудников.
Темы, которые будут упомянуты в данной статье:
- Создать приложение Spring Boot.
- Создать REST API с функциями: Запрос, создать, редактировать, удалить данные.
- AngularJS вызывает REST API для запроса данных и отображения данных на интерфейсе. AngularJS вызывает REST API для создания, удаления, редактирования данных.
В конце данной статьи мы запустим приложение и объясним принцип работы AngularJS в каждой конкретной функции.
2. Создать проект Spring Boot
На Eclipse создать проект Spring Boot:
pom.xml
<?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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.o7planning</groupId>
<artifactId>SpringBootAngularJS</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBootAngularJS</name>
<description>Spring Boot + AngularJS</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
spring.thymeleaf.cache=false
SpringBootAngularJsApplication.java
package org.o7planning.sbangularjs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootAngularJsApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAngularJsApplication.class, args);
}
}
3. Model, DAO
Employee.java
package org.o7planning.sbangularjs.model;
public class Employee {
private Long empId;
private String empNo;
private String empName;
private String position;
public Employee() {
}
public Employee(EmployeeForm empForm) {
this.empId = empForm.getEmpId();
this.empNo = empForm.getEmpNo();
this.empName = empForm.getEmpName();
this.position = empForm.getPosition();
}
public Employee(Long empId, String empNo, String empName, String position) {
this.empId = empId;
this.empNo = empNo;
this.empName = empName;
this.position = position;
}
public Long getEmpId() {
return empId;
}
public void setEmpId(Long empId) {
this.empId = empId;
}
public String getEmpNo() {
return empNo;
}
public void setEmpNo(String empNo) {
this.empNo = empNo;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
}
EmployeeForm.java
package org.o7planning.sbangularjs.model;
public class EmployeeForm {
private Long empId;
private String empNo;
private String empName;
private String position;
public Long getEmpId() {
return empId;
}
public void setEmpId(Long empId) {
this.empId = empId;
}
public String getEmpNo() {
return empNo;
}
public void setEmpNo(String empNo) {
this.empNo = empNo;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
}
EmployeeDAO.java
package org.o7planning.sbangularjs.dao;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.o7planning.sbangularjs.model.Employee;
import org.o7planning.sbangularjs.model.EmployeeForm;
import org.springframework.stereotype.Repository;
@Repository
public class EmployeeDAO {
private static final Map<Long, Employee> empMap = new HashMap<Long, Employee>();
static {
initEmps();
}
private static void initEmps() {
Employee emp1 = new Employee(1L, "E01", "Smith", "Clerk");
Employee emp2 = new Employee(2L, "E02", "Allen", "Salesman");
Employee emp3 = new Employee(3L, "E03", "Jones", "Manager");
empMap.put(emp1.getEmpId(), emp1);
empMap.put(emp2.getEmpId(), emp2);
empMap.put(emp3.getEmpId(), emp3);
}
public Long getMaxEmpId() {
Set<Long> keys = empMap.keySet();
Long max = 0L;
for (Long key : keys) {
if (key > max) {
max = key;
}
}
return max;
}
public Employee getEmployee(Long empId) {
return empMap.get(empId);
}
public Employee addEmployee(EmployeeForm empForm) {
Long empId= this.getMaxEmpId()+ 1;
empForm.setEmpId(empId);
Employee newEmp = new Employee(empForm);
empMap.put(newEmp.getEmpId(), newEmp);
return newEmp;
}
public Employee updateEmployee(EmployeeForm empForm) {
Employee emp = this.getEmployee(empForm.getEmpId());
if(emp!= null) {
emp.setEmpNo(empForm.getEmpNo());
emp.setEmpName(empForm.getEmpName());
emp.setPosition(empForm.getPosition());
}
return emp;
}
public void deleteEmployee(Long empId) {
empMap.remove(empId);
}
public List<Employee> getAllEmployees() {
Collection<Employee> c = empMap.values();
List<Employee> list = new ArrayList<Employee>();
list.addAll(c);
return list;
}
}
4. Controller, Rest Controller
MainController.java
package org.o7planning.sbangularjs.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MainController {
@RequestMapping("/")
public String welcome() {
return "index";
}
}
MainRESTController.java
package org.o7planning.sbangularjs.controller;
import java.util.List;
import org.o7planning.sbangularjs.dao.EmployeeDAO;
import org.o7planning.sbangularjs.model.Employee;
import org.o7planning.sbangularjs.model.EmployeeForm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MainRESTController {
@Autowired
private EmployeeDAO employeeDAO;
// URL:
// http://localhost:8080/SomeContextPath/employees
// http://localhost:8080/SomeContextPath/employees.xml
// http://localhost:8080/SomeContextPath/employees.json
@RequestMapping(value = "/employees", //
method = RequestMethod.GET, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public List<Employee> getEmployees() {
List<Employee> list = employeeDAO.getAllEmployees();
return list;
}
// URL:
// http://localhost:8080/SomeContextPath/employee/{empId}
// http://localhost:8080/SomeContextPath/employee/{empId}.xml
// http://localhost:8080/SomeContextPath/employee/{empId}.json
@RequestMapping(value = "/employee/{empId}", //
method = RequestMethod.GET, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public Employee getEmployee(@PathVariable("empId") Long empId) {
return employeeDAO.getEmployee(empId);
}
// URL:
// http://localhost:8080/SomeContextPath/employee
// http://localhost:8080/SomeContextPath/employee.xml
// http://localhost:8080/SomeContextPath/employee.json
@RequestMapping(value = "/employee", //
method = RequestMethod.POST, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public Employee addEmployee(@RequestBody EmployeeForm empForm) {
System.out.println("(Service Side) Creating employee with empNo: " + empForm.getEmpNo());
return employeeDAO.addEmployee(empForm);
}
// URL:
// http://localhost:8080/SomeContextPath/employee
// http://localhost:8080/SomeContextPath/employee.xml
// http://localhost:8080/SomeContextPath/employee.json
@RequestMapping(value = "/employee", //
method = RequestMethod.PUT, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public Employee updateEmployee(@RequestBody EmployeeForm empForm) {
System.out.println("(Service Side) Editing employee with Id: " + empForm.getEmpId());
return employeeDAO.updateEmployee(empForm);
}
// URL:
// http://localhost:8080/SomeContextPath/employee/{empId}
@RequestMapping(value = "/employee/{empId}", //
method = RequestMethod.DELETE, //
produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public void deleteEmployee(@PathVariable("empId") Long empId) {
System.out.println("(Service Side) Deleting employee with Id: " + empId);
employeeDAO.deleteEmployee(empId);
}
}
5. Javascript, Css, View
index.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>AngularJS</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.js"></script>
<script th:src="@{/main.js}"></script>
<link th:href="@{/main.css}" rel="stylesheet" />
<head>
<body ng-app="EmployeeManagement" ng-controller="EmployeeController">
<h3>
CRUD: Spring Boot + Rest + AngularJS
</h3>
<form ng-submit="submitEmployee()">
<table border="0">
<tr>
<td>Emp Id</td>
<td>{{employeeForm.empId}}</td>
</tr>
<tr>
<td>Emp No</td>
<td><input type="text" ng-model="employeeForm.empNo" /></td>
</tr>
<tr>
<td>Emp Name</td>
<td><input type="text" ng-model="employeeForm.empName" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Submit" class="blue-button" />
</td>
</tr>
</table>
</form>
<br/>
<a class="create-button" ng-click="createEmployee()">Create Employee</a>
<table border="1">
<tr>
<th>Emp Id</th>
<th>Emp No</th>
<th>Emp Name</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<!-- $scope.employees -->
<tr ng-repeat="employee in employees">
<td> {{ employee.empId }}</td>
<td> {{ employee.empNo }}</td>
<td >{{ employee.empName }}</td>
<td>
<a ng-click="editEmployee(employee)" class="edit-button">Edit</a>
</td>
<td>
<a ng-click="deleteEmployee(employee)" class="delete-button">Delete</a>
</td>
</tr>
</table>
</body>
</html>
main.css
table {
border-collapse: collapse;
}
table td, th {
padding: 5px;
}
.create-button {
color: blue;
cursor: pointer;
padding: 5px;
}
.edit-button {
padding: 2px 5px;
background: #25A6E1;
cursor: pointer;
}
.delete-button {
padding: 2px 5px;
background: #CD5C5C;
cursor: pointer;
}
main.js
var app = angular.module("EmployeeManagement", []);
// Controller Part
app.controller("EmployeeController", function($scope, $http) {
$scope.employees = [];
$scope.employeeForm = {
empId: 1,
empNo: "",
empName: ""
};
// Now load the data from server
_refreshEmployeeData();
// HTTP POST/PUT methods for add/edit employee
// Call: http://localhost:8080/employee
$scope.submitEmployee = function() {
var method = "";
var url = "";
if ($scope.employeeForm.empId == -1) {
method = "POST";
url = '/employee';
} else {
method = "PUT";
url = '/employee';
}
$http({
method: method,
url: url,
data: angular.toJson($scope.employeeForm),
headers: {
'Content-Type': 'application/json'
}
}).then(_success, _error);
};
$scope.createEmployee = function() {
_clearFormData();
}
// HTTP DELETE- delete employee by Id
// Call: http://localhost:8080/employee/{empId}
$scope.deleteEmployee = function(employee) {
$http({
method: 'DELETE',
url: '/employee/' + employee.empId
}).then(_success, _error);
};
// In case of edit
$scope.editEmployee = function(employee) {
$scope.employeeForm.empId = employee.empId;
$scope.employeeForm.empNo = employee.empNo;
$scope.employeeForm.empName = employee.empName;
};
// Private Method
// HTTP GET- get all employees collection
// Call: http://localhost:8080/employees
function _refreshEmployeeData() {
$http({
method: 'GET',
url: '/employees'
}).then(
function(res) { // success
$scope.employees = res.data;
},
function(res) { // error
console.log("Error: " + res.status + " : " + res.data);
}
);
}
function _success(res) {
_refreshEmployeeData();
_clearFormData();
}
function _error(res) {
var data = res.data;
var status = res.status;
var header = res.header;
var config = res.config;
alert("Error: " + status + ":" + data);
}
// Clear the form
function _clearFormData() {
$scope.employeeForm.empId = -1;
$scope.employeeForm.empNo = "";
$scope.employeeForm.empName = ""
};
});
6. Пояснение принципа работы
OK, теперь вы можете запустить приложение и посмотреть как AngularJS работает в каждой определенной функции.
Функция отображения списка сотрудников:
Когда запускается веб страница, AngularJS вызывает REST API чтобы получить список сотрудников, эти данные сохраняются на переменной $scope.employees. И AngularJS отображает их на интерфейсе. Если данные $scope.employees изменяются, AngularJS автоматически обновляет интерфейс.
AngularJS вызывает REST API для получения списка сотрудников (Employee) и сохраняет эти данные в переменную $scope.employees.
// Private Method
// HTTP GET- get all employees collection
// Call: http://localhost:8080/employees
function _refreshEmployeeData() {
$http({
method: 'GET',
url: '/employees'
}).then(
function(res) { // success
$scope.employees = res.data;
},
function(res) { // error
console.log("Error: " + res.status + " : " + res.data);
}
);
}
.....
AngularJS отображает данные в переменной $scope.employees на интерфейсе:
<table border="1">
<tr>
<th>Emp Id</th>
<th>Emp No</th>
<th>Emp Name</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<!-- $scope.employees -->
<tr ng-repeat="employee in employees">
<td> {{ employee.empId }}</td>
<td> {{ employee.empNo }}</td>
<td >{{ employee.empName }}</td>
<td>
<a ng-click="editEmployee(employee)" class="edit-button">Edit</a>
</td>
<td>
<a ng-click="deleteEmployee(employee)" class="delete-button">Delete</a>
</td>
</tr>
</table>
Функция редактирования информации сотрудников:
AngularJS можеть быть двусторонней привязкой (2-way binding) данных между Model и View. Это значит, если пользователь вводит данные на View, эти данные будут автоматически обновлены для Model, и наоборот если данные на Model меняются, это будет отображено на View.
AngularJS использует атрибут (attribute) ng-model для двусторонней привязки (2-way binding) между Model и View:
<form ng-submit="submitEmployee()">
<table border="0">
<tr>
<td>Emp Id</td>
<td>{{employeeForm.empId}}</td>
</tr>
<tr>
<td>Emp No</td>
<td><input type="text" ng-model="employeeForm.empNo" /></td>
</tr>
<tr>
<td>Emp Name</td>
<td><input type="text" ng-model="employeeForm.empName" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Submit" class="blue-button" />
</td>
</tr>
</table>
</form>
Пользователь нажимает на "Edit", вызывается функция $scope.editEmployee.
<a ng-click="editEmployee(employee)" class="edit-button">Edit</a>
Javascript:
// JavaScript: When user Click to Edit button:
$scope.editEmployee = function(employee) {
$scope.employeeForm.empId = employee.empId;
$scope.employeeForm.empNo = employee.empNo;
$scope.employeeForm.empName = employee.empName;
};
// SAVE !!
// HTTP POST/PUT methods for add/edit employee
// Call: http://localhost:8080/employee
$scope.submitEmployee = function() {
var method = "";
var url = "";
if ($scope.employeeForm.empId == -1) {
method = "POST";
url = '/employee';
} else {
method = "PUT";
url = '/employee';
}
$http({
method: method,
url: url,
data: angular.toJson($scope.employeeForm),
headers: {
'Content-Type': 'application/json'
}
}).then(_success, _error);
};
Функция удаления сотрудника (Employee):
Чтобы удалить сотрудника (Employee) AngularJS вызывает REST API с запросом удалить сотрудника. Если успешно (success) дальше вызывает REST API, чтобы сделать запрос на список сотрудников и отображает на интерфейсе.
// HTTP DELETE- delete employee by Id
// Call: http://localhost:8080/employee/{empId}
$scope.deleteEmployee = function(employee) {
$http({
method: 'DELETE',
url: '/employee/' + employee.empId
}).then(_success, _error);
};
.....
function _success(res) {
_refreshEmployeeData();
_clearFormData();
}
function _error(res) {
var data = res.data;
var status = res.status;
var header = res.header;
var config = res.config;
alert("Error: " + status + ":" + data);
}
7. Бонус: Функции success и error
В AngularJS есть 2 способа использования функции success и error:
- Функция success & error с 1 параметром.
- Функция success & error с 4 параметрами.
- success & error with 1 parameter:
$http.get('/someURL').then(
// Success
function(response) {
var data = response.data;
var status = response.status;
var header = response.header;
var config = response.config;
// ...
},
// Error
function(response) {
var data = response.data;
var status = response.status;
var header = response.header;
var config = response.config;
// ...
}
);
- success & error with 4 parameters:
$http.get('/someURL')
// Success
.success(
function(data, status, header, config) {
// ...
}
)
// Error
.error(
function(data, status, header, config) {
// error handler
}
);
Руководства Spring Boot
- Установите Spring Tool Suite для Eclipse
- Руководство Spring для начинающих
- Руководство Spring Boot для начинающих
- Общие свойства Spring Boot
- Руководство Spring Boot и Thymeleaf
- Руководство Spring Boot и FreeMarker
- Руководство Spring Boot и Groovy
- Руководство Spring Boot и Mustache
- Руководство Spring Boot и JSP
- Руководство Spring Boot, Apache Tiles, JSP
- Используйте Logging в Spring Boot
- Мониторинг приложений с помощью Spring Boot Actuator
- Создание веб-приложения с несколькими языками с помощью Spring Boot
- Используйте несколько ViewResolver в Spring Boot
- Используйте Twitter Bootstrap в Spring Boot
- Руководство Spring Boot Interceptor
- Руководство Spring Boot, Spring JDBC и Spring Transaction
- Руководство Spring JDBC
- Руководство Spring Boot, JPA и Spring Transaction
- Руководство Spring Boot и Spring Data JPA
- Руководство Spring Boot, Hibernate и Spring Transaction
- Интеграция Spring Boot, JPA и H2 Database
- Руководство Spring Boot и MongoDB
- Используйте несколько DataSources с Spring Boot и JPA
- Используйте несколько DataSource с Spring Boot и RoutingDataSource
- Создайте приложение для входа с Spring Boot, Spring Security, Spring JDBC
- Создайте приложение для входа с Spring Boot, Spring Security, JPA
- Создайте приложение регистрации пользователей с помощью Spring Boot, Spring Form Validation
- Пример OAuth2 Social Login в Spring Boot.
- Запускать фоновые запланированные задачи в Spring
- Пример CRUD Restful Web Service c Spring Boot
- Пример Spring Boot Restful Client c RestTemplate
- Пример CRUD с Spring Boot, REST и AngularJS
- Защита Spring Boot RESTful Service используя Basic Authentication
- Защита Spring Boot RESTful Service используя Auth0 JWT
- Пример Upload file c Spring Boot
- Пример Download file c Spring Boot
- Пример Upload file c Spring Boot и jQuery Ajax
- Пример Upload file c Spring Boot и AngularJS
- Создание веб-приложения для корзины покупок с помощью Spring Boot, Hibernate
- Руководство Spring Email
- Создайте простое приложение Chat с Spring Boot и Websocket
- Разверните приложение Spring Boot на Tomcat Server
- Развертывание приложения Spring Boot на Oracle WebLogic Server
- Установите бесплатный сертификат Let's Encrypt SSL для Spring Boot
- Настройте Spring Boot для перенаправления HTTP на HTTPS
Show More