Пример CRUD с Spring Boot, REST и AngularJS

View more Tutorials:

1- Цель статьи

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

View more Tutorials: