Mục lục
Ở thời điểm hiện tại Spring Boot có lẽ đang là framework nổi tiếng và được sử dụng nhiều bật nhất trong phát triển các ứng dụng sử dụng ngôn ngữ Java. Được xây dựng trên nền tảng Spring framework nó có tất cả các tính năng của Spring cộng thêm những tiện ích mà nó mang lại như giảm thiểu các bước cấu hình phức tạp, nhúng server container (Tomcat, Jetty hoặc Undertow) tự động vào ứng dụng giúp chúng ta có thể khởi chạy một ứng dụng ngay lập tức, quản lý dependence thông minh v.v.
Tất cả điều này đã tạo nên một sức lôi cuốn vô cùng lớn đối với cộng đồng developer trên khắp thế giới, các công ty cũng đang dần chuyển sang dùng Spring Boot cho các dự án tiếp theo. Lựa chọn học Spring Boot ở thời điểm hiện tại là một quyết định đúng đắn vì thị trường việc làm của nó đang rất nhiều. Thông tin thêm là hiện tại công ty mình cũng đang dần chuyển từ Spring qua Spring Boot cho các dự án cũ và các dự án mới sẽ dùng hẳn Spring Boot, nên các bạn cứ an tâm học Spring Boot nhé.
Khởi tạo project spring boot
Để khởi tạo một project Spring Boot nhanh chóng các bạn có thể truy cập vào trang Spring Initializr và lựa chọn các thông số cấu hình cho dự án, cũng như các dependency cần thiết. Hoặc có thể khởi tạo project maven với các dependency như sau.
<dependencies> <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>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> <scope>provided</scope> </dependency> </dependencies>
Lưu ý là nếu dùng Spring Initializr thì các bạn có thể thêm các dependency này bằng chọn Add dependencies ở tab Dependencies nhé
Cấu trúc project Spring Boot
Trong một project Spring Boot thường sẽ có các layer chính:
- Domain: chứa các ánh xạ database – entity
- Repository: định nghĩa các DAO (Data access object) class dùng để làm việc với database
- Service: chứa các business logic class
- Controller: nhận request từ client
- Mapper: dùng để convert qua lại giữa entity và dto (không có cũng được)
- Exception Hanlder: xử lý các exception xảy ra trong quá trình thực thi yêu cầu của client
Dự án sau khi chúng ta hoàn thành sẽ có cấu trúc như thế này. Bây giờ chúng ta sẽ bắt đầu đi từng phần một. Các bạn đừng lo lắng vì mình cũng sẽ upload code để các bạn tiện thực hành, chỉ cần theo dõi bài viết là được.
Domain layer
Đối với hầu hết các dự án thì chúng ta thường bắt đầu với domain layer trước, sau khi đã phân tích thiết kế cơ sở dữ liệu xong thì chúng ta sẽ đến bước định nghĩa các entity ánh xạ các table tương ứng dưới database.
Trong bài viết này mình dùng H2 database, các bạn có thể linh hoạt sử dụng các database khác như SQL, MySQL, Mariadb tùy ý. Vì mình sử dung JPA và JPA provider bên dưới là Hibernate nên có thể dễ dàng chuyển đổi qua lại giữa các database mà code không bị ảnh hưởng.
Trong khuôn khổ bài viết này thì mình sẽ chỉ định nghĩa một User entity như sau:
@Entity @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String name; private String email; }
Các entity class phải được chú thích với @Entity annotation, ngoài ra các annotation khác của lombok được mình sử dụng để giảm thiểu việc triển khai các method getter, setter, constructor v.v
Repository Layer
Đây là layer chúng ta định nghĩa các DAO class dùng để thao tác với database. Tuy nhiên trong phần này chúng ta không cần phải triển khai các method cơ bản như là findById(), findAll, save(), delete(), update() vì chúng đã được triển khai thông qua một tầng abstraction được đặt ở trên JPA implementation.
Để sử dụng các tính năng cơ bản này chúng ta chỉ cần khai báo một Repository class thừa kế từ CrudRepository.
@Repository public interface UserRepository extends CrudRepository<User, Long> {}
Service Layer
Như đã đề cập trước đó thì Service layer sẽ là nơi chứa các business chính của dự án, là cầu nối giữa Controller layer và Repository layer.
Với một ứng dụng CRUD thì chúng ta cần ít nhất các method dùng cho việc thêm, sửa, xóa, cập nhật và tìm kiếm. Chúng ta sẽ đi qua từng phần này ngay sau đây.
Trước tiên chúng ta cần định nghĩa một UserService Interface và một UserServiceImpl class dùng để triển khai các method định nghĩa trong UserService.
public interface UserService { User create(User user); User update(Long id, User user); void delete(Long id); User findById(Long id); Iterable<User> findAll(); }
Và
@Service public class UserServiceImpl implements UserService { }
Các bạn có thấy cách làm này có kỳ cục không? Sao không tạo luôn UserService class rồi triển khai code trong đó luôn? Điều này hoàn toàn được, tuy nhiên các bạn cần phải biết đây là một cách làm giúp tăng tính mở rộng code.
Gỉa sử hiện tại mình đang có UserService và UserServiceImpl hoạt động ổn. Về sau này chúng ta cần chỉnh sửa một số tính năng trong UserServiceImpl để tăng performance hoặc adapt cho phù hợp với business mới. Nhưng do UserServiceImpl đã được phát triển qua một thời gian dài nên việc sửa code trong này rất khó khăn, lúc đó chúng ta có thể tạo ra một UserServiceImplNew mới cũng implement UserService và triển khai code tách bạch khỏi UserServiceImpl. Sau đó chúng ta chỉ cần thay đổi việc sử dụng UserServiceImplNew thay vì UserServiceImpl mà không cần chỉnh sửa code ở những chỗ khác.
Những chỗ khác đang sử dụng UserServiceImpl mà không có nhu cầu chỉnh sửa thì vẫn có thể sử dụng, do không có chỉnh sửa gì trên UserServiceImpl nên chúng ta yên tâm là các tính năng sẽ vẫn hoạt động như cũ.
Create
Đến với chức năng đầu tiên, chúng ta cần tạo một hàm createUser dùng để tạo và lưu một record User xuống database.
@Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Override public User create(User user) { if (user.getEmail() == null || user.getEmail().isEmpty()) { return null; } if (user.getName() == null || user.getName().isEmpty()) { return null; } return userRepository.save(user); } }
Các bạn có thể tùy ý thêm các mã kiểm tra khác như email có hợp lệ không? email có tồn tại trước đó hay chưa? v.v Sau đó sử dụng method save() để lưu xuống database.
Note: Khi sử dụng H2 trong project Spring Boot thì mặc định nó đã cấu hình tự động cho bạn sử dụng một database H2-mem, nghĩa là khi tắt ứng dụng thì dữ liệu sẽ bị mất.
Update
Với hàm update() thì client cần truyền cho chúng ta ID của user muốn cập nhật và một User object chứa các thông tin được cập nhật.
@Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; // ...... @Override public User update(Long id, User user) { User fromDB = userRepository.findById(id).orElse(null); if (fromDB == null) { return null; } fromDB.setEmail(user.getEmail()); fromDB.setName(user.getName()); return userRepository.save(fromDB); }
Delete
Tính năng này khá đơn giản, chúng ta sẽ nhận vào một User ID và xóa User trong database có ID tương tứng
@Override public void delete(Long id) { userRepository.deleteById(id); }
Find
Sẽ có 2 method thường được dùng là tìm kiếm theo ID hoặc lấy tất cả.
@Override public User findById(Long id) { return userRepository.findById(id).orElse(null); } @Override public Iterable<User> findAll() { return userRepository.findAll(); }
Controller Layer
Đến layer cuối cùng dùng để nhận các request từ client, chúng ta sẽ cần đến @RestController annotation để đánh dấu một class là một controller, và @RequestMapping, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping để chỉ định các endpoint cụ thể cho các API.
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping public User create(@RequestBody User user) { return userService.create(user); } @PutMapping("/{id}") public User update(@PathVariable Long id, @RequestBody User user) { return userService.update(id, user); } @DeleteMapping("/{id}") public void delete(@PathVariable Long id) { userService.delete(id); } @GetMapping public Iterable<User> findAll() { return userService.findAll(); } @GetMapping("/{id}") public User findByID(@PathVariable Long id) { return userService.findById(id); } }
Trong đó các bạn cần chú ý @RequestBody dùng để parse request body sang User object tự động, và @PathVariable dùng để parse ID từ Path parameter.
Kiểm thử
Sau khi đã hoàn tất các layer trên thì giờ đây chúng ta đã có một ứng dụng Web API đơn giản với các function CRUD User.
Các bạn có thể dùng cURL để tạo một User
curl --location --request POST 'http://localhost:8080/user' \ --header 'Content-Type: application/json' \ --data-raw '{ "name": "deft", "email": "[email protected]" }'
Để cập nhật thì có thể dùng
curl --location --request PUT 'http://localhost:8080/user/1' \ --header 'Content-Type: application/json' \ --data-raw '{ "name": "deft change", "email": "[email protected]" }'
Nếu thực thi theo thứ tự trên thì User được tạo ở bước đầu tiên sẽ được cập nhật NAME thành deft change. Để kiểm tra sự thay đổi các bạn có thể dùng find hoặc findById để kiểm tra.
curl --location --request GET 'http://localhost:8080/user' \ --header 'Content-Type: application/json' \ --data-raw '{ "name": "deft change", "email": "[email protected]" }' Hoặc curl --location --request GET 'http://localhost:8080/user/1' \ --header 'Content-Type: application/json' \ --data-raw '{ "name": "deft change", "email": "[email protected]" }'
Kết quả tương ứng sẽ là
[ { "id": 1, "name": "deft change", "email": "[email protected]" } ] Và { "id": 1, "name": "deft change", "email": "[email protected]" }
Tóm lược
Như vậy là chúng ta đã biết cách xây dựng một ứng dụng spring boot đơn giản với các tính năng CRUD cơ bản nhất mà một ứng dụng cần có. Bên cạnh đó chúng ta cũng biết cách sử dụng các annotation để ánh xạ dữ liệu và tạo ra các endpoint cho ứng dụng.
Link mã nguồn: spring-boot-crud