CRUD with Spring Boot + Rest and AngularJS

Giới thiệu

Spring Boot là một dự án phát triển bởi JAV (ngôn ngữ java) trong hệ sinh thái Spring framework. Nó giúp cho các lập trình viên chúng ta đơn giản hóa quá trình lập trình một ứng dụng với Spring, chỉ tập trung vào việc phát triển business cho ứng dụng.

5293864.png

1. Mục đích của bài viết

Trong bài viết này tôi sẽ hướng dẫn bạn tạo một ứng dụng đơn giản kết hợp các công nghệ Spring Boot, Rest và AngularJS. Trước hết, bạn có thể xem trước ứng dụng mà chúng ta sẽ thực hiện thông qua liên kết này.

AngularJS là một thư viện mã nguồn mở, nó được tạo ra dựa trên Javascript, và giúp bạn xây dựng các ứng dụng Single Page (một trang đơn nhất). Trong bài học này chúng ta sẽ tạo một trang, trang này hiển thị danh sách các nhân viên, đồng thời cho phép bạn thêm, xóa, sửa các nhân viên.

Các vấn đề sẽ được đề cập trong bài học này:

  1. Tạo một ứng dụng Spring Boot.
  2. Tạo các REST API với các chức năng: Truy vấn, tạo, sửa, xóa dữ liệu.
  3. AngularJS gọi đến các REST API để truy vấn và xử lí dữ liệu trên database.

2. Tạo ứng dụng Spring boot

Đầu tiên bạn cần cài đặt Visual Studio CodeSpring Boot Extension Pack

  • Ấn Ctrl + Shift + P và chọn Generate a Gradle Project

Screen Shot 2019-03-09 at 5.51.51 PM.png

  • Chọn Java

Screen Shot 2019-03-09 at 5.50.58 PM.png

  • Đặt tên cho project của bạn

Screen Shot 2019-03-09 at 5.52.59 PM.png

  • Chọn Spring boot version

Screen Shot 2019-03-09 at 5.55.13 PM.png

  • Add dependency cần sử dụng cho project

Screen Shot 2019-03-09 at 5.56.51 PM.png

Thế là đã hoàn tất việc tạo 1 project spring boot với visual studio code rồi. Chúng ta sẽ tiếp tục với những class cần thiết sau nhé!

2.1 pom.xml

Những thư viện cần thiết đề tạo ứng dụng này:

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>   
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </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>
2.2 Account.java

Tạo Entity Account trên database dựa vào JPA

import javax.persistence.*;
@Entity
@Table(name = "Account")
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int Id;
    private String Name;
    private String Phone;
    private String Position;

    public Account() {

    }

    public Account(AccountWrapper aw) {
        this.Id = aw.getId();
        this.Name = aw.getName();
        this.Phone = aw.getPhone();
        this.Position = aw.getPosition();
    }

    /**
     * @return the position
     */
    public String getPosition() {
        return Position;
    }

    /**
     * @param position the position to set
     */
    public void setPosition(String position) {
        this.Position = position;
    }

    public Account(String name, String phone) {
        this.Name = name;
        this.Phone = phone;
    }

    public int getId(){
        return Id;
    }

    public String getName(){
        return Name;
    }

    public void setName(String name){
        this.Name = name;
    }

    public String getPhone(){
        return Phone;
    }

    public void setPhone(String phone){
        this.Phone = phone;
    }
}
2.3 AccountWrapper.java

Wrapper class dùng để binding dữ liệu giữa view và controller của ứng dụng

public class AccountWrapper {

    private int Id;
    private String Name;
    private String Phone;
    private String Position;

    /**
     * @return the id
     */
    public int getId() {
        return Id;
    }

    /**
     * @return the position
     */
    public String getPosition() {
        return Position;
    }

    /**
     * @param position the position to set
     */
    public void setPosition(String position) {
        this.Position = position;
    }

    /**
     * @return the phone
     */
    public String getPhone() {
        return Phone;
    }

    /**
     * @param phone the phone to set
     */
    public void setPhone(String phone) {
        this.Phone = phone;
    }

    /**
     * @return the name
     */
    public String getName() {
        return Name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.Name = name;
    }

    /**
     * @param id the id to set
     */
    public void setId(int id) {
        this.Id = id;
    }
}
2.4 AccountRepository.java

Thực hiện truy vấn và thực thi dữ liệu dựa vào CrudRepository

import org.springframework.data.repository.CrudRepository;

public interface AccountRepository extends CrudRepository<Account, Integer>{
}
2.5 AccountController.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
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;

@Controller
public class AccountController {

    @Autowired
    private AccountRepository accountRepository;

    @RequestMapping("/")
    public String welcome() {
        return "index";
    }

    @RequestMapping(value = "/accounts", //
            method = RequestMethod.GET, //
            produces = { MediaType.APPLICATION_JSON_VALUE, //
                    MediaType.APPLICATION_XML_VALUE })
    @ResponseBody
    public Iterable<Account> getAccounts() {
        return accountRepository.findAll();
    }

    @RequestMapping(value = "/account", //
            method = RequestMethod.POST, //
            produces = { MediaType.APPLICATION_JSON_VALUE, //
                    MediaType.APPLICATION_XML_VALUE })
    @ResponseBody
    public Account addAccount(@RequestBody Account acc) {
       return accountRepository.save(acc);
    }

    @RequestMapping(value = "/account", //
            method = RequestMethod.PUT, //
            produces = { MediaType.APPLICATION_JSON_VALUE, //
                    MediaType.APPLICATION_XML_VALUE })
    @ResponseBody
    public Account updateAccount(@RequestBody AccountWrapper acc) {
        Account account = new Account(acc);
        System.out.println("acc:"+acc);
        return accountRepository.save(account);
    }

    @RequestMapping(value = "/account/{accId}", //
            method = RequestMethod.DELETE, //
            produces = { MediaType.APPLICATION_JSON_VALUE, //
                    MediaType.APPLICATION_XML_VALUE })
    @ResponseBody
    public void deleteAccount(@PathVariable("accId") Integer accId) {
       accountRepository.deleteById(accId);
    }
}

2.6 View, CSS và Javascript

index.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <title>Spring Boot + Restful + 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="AccountManagement" ng-controller="AccountController">
      <h3>
         CRUD: Spring Boot + Rest + AngularJS
      </h3>
      <form ng-submit="submit();" ng-show="acc.id != null">
         <table border="0">
            <tr ng-show="acc.id != -1"> 
               <td>Account Id</td>
               <td>{{acc.id}}</td>
            </tr>
            <tr>
               <td>Account Name</td>
               <td><input type="text" ng-model="acc.name"/></td>
            </tr>
            <tr>
               <td>Phone</td>
               <td><input type="text" ng-model="acc.phone"/></td>
            </tr>
            <tr>
               <td>Position</td>
               <td><input type="text" ng-model="acc.position"/></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="addAccount();">Create Account</a>
      <table border="1">
         <tr>
            <th>Id</th>
            <th>Name</th>
            <th>Phone</th>
            <th>Position</th>
            <th>Edit</th>
            <th>Delete</th>
         </tr>
         <!-- $scope.employees -->
         <tr ng-repeat="account in accounts">
            <td> {{ account.id }}</td>
            <td> {{ account.name }}</td>
            <td >{{ account.phone }}</td>
            <td >{{ account.position }}</td>
            <td>
            <a ng-click="edit(account)" class="edit-button">Edit</a>
            </td>
            <td>
            <a ng-click="delete(account)" 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("AccountManagement", []);

// Controller Part
app.controller("AccountController", function($scope, $http) {

    $scope.accounts = [];
    $scope.acc = {
        name: "",
        phone: "",
        position: ""
    };

    // Now load the data from server
    _refreshAccountData();

    // HTTP POST/PUT methods for add/edit account  
    // Call: http://localhost:8080/account
    $scope.submit = function() {

        var method = "";
        var url = "";

        if ($scope.acc.id == -1) {
            method = "POST";
            url = '/account';
        } else {
            method = "PUT";
            url = '/account';
        }
        var r = angular.toJson($scope.acc);
        $http({
            method: method,
            url: url,
            data: JSON.stringify($scope.acc),
            headers: {
                'Content-Type': 'application/json'
            }
        }).then(_success, _error);
    };

    $scope.addAccount = function() {
        _clearFormData();
    }

    // HTTP DELETE- delete employee by Id
    // Call: http://localhost:8080/employee/{empId}
    $scope.delete = function(acc) {
        $http({
            method: 'DELETE',
            url: '/account/' + acc.id
        }).then(_success, _error);
    };

    // In case of edit
    $scope.edit = function(acc) {
        $scope.acc.id = acc.id;
        $scope.acc.name = acc.name;
        $scope.acc.phone = acc.phone;
        $scope.acc.position = acc.position;

    };

    // Private Method  
    // HTTP GET- get all employees collection
    // Call: http://localhost:8080/employees
    function _refreshAccountData() {
        $http({
            method: 'GET',
            url: '/accounts'
        }).then(
            function(res) { // success
                $scope.accounts = res.data;
            },
            function(res) { // error
                console.log("Error: " + res.status + " : " + res.data);
            }
        );
    }

    function _success(res) {
        _refreshAccountData();
        _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.acc.id = -1;
        $scope.acc.name = "";
        $scope.acc.phone = "";
        $scope.acc.position = "";
    };
});

3. Deploy lên heroku (Coming soon)

Phần deploy ứng dụng lên heroku tôi sẽ bổ sung ở bài viết sau nha!

Happy Coding!!!

Posted in Java on Mar 09, 2019