iT邦幫忙

2024 iThome 鐵人賽

DAY 9
0
Software Development

我的SpringBoot絕學:7+2個專案,從新手變專家系列 第 9

Day9 第四個Spring Boot專案:客戶管理系統(2)後端與UUID介紹

  • 分享至 

  • xImage
  •  

Document

和entity的作用相同,但由於我們使用NoSQL,所以改稱呼為document。

collation用來指定使用的資料表名稱

//Customer.java
@Document(collection = "customer")
public class Customer {
    @Id
    private String id;

當留空時會出現錯誤,message是提供給前端顯示的訊息

@NotEmpty(message = "名字為必填項目")
    private String firstName;
    @NotEmpty(message = "姓氏為必填項目")
    private String lastName;
    @NotEmpty(message = "Email為必填項目")

必須符合Email的格式,例如xxx@xxx.xxx,不然會顯示錯誤

    @Email(message = "請填入正確的Email")
    private String email;

    public Customer(){

    }

    public Customer(String id, String firstName, String lastName, String email) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

在寫entity時,有些人可能注意到了,id的資料形態被設定成String而不是Long。

這是因為在MongoDB中,預設的id格式是UUID(Universally Unique Identifier),這裡提供一個產生的UUID的範例:560266b0-2bf0-4241-a578-66efb7c3ec80。

UUID的優勢是,可以大幅降低產生相同id的機率。

ID collision

傳統ID (自增ID )在很多人同時使用的大型專案,容易發生碰撞(collision),因為它們是根據時間來決定給的id順序。

在資料庫中,ID是唯一的,所以不能重複。

然而,當許多人同時在新建內容時,可能在同一毫秒送出新增資料的請求。

這時就有可能給出相同的ID ,由於資料庫不允許重複的ID 存在,發生了collision,所以增加資料的請求會被拒絕,並回傳錯誤,最後導致使用者無法新增內容。

使用者見到無法新增資料,一定會按下按鈕,再傳送一次,但是這將導致系統狀態進一步的惡化,最終讓伺服器當機,全部人不能新增資料。

如何解決collision

為了解決collision的問題,設計了UUID。

UUID是隨機產生的,不會遞增,這代表即使在同一毫秒送出請求,產生的UUID還是會有很大的不同。

UUID的概念加上隨機的成分,把collision發生的可能性降到最低,需要每秒產生10億個UUID並且持續85年,才有可能發生一次collision。

UUID的缺點

儘管UUID在避免collision的部分有強大的優勢,但是它也有缺點。

其中一個缺點是,查詢的速度比起傳統的ID慢。

由於UUID充滿隨機性,我們無法提前得知某個UUID是否存在資料庫中,因此只能一個一個確認。

而傳統的id是遞增的,有順序的,可以快速定位。

舉個例子

  • 在傳統ID中,1、2、3,那麼我們就能確定,下一個會是4。
  • UUID,7a46b115-8d22-454e-b6ff-8445f65d2cd4、d3b92d99-7317-444f-80d5-ad7d96230b6f,下一個會是多少?這是個難題。

傳統id vs UUID

總結一下

傳統id

  • 優點:查詢速度快,因為其有序性和可預測性。
  • 缺點:容易發生collision,尤其是同時有許多用戶時。

UUID

  • 優點:發生collision的機率極低。
  • 缺點:查詢速度相對較慢,由於其隨機性。

Repository

透過繼承MongoRepository來使用Spring Data MongoDB,這樣可以輕鬆的對 MongoDB 進行資料庫的操作。

和之前使用Spring Data JPA時相同,因為我們換成Spring Data MongoDB,所以我們改為extends MongoRepository。

Customer是Document,String是id的資料型態。

//CustomerRepository.java

public interface CustomerRepository extends MongoRepository<Customer, String> {
}

Service

實作建立客戶、取得全部的客戶、取得指定id的客戶、修改客戶、刪除客戶的功能。

//CustomerService.java

@Service
public class CustomerService {
    private final CustomerRepository customerRepository;

    public CustomerService(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }
//建立客戶並儲存
public void createCustomer(Customer customer) {
        customerRepository.save(customer);
    }
//取得全部的客戶
public List<Customer> getAllCustomers() {
        return customerRepository.findAll();
    }
//取得指定id的客戶
public Customer getCustomerById(String id) {
        return customerRepository.findById(id).orElse(null);
    }
//修改客戶內容
public void updateCustomer(Customer customer) {
        Customer oldCustomer = customerRepository.findById(customer.getId()).orElse(null);
        if (oldCustomer != null) {
            oldCustomer.setFirstName(customer.getFirstName());
            oldCustomer.setLastName(customer.getLastName());
            oldCustomer.setEmail(customer.getEmail());
            customerRepository.save(oldCustomer);
        }
    }
    //刪除客戶
    public void deleteCustomer(String id) {
        customerRepository.deleteById(id);
    }
}

Controller

之前的專案我們都是使用RestController,但是這次不一樣了,因為我們使用了thymeleaf作為前端,因此要使用Controller來呈現我們的網頁內容。

注意!是Controller,不是RestController

//CustomerController.java

@Controller
public class CustomerController {
    private final CustomerService customerService;

    public CustomerController(CustomerService customerService) {
        this.customerService = customerService;
    }
  • 顯示資料庫中全部的客戶內容
@GetMapping("/")
    public String listCustomers(Model model) {
    List<Customer> customers = customerService.getAllCustomers()

我們需要使用model才能把資料從後端傳給前端,"customers"是識別用的名稱

model.addAttribute("customers", customers);

Thymeleaf使用到的html檔案都需要放在src/main/resources/templates中

所以以下的程式碼,會將src/main/resources/templates/index.html的內容顯示在網頁上

return "index";
 }
  • 顯示新增客戶的網頁
@GetMapping("/new")
    public String newCustomer(Model model) {

創建一個空的新客戶,並傳送到前端

Customer customer = new Customer();
        model.addAttribute("customer", customer);

顯示new_customer.html的內容

return "new_customer";
    }
  • 儲存新增的客戶
 @PostMapping("/new")
    public String saveCustomer(Model model,

Valid用來在前端驗證內容
ModelAttribute用來接收前端中customer的資料

@Valid @ModelAttribute("customer") Customer customer,
                               BindingResult bindingResult) {

如果資料不符合規定,例如留空或email格式錯誤,就會將document設定的訊息顯示在網頁上

if(bindingResult.hasErrors()) {
            model.addAttribute("customer", customer);
            return "new_customer";
        }

符合規定就可以直接新增

customerService.createCustomer(customer);

導回http://localhost:8080/

return "redirect:/";
    }
  • 查看單一客戶的頁面
@GetMapping("/view/{id}")
    public String viewCustomer(Model model, @PathVariable("id") String id) {

將資料庫的客戶內容顯示在網頁上

Customer customer = customerService.getCustomerById(id);
        model.addAttribute("customer", customer);

顯示view_customer.html的內容

 return "view_customer";
    }
  • 顯示修改客戶的網頁
@GetMapping("/edit/{id}")
    public String editCustomer(Model model, @PathVariable("id") String id) {

將資料庫原本的客戶內容顯示在網頁上

 Customer customer = customerService.getCustomerById(id);
        model.addAttribute("customer", customer);

顯示edit_customer.html的內容

return "edit_customer";
    }
  • 儲存修改的客戶內容
@PostMapping("/edit/{id}")
    public String updateCustomer(Model model,
                                 @PathVariable("id") String id,

Valid用來在前端驗證內容
ModelAttribute用來接收前端中customer的資料

@Valid @ModelAttribute("customer") Customer customer,
                                 BindingResult bindingResult) {

如果資料不符合規定,例如留空或email格式錯誤,就會將document設定的訊息顯示在網頁上

if(bindingResult.hasErrors()) {
            model.addAttribute("customer", customer);
            return "edit_customer";
        }
        customerService.updateCustomer(customer);

導回http://localhost:8080/

return "redirect:/";
    }
  • 刪除客戶

    @GetMapping("/delete/{id}")
    public String deleteCustomer(@PathVariable("id") String id) {
        customerService.deleteCustomer(id);
        //導回http://localhost:8080/
        return "redirect:/";
    }
}

專案後端的部分結束了,可以從程式碼的部分觀察到,使用thymeleaf除了要寫前端的程式碼,還要另外寫後端如何去和前端交流的部分。


上一篇
Day8 第四個Spring Boot專案:客戶管理系統(1)MongoDB與資料庫設定
下一篇
Day10 第四個Spring Boot專案:客戶管理系統(3)前端與測試
系列文
我的SpringBoot絕學:7+2個專案,從新手變專家31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言