iT邦幫忙

2023 iThome 鐵人賽

DAY 29
0
Software Development

Spring Boot 零基礎入門系列 第 29

Spring Boot 零基礎入門 (29) - 實戰演練 - 打造一個簡單的圖書館系統

  • 分享至 

  • xImage
  •  

個人網站好讀版:Spring Boot 零基礎入門 系列文章

哈囉大家好,我是古古。

在前面的文章中,我們有介紹了 Spring Boot 中的許多用法,包含 Spring IoC、Spring AOP、Spring MVC(和前端溝通)、以及 Spring JDBC(和資料庫溝通),並且我們也介紹了 MVC 架構模式的概念,以及 Controller-Service-Dao 三層式架構的實作。

所以接著的這篇文章,我們就會來做個實戰演練,總和前面所學習到的所有功能,練習實作一個圖書館的管理系統出來,所以我們就開始吧!

補充:本文中所使用的完整程式碼會放在 GitHub 上 springboot-library,建議可以搭配觀看,學習效果更好。

功能分析:圖書館管理系統


在我們開始動手寫程式去實作圖書館管理系統之前,首先可以先來分析一下,在這個圖書館的管理系統中,我們想要提供什麼樣的功能。

像是圖書館管理系統顧名思義,就是用來管理「書」的,所以我們針對「書」這個資源,就可以去實作他的 CRUD 四大基本操作,也就是「新增一本書」、「查詢某一本書」、「更新某本書的資訊」、「刪除某一本書」。

因此下面就會分別來介紹,要如何在 Spring Boot 中設計和實作出「書」的 CRUD 四個功能,完成一個簡易的圖書館管理系統。

資料庫 table 設計


在設計資料庫 table 時,因為是針對「書本」這個資源來設計,因此我們可以設計一個 book table,去儲存書本的相關資訊。以下是創建 book table 的 SQL 語法:

CREATE TABLE book
(
    book_id            INT          NOT NULL PRIMARY KEY AUTO_INCREMENT,
    title              VARCHAR(128) NOT NULL,
    author             VARCHAR(32)  NOT NULL,
    image_url          VARCHAR(256) NOT NULL,
    price              INT          NOT NULL,
    published_date     TIMESTAMP    NOT NULL,
    created_date       TIMESTAMP    NOT NULL,
    last_modified_date TIMESTAMP    NOT NULL
);

其中各個欄位的含義如下:

  • book_id:表示 book 的唯一 id,由資料庫自動遞增

  • title:表示此書的書名

  • author:表示此書的作者

  • image_url:儲存此書的圖片連結

  • price:此書的販售價格

    補充:在實務上只要牽扯到「金錢」的部分,通常會用 BigDecimal 特別處理,不過由於此專案主要在練習 Spring Boot 的基礎結構和用法,因此此處使用 Integer 類型來簡化實作細節。

  • published_date:此書的上架時間

  • created_date:創建這筆數據的時間

  • last_modified_date:最後修改這筆數據的時間

而設計好資料庫 table 之後,就可以將這段 SQL 語法貼到 IntelliJ 中的 console 上,在 IntelliJ 中執行這個 SQL 語法,就可以在 myjdbc database 中創建 book table 出來。

29-1.png

29-2.png

1. 實作「查詢某一本書」的功能


創建好 book table 之後,接下來我們就可以開始來實作 Spring Boot 程式了!

通常在實作基礎的 CRUD 功能時,建議可以先從「查詢功能」開始實作,因此我們就先從「查詢一本書」這個功能開始實作。

BookController(Controller 層)的實作

首先我們可以先在 BookController 中,添加如下的程式:

@GetMapping("/books/{bookId}")
public ResponseEntity<Book> getBook(@PathVariable Integer bookId) {
    Book book = bookService.getBookById(bookId);

    if (book != null) {
        return ResponseEntity.status(HttpStatus.OK).body(book);
    } else {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
    }
}

補充:由於程式的篇幅過長,因此本文中只會擷取部分重點來介紹,完整的程式碼可以前往 GitHub springboot-library 查看。

在 BookController 中,因為他是屬於 Controller 層,所以他會使用 @GetMapping,去接住前端放在 url 路徑中的 bookId 的參數。

而在接到 bookId 的值之後,BookController 就會直接往後傳給 BookService 去做後續處理,即是將商業邏輯寫在 Service 層,讓 Controller 層保持「和前端溝通」的部分。

而當 BookService 返回查詢到的 book 數據之後,BookController 就可以根據「是否有查詢到數據與否」,去決定要返回給前端的 Http status code 為何。

像是在下面的程式中,if 區塊就呈現出「當 book 不為 null 時,就返回 200 OK 的 Http status code 給前端,並且把 book 數據放在 response body 中」的資訊。而在 else 區塊中,則是呈現「當 book 為 null 時,就返回 404 Not Found 的 Http status code 給前端」。

if (book != null) {
    return ResponseEntity.status(HttpStatus.OK).body(book);
} else {
    return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}

補充:如果不熟悉 Http status code 的話,可以回頭參考 Day 23 - Http Status Code(Http 狀態碼)介紹 的文章。

BookService(Service 層)的實作

實作完 BookController 之後,接著我們往下實作 BookService。

當 BookService 收到 Controller 傳過來的 bookId 之後,因為這裡的邏輯比較簡單,沒有太複雜的商業邏輯要處理,因此 BookService 就只要直接去 call BookDao 的方法,交由 Dao 層去資料庫查詢數據即可。

public Book getBookById(Integer bookId) {
    return bookDao.getBookById(bookId);
}

BookDao(Dao 層)的實作

而在 BookDao 這裡,因為他是 Dao 層,負責和「資料庫」溝通,因此就可以在這裡使用 Spring JDBC 的 query() 方法,在資料庫中執行 SELECT SQL,去查詢這一筆 bookId 的數據出來。

public Book getBookById(Integer bookId) {
    String sql = "SELECT book_id, title, author, image_url, price, published_date, created_date, last_modified_date " +
            "FROM book WHERE book_id = :bookId";

    Map<String, Object> map = new HashMap<>();
    map.put("bookId", bookId);

    List<Book> bookList = namedParameterJdbcTemplate.query(sql, map, new BookRowMapper());

    if (bookList.size() > 0) {
        return bookList.get(0);
    } else {
        return null;
    }
}

因此到這邊,「查詢某一本書」的功能就實作完畢了!

API Tester 實際測試

因為目前 book table 中沒有任何數據,因此在測試「查詢某一本書」的功能之前,我們需要先在 IntelliJ 的 console 中執行下列的 SQL 語法,手動插入一筆數據到 book table 中。

INSERT INTO book (title, author, image_url, price, published_date, created_date, last_modified_date) VALUES ('先問,為什麼?:顛覆慣性思考的黃金圈理論,啟動你的感召領導力', '賽門‧西奈克', 'https://im1.book.com.tw/image/getImage?i=https://www.books.com.tw/img/001/092/65/0010926506.jpg', 331, '2018-05-23 00:00:00', '2023-10-14 02:42:02', '2023-10-14 02:42:02');

插入這本書的數據之後,接著就可以在 API Tester 中,填上以下的參數設定,來測試「查詢某一本書」的功能:

29-3.png

填寫完成之後,就可以按下 Send 鍵進行測試,結果如下:

29-4.png

補充:時間格式的設定

這裡題外話補充一下,如果大家不是直接下載 GitHub 上的 springboot-library 程式來使用,而是靠自己自己一行一行輸入程式的話,那麼你在上圖中所返回的 publishedDate、createdDate、以及 lastModifiedDate 的值,就會是 2018-05-22T16:00:00.000+00:00 的格式,而不是上圖中的 2018-05-23 00:00:00 的格式。

之所以會有這個差異,是因為 Spring Boot 預設的時間格式為 2018-05-22T16:00:00.000+00:00,而要改變這個格式成 2018-05-23 00:00:00 的話,會需要在 application.properties 檔案中進行相關的設定。

因此就要麻煩大家,手動在 application.properties 檔案中添加下面這兩行程式,這樣子才能夠將 Spring Boot 的時間格式,改成是 2018-05-23 00:00:00 的樣子。

spring.jackson.time-zone=GMT+8
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

29-5.png

2. 實作「新增一本書」的功能


實作完「查詢某一本書」的功能之後,第二個推薦實作的,就是「新增一本書」的功能。

BookController(Controller 層)的實作

首先我們一樣是可以先在 BookController 中,添加以下的程式,去接住前端所傳過來的參數:

@PostMapping("/books")
public ResponseEntity<Book> createBook(@RequestBody BookRequest bookRequest) {
    Integer bookId = bookService.createBook(bookRequest);

    Book book = bookService.getBookById(bookId);

    return ResponseEntity.status(HttpStatus.CREATED).body(book);
}

而在實作「新增一本書」時,通常會分成兩個步驟來實作:

  1. 先去 call BookService 的 createBook() 方法,真的去資料庫中創建一筆 Book 數據出來
  2. 當資料庫創建好數據之後,重新查詢一次該筆 Book 數據,然後將這筆 Book 數據原封不動的回傳給前端

首先第一步比較單純,就是在 BookController 接收到前端傳過來的 BookRequest 參數,接著將 BookRequest 往後傳遞給 BookService 去做處理。

範例的前端請求如下:

{
  "title": "原子習慣:細微改變帶來巨大成就的實證法則",
  "author": "詹姆斯‧克利爾",
  "imageUrl": "https://im1.book.com.tw/image/getImage?i=https://www.books.com.tw/img/001/082/25/0010822522.jpg",
  "price": 260,
  "publishedDate": "2019-06-01 00:00:00"
}

29-6.png

而第二步的實作與否,其實不會影響到「新增一本書」的具體功能(因為第二步只是查詢而已),因此可以看個人的喜好,決定是否要實作第二步。

BookService(Service 層)的實作

在「新增一本書」的實作中,因為 BookService 的實作也是比較簡單,因此只要直接去 call BookDao 的方法,交由 Dao 層去資料庫中創建一筆數據就可以了。

public Integer createBook(BookRequest bookRequest) {
    return bookDao.createBook(bookRequest);
}

BookDao(Dao 層)的實作

而在 BookDao 裡面,因為他是 Dao 層,負責和「資料庫」溝通,因此就可以在這裡使用 Spring JDBC 的 update() 方法,在資料庫中執行 INSERT SQL,去新增一筆 Book 的數據。

public Integer createBook(BookRequest bookRequest) {
    String sql = "INSERT INTO book(title, author, image_url, price, published_date, created_date, last_modified_date) " +
            "VALUES (:title, :author, :imageUrl, :price, :publishedDate, :createdDate, :lastModifiedDate)";

    Map<String, Object> map = new HashMap<>();
    map.put("title", bookRequest.getTitle());
    map.put("author", bookRequest.getAuthor());
    map.put("imageUrl", bookRequest.getImageUrl());
    map.put("price", bookRequest.getPrice());
    map.put("publishedDate", bookRequest.getPublishedDate());

    Date now = new Date();
    map.put("createdDate", now);
    map.put("lastModifiedDate", now);

    KeyHolder keyHolder = new GeneratedKeyHolder();

    namedParameterJdbcTemplate.update(sql, new MapSqlParameterSource(map), keyHolder);

    int bookId = keyHolder.getKey().intValue();

    return bookId;
}

這裡比較值得注意的是後面的 KeyHolder 的用法,他是在 update() 的一種進階的用法。

KeyHolder 的用途,是在創建一筆數據到資料庫時,取得「資料庫自動生成的 id 的值」。

之所以要特別使用 KeyHolder,是因為我們前面在設計 book table 時,其中的 book_id 欄位,我們是設定成 AUTO_INCREMENT,而這就會導致一種現象,即是「我們在 Spring Boot 中插入了一筆數據,但是我們卻不知道這筆數據的 id 值是多少」。

book_id  INT NOT NULL PRIMARY KEY AUTO_INCREMENT,

因此 KeyHolder 就是為了解決這個問題而誕生的!

只要在執行 update() 方法時,同時也加入一個 KeyHolder 的參數,這樣子就可以在創建數據的同時,取得「資料庫自動生成的 id 的值」了(也就是取得 book_id 的值)。

KeyHolder keyHolder = new GeneratedKeyHolder();
namedParameterJdbcTemplate.update(sql, new MapSqlParameterSource(map), keyHolder);
int bookId = keyHolder.getKey().intValue();

所以到這邊,我們也完成了「新增一本書」的功能實作了!

API Tester 實際測試

要測試「新增一本書」的功能的話,只需要在 API Tester 中,填上以下的參數設定:

{
  "title": "原子習慣:細微改變帶來巨大成就的實證法則",
  "author": "詹姆斯‧克利爾",
  "imageUrl": "https://im1.book.com.tw/image/getImage?i=https://www.books.com.tw/img/001/082/25/0010822522.jpg",
  "price": 260,
  "publishedDate": "2019-06-01 00:00:00"
}

29-7.png

填寫完成之後,就可以按下 Send 鍵進行測試,結果如下:

29-8.png

注意這裡有一個小細節,就是「返回的 Http status code 為 201,而不是普通的 200」。

201 表示的不僅是這一次的 Http 請求成功而已,他還額外表示「有一個新的資源成功的被創建了」的含義。也因為如此,201 很常被用在 POST 請求所返回的 Http status code 上。

補充:有關 201 的詳細介紹,可以回頭參考 Day 23 - Http Status Code(Http 狀態碼)介紹 的文章。

3. 實作「更新某一本書」的功能


實作完「新增一本書」的功能之後,接著我們往下實作「更新某一本書」的功能。

BookController(Controller 層)的實作

首先我們一樣是可以先在 BookController 中,添加以下的程式,去接住前端所傳過來的參數:

@PutMapping("/books/{bookId}")
public ResponseEntity<Book> updateBook(@PathVariable Integer bookId,
                                       @RequestBody BookRequest bookRequest) {

    // 檢查 book 是否存在Book
    book = bookService.getBookById(bookId);
    
    if (book == null) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
    }

    // 修改 Book 的數據
    bookService.updateBook(bookId, bookRequest);

    Book updatedBook = bookService.getBookById(bookId);

    return ResponseEntity.status(HttpStatus.OK).body(updatedBook);
}

在實作「更新某一本書」的功能時,也是會分成兩個步驟來進行:

  1. 先檢查此 Book 是否存在,如果不存在,就直接返回 404 Not Found 的錯誤給前端
  2. 如果此 Book 存在,則修改該 Book 的數據,並且返回修改後的 Book 數據給前端

之所以會分成兩個步驟來執行,是因為透過這樣子的寫法,才能讓前端知道「他具體遇到的是什麼狀況」。

舉例來說,當前端拿到 404 的錯誤時,前端就知道可能是他的 bookId 參數寫錯,導致他嘗試去更新一個不存在的 Book,因此這時後前端就可以回頭去修改他的請求參數。而如果當前端拿到的是 200 成功,那就表示 Book 數據更新成功了。

假設我們不分成兩步驟來做,而是一拿到前端的 bookId 參數,就直接去更新該 bookId 的數據的話,雖然在資料庫的執行上不會出問題,但是對於前端來說,他卻會拿到 200 的成功回應,因此就會出現 「前端明明嘗試更新一筆不存在的數據,但是我們卻跟他說 OK 更新成功」 的奇怪情境,邏輯上並不是很合理。

所以這裡在實作上,才會特別分成兩個步驟,先判斷 Book 是否存在,如果存在,才更新該 Book 的數據,透過這樣的實作,才能讓我們的後端程式更忠實的去呈現正確的邏輯。

BookService(Service 層)的實作

在實作「更新某一本書」時,因為 BookService 的實作也是比較簡單,因此只要直接去 call BookDao 的方法,交由 Dao 層去修改資料庫中的數據即可。

public void updateBook(Integer bookId, BookRequest bookRequest) {
    bookDao.updateBook(bookId, bookRequest);
}

BookDao(Dao 層)的實作

而在 BookDao 裡面,則可以使用 Spring JDBC 的 update() 方法,在資料庫中執行 UPDATE SQL,去更新這一筆 book 的數據。

public void updateBook(Integer bookId, BookRequest bookRequest) {
    String sql = "UPDATE book SET title = :title, author = :author, image_url = :imageUrl, " +
            "price = :price, published_date = :publishedDate, last_modified_date = :lastModifiedDate" +
            " WHERE book_id = :bookId ";

    Map<String, Object> map = new HashMap<>();
    map.put("bookId", bookId);

    map.put("title", bookRequest.getTitle());
    map.put("author", bookRequest.getAuthor());
    map.put("imageUrl", bookRequest.getImageUrl());
    map.put("price", bookRequest.getPrice());
    map.put("publishedDate", bookRequest.getPublishedDate());

    map.put("lastModifiedDate", new Date());

    namedParameterJdbcTemplate.update(sql, map);
}

這裡在實作上需要注意一個細節,即是在更新數據時,要記得也去更新 book table 中的 last_modified_date 欄位的值,將他更新成當前的時間。

之所以要特別更新 last_modified_date 的時間,是因為 last_modified_date 的含義是「最後修改此筆數據的時間」,因此其他人就可以透過這個欄位,知道這筆數據最後是在什麼時候被修改過。

因此當我們對 book table 中的數據進行更新時,就要記得同時也去更新 last_modified_date 的值,確保「最後更新時間」的值有一起被修改成當前時間。

所以到這邊,我們就完成了「更新某一本書」的功能實作了!

API Tester 實際測試

假設我們要更新 id 為 2 的那本書,那麼就只需要在 API Tester 中,填上以下的參數設定:

{
  "title": "Atomic Habits: An Easy & Proven Way to Build Good Habits & Break Bad Ones",
  "author": "James Clear",
  "imageUrl": "https://im1.book.com.tw/image/getImage?i=https://www.books.com.tw/img/001/082/25/0010822522.jpg",
  "price": 1000000,
  "publishedDate": "2019-06-01 00:00:00"
}

29-9.png

填寫完成之後,就可以按下 Send 鍵進行測試,結果如下:

29-10.png

4. 實作「刪除某一本書」的功


實作完上述的功能之後,最後我們可以來實作「刪除某一本書」的功能。

BookController(Controller 層)的實作

首先我們一樣是可以先在 BookController 中,添加以下的程式,去接住前端所傳過來的參數:

@DeleteMapping("/books/{bookId}")
public ResponseEntity<?> deleteBook(@PathVariable Integer bookId) {
    bookService.deleteBookById(bookId);

    return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}

在實作「刪除某一本書」的功能時,他的設計理念,會和上面的「更新某一本書」有點不一樣。

在上面的「更新某一本書」中,我們會先去檢查該書是否存在,然後再根據該書是否存在,去返回不同的 Http status code 給前端。但是在實作「刪除某一本書」時,不管這本書存不存在,我們就只要通通回傳「成功的 204 No Content」的 Http status code 給前端即可。

之所以會有這樣子的差異,是因為在「刪除某一本書」的觀念裡,他的目的就是「要去刪除那一本書」,所以換句話說的話,就是「要讓那本書的數據從地球上消失」就對了,因此這時會有兩種情況:

  • 假設這本書存在,那麼我們就刪除他,所以這本書的數據就不存在了,完美!因此就回傳 204 No Content 給前端
  • 假設這本書不存在,那麼即使我們沒有執行刪除的動作,這本書的數據本身也是不存在的,所以同樣也是回傳 204 No Content 給前端

所以對於「刪除某一本書」的功能來說,他在意的「不是」有沒有真的刪除到數據,他在意的是「該數據是否真的消失了」。 只要數據消失,不管他是曾經出現過然後被刪除、或是從來就沒出現過,對於「刪除某一本書」這個功能來說,就通通都是回傳成功的 204 No Content 就對了!

BookService(Service 層)的實作

在「刪除某一本書」的功能中,因為 BookService 的實作也是比較簡單,因此只要直接去 call BookDao 的方法,交由 Dao 層去刪除資料庫中的數據即可。

public void deleteBookById(Integer bookId) {
    bookDao.deleteBookById(bookId);
}

BookDao(Dao 層)的實作

而在 BookDao 裡面,則是可以使用 Spring JDBC 的 update() 方法,在資料庫中執行 DELETE SQL,去刪除這一筆 book 的數據。

public void deleteBookById(Integer bookId) {
    String sql = "DELETE FROM book WHERE book_id = :bookId ";

    Map<String, Object> map = new HashMap<>();
    map.put("bookId", bookId);

    namedParameterJdbcTemplate.update(sql, map);
}

所以到這邊,我們就完成了「刪除某一本書」的功能實作了!

API Tester 實際測試

假設我們要刪除 id 為 1 的那本書,那麼就只需要在 API Tester 中,填上以下的參數設定:

29-11.png

填寫完成之後,就可以按下 Send 鍵進行測試,結果如下:

29-12.png

圖書館管理系統總結


在實作完上述的四個功能之後,我們就完成了針對「書本」這個資源的 CRUD 實作了!所以在上面的實作中,我們完成了以下四個功能:

  • 新增一本書(Create)
  • 查詢某一本書(Read)
  • 更新某一本書的資訊(Update)
  • 刪除某一本書(Delete)

因此對於管理員來說,就可以透過這個後端系統,在資料庫中去新增、刪除、查詢、和修改裡面的書本了!

不過,對於一個圖書館管理系統來說,其實單單只有 CRUD 的功能,是沒辦法滿足所有需求的。

像是管理員可能需要「查詢書本列表」的功能,要能夠根據出版時間、價格、名字...等等的因素,去列出符合條件的書本有哪些。

又或者是管理員可能想要「權限管理」的功能,只讓正職員工擁有「新增、修改、刪除」書本的功能,而讓實習生只有「查詢」書本的功能,避免實習生誤操作,使得資料庫中的書本資料被刪除。

因此針對一個圖書館管理系統,背後還是有許多功能可以延伸的!不過這些強大的功能,都是建立在 CRUD 功能已經完成的前提下,才有辦法往下延伸。

所以大家在剛入門 Spring Boot 時,建議一定要打好 CRUD 的基礎,只有當你能夠獨當一面實作出 CRUD 的功能時,才算是具備後端工程師的基本實作能力。

補充:雖然大家在實作 CRUD 時可能會覺得很重複很無聊,但還是建議大家要好好打下這邊的基礎,練好 CRUD 的基本功,這樣後續不管是要繼續去學習 Spring Boot 的進階功能,還是延伸去學習 Spring Security...等框架,才能更好上手。

總結


這篇文章我們先分析了「圖書館管理系統」需要實作哪些功能,並且針對「書本」這個資源,去設計了 CRUD 的四大基本操作,同時我們也有實際到 Spring Boot 中,練習去實作出 CRUD 的四個功能出來。

那麼有關 Spring Boot 的基礎入門介紹,到這邊就告一個段落了,因此下一篇文章我們就會來總結一下,在這 30 篇文章中我們都介紹了哪些知識,來做一個大總結,那我們就下一篇文章見啦!

補充:本文是擷取自我開設的線上課程 Java 工程師必備!Spring Boot 零基礎入門 的內容,如果你想了解更多的 Spring Boot 的用法,歡迎參考課程簡介。


上一篇
Spring Boot 零基礎入門 (28) - MVC 架構模式 - Controller-Service-Dao 三層式架構
下一篇
Spring Boot 零基礎入門 (30) - Spring Boot 零基礎入門總結
系列文
Spring Boot 零基礎入門30
.

2 則留言

1

您好,我想請問實作「新增一本書」這一個部分的功能
我的 API Tester 顯示 400 Bad Request
Controller Service Dao 以及 Request 的程式碼我確認都沒有問題

Spring Console 的報錯為:
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type java.util.Date from String "2019-06-01 00:00:00": not a valid representation (error: Failed to parse Date value '2019-06-01 00:00:00': Cannot parse date "2019-06-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails (leniency? null))]

您的文章曾經提過

前端傳給後端的參數名稱不同、或是說請求的格式有問題,都是可以被歸類在 400 這個 status code,所以以後只要看到 400,通常第一件事,就是回頭檢查一下是不是請求的參數寫錯了

加上報錯好像也有提到格式不匹配的問題,請問是否是
這一段參數設定的

{
  "title": "原子習慣:細微改變帶來巨大成就的實證法則",
  "author": "詹姆斯‧克利爾",
  "imageUrl": "https://im1.book.com.tw/image/getImage?i=https://www.books.com.tw/img/001/082/25/0010822522.jpg",
  "price": 260,
  "publishedDate": "2019-06-01 00:00:00"
}

"publishedDate": "2019-06-01 00:00:00" 這個部分的設定有問題呢?

看更多先前的回應...收起先前的回應...

不得不說 ChatGPT 還真的是好用xD ,只不過你得先有耐心地將你的問題統整好後,完整地敘述給它聽。
以下為這個問題解決的方法,如果您有遇到同樣的問題,可以參考看看。

在BookRequest 以及 Book 這兩個Class中的

private Date publishedDate;

加上

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")

變成

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date publishedDate;

透過這個註解来指定日期的解析格式
我的部分是加入這個註解之後,API Tester 測試就是 201 成功了

資料庫也順利新增一筆資料了!

f88083 iT邦新手 4 級 ‧ 2024-04-07 00:22:47 檢舉

謝謝分享

f88083 iT邦新手 4 級 ‧ 2024-04-24 18:29:45 檢舉

後來發現作者的github程式碼中有把相關的設定加入到application.properties裡面,就無需這個annotation了

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/myjdbc?serverTimezone=Asia/Taipei&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=springboot

spring.jackson.time-zone=GMT+8
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

原始程式碼

古古 iT邦新手 2 級 ‧ 2024-08-08 18:53:32 檢舉

哇我竟然漏掉這一串討論了,抱歉抱歉😭

時間格式之所以會是 yyyy-MM-dd HH:mm:ss,確實是因為我在 application.properties 中偷加了下面兩行程式所導致的

spring.jackson.time-zone=GMT+8
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

抱歉我應該把這個寫進去文章裡面的🥹,感謝大家的回覆!文章也已經修正完畢了!

0
f88083
iT邦新手 4 級 ‧ 2024-04-07 00:24:48

完整的把所有文章看過了,感謝你的系列文章,收穫良多,字詞簡潔明瞭,解釋通暢直白,感激不盡!!

古古 iT邦新手 2 級 ‧ 2024-04-11 20:15:25 檢舉

噢噢噢有幫助到你就好!感謝感謝~

我要留言

立即登入留言