Giới thiệu
Đầu xuân, tôi muốn viết bài này để chia sẻ với tập thể giảng viên Techmaster cũng như các sinh viên hay lập trình viên về việc chúng ta nên tích cực tận dụng AI, ChatGPT để viết ra phần mềm sáng tạo hơn, chất lượng hơn trong khoảng thời gian ngắn hơn, chi phí rẻ hơn. Nếu bạn chưa nắm vững Java hay Spring Boot thì việc ra lệnh cho ChatGPT không có nhiều ý nghĩa vì bạn chả hiểu code ChatGPT sinh ra như thế nào. Tuy nhiên nếu bạn đã từng lập trình một web framework khác như PHP Laravel, Node.js, ASP.net MVC muốn chuyển qua Java Spring Boot thì ChatGPT cực kỳ hữu ích.
Đề bài
Xây dựng một ứng dụng REST API bằng web frame work Spring Boot, ngôn ngữ lập trình Java thực hiện nghiệp vụ quản lý sách (Book) và người mượn sách (Borrower). Mình quyết định ra lệnh cho ChatGPT thực hiện viết mã.
Tổng kết nhanh
Thời gian viết yêu cầu để ChatGPT viết mã, sau đó kiểm tra lại xem nó viết có đúng không là khoảng .5 tiếng. Nếu tự viết bằng tay với kinh nghiệm lập trình Spring Boot đã có, thì mất khoảng 3-4 tiếng. Ưu điểm của tự viết code là hiểu rõ từng dòng code mình viết ra, sau này tinh chỉnh dễ dàng. Tuy nhiên nếu tôi hoàn toàn chưa có kinh nghiệm hoặc có rất ít kinh nghiệm với Spring Boot thì có thể tăng lên 4-6 tiếng chủ yếu là Google tìm hướng dẫn.
Sau khi ChatGPT sinh mã, nếu không hiểu rõ được những mã này thì thực sự bạn khó tiếp tục phát triển tiếp ứng dụng. Tạm thời tôi có thể dùng ChatGPT như là một boiler plate code generator
.
1. Tạo 2 model Book
và Borrower
quan hệ nhiều - nhiều
Tôi : Write a Spring Boot app that has Book, Borrower models. Relationship between Book and Borrower is Many to Many. Book model has following properties: unique ID with fixed length 12 characters, title as string, authors as array of string, published year as integer. Borrower model has following properties: unique ID with fixed length 12 characters, full name as string, email and mobile phone.
ChatGPT
Here’s an example of a Spring Boot app that has Book and Borrower models with a many-to-many relationship:
@Entity
@Table(name = "books")
public class Book {
@Id
@Column(length = 12)
private String id;
private String title;
@ElementCollection
private List<String> authors;
private int publishedYear;
@ManyToMany(mappedBy = "books")
private List<Borrower> borrowers;
// Getters and setters
}
Book.java
@Entity
@Table(name = "borrowers")
public class Borrower {
@Id
@Column(length = 12)
private String id;
private String fullName;
private String email;
private String mobilePhone;
@ManyToMany
@JoinTable(
name = "borrowers_books",
joinColumns = @JoinColumn(name = "borrower_id"),
inverseJoinColumns = @JoinColumn(name = "book_id")
)
private List<Book> books;
// Getters and setters
}
Borrower.java
In this example, the Book
model has a unique ID with a fixed length of 12 characters, a title as a string, authors as an array of strings, and a published year as an integer. The Borrower
model has a unique ID with a fixed length of 12 characters, a full name as a string, an email, and a mobile phone. The relationship between the Book and Borrower models is many-to-many, and is defined by the @ManyToMany
annotation on the borrowers and books properties. The @JoinTable
annotation is used to specify the name of the join table and the names of the join columns.
2. Viết automation unit test để kiểm thử 2 model trên
Tôi : Great. Using JUnit5 and AssertJ please write unit test to verify above two models: Book and Borrower
ChatGPT
Here’s an example of unit tests using JUnit 5 and AssertJ that test the Book
and Borrower
models:
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
public class BookAndBorrowerTests {
@Test
void testBookProperties() {
Book book = new Book();
book.setId("1234567890AB");
book.setTitle("The Great Gatsby");
book.setAuthors(Arrays.asList("F. Scott Fitzgerald"));
book.setPublishedYear(1925);
assertThat(book.getId()).isEqualTo("1234567890AB");
assertThat(book.getTitle()).isEqualTo("The Great Gatsby");
assertThat(book.getAuthors()).containsExactly("F. Scott Fitzgerald");
assertThat(book.getPublishedYear()).isEqualTo(1925);
}
@Test
void testBorrowerProperties() {
Borrower borrower = new Borrower();
borrower.setId("0987654321ZA");
borrower.setFullName("John Doe");
borrower.setEmail("johndoe@example.com");
borrower.setMobilePhone("+1234567890");
assertThat(borrower.getId()).isEqualTo("0987654321ZA");
assertThat(borrower.getFullName()).isEqualTo("John Doe");
assertThat(borrower.getEmail()).isEqualTo("johndoe@example.com");
assertThat(borrower.getMobilePhone()).isEqualTo("+1234567890");
}
@Test
void testManyToManyRelation() {
Book book1 = new Book();
book1.setId("1234567890AB");
book1.setTitle("The Great Gatsby");
book1.setAuthors(Arrays.asList("F. Scott Fitzgerald"));
book1.setPublishedYear(1925);
Book book2 = new Book();
book2.setId("0987654321ZA");
book2.setTitle("The Catcher in the Rye");
book2.setAuthors(Arrays.asList("J.D. Salinger"));
book2.setPublishedYear(1951);
Borrower borrower1 = new Borrower();
borrower1.setId("1111111111AA");
borrower1.setFullName("Jane Smith");
borrower1.setEmail("janesmith@example.com");
borrower1.setMobilePhone("+1234567890");
Borrower borrower2 = new Borrower();
borrower2.setId("2222222222BB");
borrower2.setFullName("Bob Johnson");
borrower2.setEmail("bobjohnson@example.com");
borrower2.setMobilePhone("+0987654321");
borrower1.setBooks(Arrays.asList(book1, book2));
borrower2.setBooks(Arrays.asList(book1, book2));
assertThat(book1.getBorrowers()).containsExactlyInAnyOrder(borrower1, borrower2);
assertThat(book2.getBorrowers()).containsExactlyInAnyOrder(borrower1, borrower2);
assertThat(borrower1.getBooks()).containsExactlyInAnyOrder(book1, book2);
assertThat(borrower2.getBooks()).containsExactlyInAnyOrder(book1, book2);
}
}
BookAndBorrowerTests.java
In this test case, it creates two books, two borrowers and assign these books to the borrowers and vice versa, and then it assert that the books and borrowers are equal to the expected values.
It is also important to note that since it is a many-to-many relationship, the order of the elements in the list can be different, so the containsExactlyInAnyOrder
is used to check the elements in any order.
Nếu code quá dài, ChatGPT có thể ngưng giữa chừng, các bạn gõ tiếp “Please continue”, ChatGPT sẽ tiếp tục viết tiếp.
3. Viết 2 JPA repository để thao tác 2 model Book và Borrower xuống CSDL
Tôi : Great job! Please write two JPA repositories that manipulate Book and Borrower models. In this project just use H2 as database
ChatGPT
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, String> { }
BookRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
public interface BorrowerRepository extends JpaRepository<Borrower, String> { }
BorrowerRepository.java
In this example, the BookRepository
extends JpaRepository
and uses the Book
model and its ID type, String, as its generic types. The BorrowerRepository
is similar, but uses the Borrower
model instead.
By extending JpaRepository
, these repositories will have a number of built-in methods for performing CRUD operations on the corresponding model, such as findAll(), save(), delete(), etc.
To use these repositories, you will need to enable JPA and H2 in your Spring Boot application. You can do this by adding the following dependencies to your pom.xml
file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
pom.xml
You also need to configure the H2 database by adding the following properties to your application.properties
file:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
application.properties
These properties configure the H2 database to run in-memory with the name testdb, and configure the JPA to use the H2 dialect.
Once you have these repositories and configurations set up, you can use them in your application to perform CRUD operations on theBook
and Borrower
models. For example, you can use the save() method to add a new book to the database, or the findAll() method to retrieve a list of all books in the database.
It’s also worth noting that you should annotate your entities with @Entity
and @Table
and your fields with @Id
and @Column
to specify the table name and the column name respectively.
ChatGPT quá chi tiết và tỷ mỷ. Nó còn gợi ý luôn để dùng H2 database thì cần bổ xung dependencies vào pom.xml
4. Viết 2 services sử dụng Book và Borrower repositories
Tôi : Great, write 2 service components that consume Book and Borrower repositories
ChatGPT
Here’s an example of two service components, BookService
and BorrowerService
, that consume the BookRepository
and BorrowerRepository
respectively:
@Service
public class BookService {
private final BookRepository bookRepository;
@Autowired
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
public List<Book> getAllBooks() {
return bookRepository.findAll();
}
public Book saveBook(Book book) {
return bookRepository.save(book);
}
public void deleteBook(String id) {
bookRepository.deleteById(id);
}
public Optional<Book> findBookById(String id) {
return bookRepository.findById(id);
}
}
BookService.java
@Service
public class BorrowerService {
private final BorrowerRepository borrowerRepository;
@Autowired
public BorrowerService(BorrowerRepository borrowerRepository) {
this.borrowerRepository = borrowerRepository;
}
public List<Borrower> getAllBorrowers() {
return borrowerRepository.findAll();
}
public Borrower saveBorrower(Borrower borrower) {
return borrowerRepository.save(borrower);
}
public void deleteBorrower(String id) {
borrowerRepository.deleteById(id);
}
public Optional<Borrower> findBorrowerById(String id) {
return borrowerRepository.findById(id);
}
}
BorrowerService.java
Both of these services are annotated with @Service
to make them Spring components, and they both have a constructor that accepts the corresponding repository as a parameter and @Autowired
to automatically initialize the repository.
Both services have basic CRUD operations like save(), findAll(), delete() and findById()
which can be used to perform CRUD operations on the Book and Borrower models.
It’s also worth noting that in the saveBook
or saveBorrower
method, it’s returning the saved object that has the id generated by the database. In the delete methods, it’s also using the deleteById which is more secure than delete because you don’t need to fetch the object first, you can directly delete it by its id.
5. Viết 2 REST Controller để xử lý các CRUD request
Tôi : Write 2 REST Controllers: BookController and BorrowerController that handle CRUD requests. These REST Controllers will use BookService and BorrowerService
ChatGPT:
Here’s an example of two REST controllers, BookController
and BorrowerController
, that handle CRUD requests and use the BookService
and BorrowerService
respectively:
@RestController
@RequestMapping("/books")
public class BookController {
private final BookService bookService;
@Autowired
public BookController(BookService bookService) {
this.bookService = bookService;
}
@GetMapping
public List<Book> getAllBooks() {
return bookService.getAllBooks();
}
@PostMapping
public Book saveBook(@RequestBody Book book) {
return bookService.saveBook(book);
}
@DeleteMapping("/{id}")
public void deleteBook(@PathVariable String id) {
bookService.deleteBook(id);
}
@GetMapping("/{id}")
public Optional<Book> findBookById(@PathVariable String id) {
return bookService.findBookById(id);
}
}
BookController.java
@RestController
@RequestMapping("/borrowers")
public class BorrowerController {
private final BorrowerService borrowerService;
@Autowired
public BorrowerController(BorrowerService borrowerService) {
this.borrowerService = borrowerService;
}
@GetMapping
public List<Borrower> getAllBorrowers() {
return borrowerService.getAllBorrowers();
}
@PostMapping
public Borrower saveBorrower(@RequestBody Borrower borrower) {
return borrowerService.saveBorrower(borrower);
}
@DeleteMapping("/{id}")
public void deleteBorrower(@PathVariable String id) {
borrowerService.deleteBorrower(id);
}
@GetMapping("/{id}")
public Optional<Borrower> findBorrowerById(@PathVariable String id) {
return borrowerService.findBorrowerById(id);
}
}
BorrowerController.java
Both of these controllers are annotated with @RestController
to make them Spring REST controllers, and they both have a constructor that accepts the corresponding service as a parameter and @Autowired to automatically initialize the service.
Both controllers have basic CRUD operations like GET, POST, DELETE and GET/{id} which can be used to perform CRUD operations on the Book and Borrower models via REST endpoints.
In the saveBook
and saveBorrower
methods, it uses @RequestBody
to get the request payload, and in the deleteBook and deleteBorrower methods, it uses @PathVariable
to get the id from the url.
In the GET methods, it’s returning the list of Books or Borrowers, in the POST methods, it’s returning the saved object that has the id generated by the database, and in the GET/{id} methods, it’s returning the object of the corresponding id.
6. Viết lệnh curl
để kiểm tra 2 REST controller phía trên
Tôi : Write curl command to test two REST Controllers
ChatGPT
Here are some examples of curl commands you can use to test the BookController
and BorrowerController
:
To get a list of all books:
curl -X GET http://localhost:8080/books
To save a book
curl -X POST -H "Content-Type: application/json" -d '{"id":"123456789012","title":"The Lord of the Rings","authors":["J.R.R. Tolkien"],"publishedYear":1954}' http://localhost:8080/books
To delete a book by id:
curl -X DELETE http://localhost:8080/books/123456789012
To find a book by id:
curl -X GET http://localhost:8080/books/123456789012
To get a list of all borrowers:
curl -X GET http://localhost:8080/borrowers
To save a borrower:
curl -X POST -H "Content-Type: application/json" -d '{"id":"098765432109","fullName":"John Doe","email":"johndoe@example.com","mobilePhone":"555-555-5555"}' http://localhost:8080/borrowers
To delete a borrower by id:
curl -X DELETE http://localhost:8080/borrowers/098765432109
To find a borrower by id:
curl -X GET http://localhost:8080/borrowers/098765432109
Note that in these examples, http://localhost:8080
is the base URL of your Spring Boot app, and you may need to adjust it to match your app’s configuration. Also, these examples assume that you’re running your app on your local machine, so you may need to replace localhost
with the IP address or hostname of the machine where your app is running.
Please also note that when you are running these commands, you should have your Spring Boot app running, otherwise you will get an error message indicating that it cannot connect to the specified address.
Also, when you are running the curl command in your local machine, the headers and payload in the command should match with the headers and payload of the request that the endpoint is expecting.
Bổ xung phương thức mượn sách
Tôi : Write me a method in BorrowerService that one borrower borrows several books
ChatGPT :
Here is an example of a method in the BorrowerService class that allows a borrower to borrow several books:
@Transactional
public Borrower borrowBooks(String borrowerId, List<String> bookIds) {
Borrower borrower = borrowerRepository.findById(borrowerId)
.orElseThrow(() -> new EntityNotFoundException("Borrower not found"));
List<Book> books = bookRepository.findAllById(bookIds);
if (books.size() != bookIds.size()) {
throw new EntitiesNotFoundException("Some books not found");
}
books.forEach(book -> book.getBorrowers().add(borrower));
borrower.getBooks().addAll(books);
return borrowerRepository.save(borrower);
}
java
This method takes two arguments, the ID of the borrower and a list of book IDs. It first finds the borrower by ID using the BorrowerRepository, and throws an exception if the borrower is not found. Then it finds all the books by their IDs using the BookRepository, and throws an exception if some of the books are not found.
It then iterates through the books and add the borrower to the list of borrower of each book and then it add all the books to the borrower. Finally, it saves the borrower using the BorrowerRepository.
Please note that this method uses JPA to manage the many-to-many relationship between the Book and Borrower entities, if you are using other ORM like Hibernate, the implementation might be different.
Yêu cầu vẽ Mermaid diagram
Tôi : Can you generate mermaid diagrams describes Book, Borrower, BookRepository, BorrowerRepository, BookService, BorrowerService, BookController and BorrowerController?
ChatGPT:
You can copy and paste this code into the editor of website like https://mermaid-js.github.io/mermaid-live-editor/ or use any other tool that supports Mermaid diagrams.
Please note that, this is just a basic diagram representation, you can always add or remove methods and fields as per your requirement and implement the relations that you want to show.
Tôi : give me an example of sequence diagram using Mermaid
Yêu cầu đóng gói dự án vào Docker
Tôi : Write a Docker file that compile above SpringBoot project and build into a Alpine Linux with JDK 17
ChatGPT
Here’s an example of a Dockerfile that can be used to compile and build your Spring Boot project using Alpine Linux with JDK 17:
# Use the official Alpine Linux image as the base image
FROM alpine:3.13
# Update the package manager and install JDK 17
RUN apk update && apk add openjdk17
# Set JAVA_HOME environment variable
ENV JAVA_HOME /usr/lib/jvm/java-17-openjdk
ENV PATH ${PATH}:${JAVA_HOME}/bin
# Create a directory for the application
RUN mkdir /app
# Set the working directory to the application directory
WORKDIR /app
# Copy the application jar and dependencies to the container
COPY target/*.jar app.jar
# Expose the application's port
EXPOSE 8080
# Run the application
ENTRYPOINT ["java", "-jar", "app.jar"]
Dockerfile
This Dockerfile can be used to build an image of your application using the following command:
docker build -t my-spring-app .
The -t
flag specifies the name and optionally a tag to the name of the image in the name:tag
format.
This command will build an image of your application using the Dockerfile in the current directory. Once the image is built, you can start a container using the following command:
docker run -p 8080:8080 my-spring-app
The -p
flag maps the host port to the container port, in this case 8080.
This command will start a container from the image and run the application inside it. The application will be accessible on port 8080 of the host machine.
Please note that you need to have your jar package ready and in the same folder where you are running the build command. Also, you might need to adjust some of the settings based on your project
Đánh giá những gì ChatGPT vừa viết
Là một lập trình viên và giảng viên dạy Java, Spring Boot nhiều năm kinh nghiệm, tôi đánh giá những đoạn code mà ChatGPT sinh ra có độ chính xác rất cao. Lập trình viên chỉ cần copy và paste vào dự án SpringBoot là có thể chạy được ngay. Code những đoạn code rất dài, ChatGPT viết nửa chừng rồi tạm ngừng. Bạn hãy gõ “Please continue”, chatGPT sẽ viết nốt.
ChatGPT có khả năng nhớ hoàn cảnh, ngữ cảnh trao đổi trước đó với con người. Ví dụ tôi yêu cầu
id
của modelBook
phải chứa đúng 12 ký tự. Trong những đoạn code ChatGPT sinh ra đều tuân thủ yêu cầu này. Book id dài đúng 12 ký tự trong lệnh curl123456789012
curl -X GET http://localhost:8080/books/123456789012
ChatGPT tỷ mỷ đến mức khi yêu cầu kết nối H2, nó sẽ gợi ý các dependencies cần bổ xung vào
pom.xml
và cấu hình cho fileapplication.properties
để chạy được H2.,Khi dùng code ChatGPT sinh ra, nếu có lỗi, hãy copy thông báo lỗi rồi yêu cầu ChatGPT gỡ lỗi. Trong nhiều trường hợp nó sẽ đưa ra đề xuất sửa lỗi,
ChatGPT có ảnh hưởng gì đến công việc của lập trình viên?
Đây là một câu hỏi mà rất nhiều lập trình băn khoăn lo lắng khi ChatGPT code quá nhanh, chính xác và hầu như không có lỗi. Tôi xin trả lời theo hiểu biết và dự đoán của cá nhân tôi:
- Khi có ChatGPT thì chi phí lập trình trên mỗi lập trình viên cần phải giảm xuống khoảng 60-70% cho cùng 1 đầu việc (task). Hoặc lập trình viên đó cần phải tăng năng suất lên gấp 2-3 lần. Cụ thể code phải có Unit Test, thời gian hoàn thành phải ngắn lại.
- Một lập trình viên cần phải dùng ChatGPT để chuyển đổi nhanh giữa các ngôn ngữ lập trình khác nhau, web frameworks, mobile frameworks khác nhau thích nghi nhanh với mọi yêu cầu công việc.
- Thay vì phải mất 5-8 năm mới trở thành một senior developer hay solution architect giờ với sự hỗ trợ của ChatGPT bạn chỉ nên mất khoảng 1-3 năm nếu kết hợp với các dịch vụ , pattern mà điện toán đám mây.
- Việc học lập trình căn bản, nắm vững kiến thức nền tảng khoa học máy tính, mạng, hệ điều hành và tự code thành thạo ít nhất một framework vẫn cần thiết. Việc ra lệnh cho ChatGPT chỉ có thể thành công khi bạn hiểu bạn cần gì và có khả năng kiểm tra những gì ChatGPT sinh ra.
- Lập trình viên đầu tư học tiếng Anh để viết câu chữ thật rõ ràng, chính xác ngữ nghĩa, cú pháp để ChatGPT phục vụ bạn chính xác tận tình hơn.
- Thời của cách học cũ đã hết, bạn phải học và làm việc với AI, dùng nó chứ không chối bỏ nó.
Bình luận