Пример CRUD с Spring Boot, REST и AngularJS
View more Tutorials:
В данной статье я покажу вам как создать простое приложение, которое комбинирует технологии Spring Boot, Rest и AngularJS. Для начала, вы можете просмотреть приложение, которое мы выполним:

AngularJS это библиотека открытого исходного кода, созданная на основании Javascript, и помогает вам построить приложения Single Page (единственную страницу). В данной статье мы создадим страницу, данная страница отображает список сотрудников, одновременно позволяет вам добавить, удалить, редактировать сотрудников.
Темы, которые будут упомянуты в данной статье:
- Создать приложение Spring Boot.
- Создать REST API с функциями: Запрос, создать, редактировать, удалить данные.
- AngularJS вызывает REST API для запроса данных и отображения данных на интерфейсе. AngularJS вызывает REST API для создания, удаления, редактирования данных.
В конце данной статьи мы запустим приложение и объясним принцип работы AngularJS в каждой конкретной функции.
На 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); } }

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; } }

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); } }

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 = "" }; });
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); }
В 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 } );