0

Spring Boot các annotation thông dụng thường dùng

image.png

Trong Spring Boot, Annotation (mấy cái bắt đầu bằng @) chính là "phép thuật". Bạn chỉ cần dán cái nhãn lên, Spring tự động làm mọi việc hậu cần phía sau.

Bài viết này sẽ Tổng hợp các Annotation thông dụng thường dùng nhất, giúp chúng ta hiểu chúng làm gì và đặt ở đâu trong dự án của mình

I.Nhóm khởi tạo và cấu hình (The core)

Đây là những annotation giúp Spring Boot hiểu và khởi chạy ứng dụng

1. @SpringBootApplication

Đây là "trùm cuối", luôn nằm ở class main. Thực chất , nó là tổ hợp của 3 annotation con:

  • @Configuration : Khai báo đây là file cấu hình
  • @EnableAutoConfiguration: Kích hoạt cơ chế tự động cấu hình của Spring Boot (thấy dependency nào thì tự config cái đó)
  • @ComponentScan: Ra lệnh cho Spring đi "quét" toàn bộ các file trong package hiện tại và các package con để tìm Bean

2. @Bean (đi kèm với @Configuration)

Bean = object được Spring quản lý.
  • Chức năng: Dùng đẻ tạo ra các Bean một cách thủ công
  • Khi nào dùng? Khi ta muốn dùng một thư viện bên thứ 3 và muốn Spring quản lý nó. Vì ta không thể mở source code thư viện đó để gắn @Component được, nên phải dùng @Bean
@Configuration
public class AppConfig {
    @Bean
    public ModelMapper modelMapper() {
        return new ModelMapper(); // Tự khởi tạo và giao cho Spring quản lý
    }
}

II. Nhóm định danh Bean

Để Spring quản lý các class của ta (Di), ta cần đánh dấu chúng.

Thực chất @Service, @Repository, @Controller đều là "con" của @Component. Chúng kế thừa bản chất của @Component nhưng khoác lên những "nhiệm vụ" và chúng có "quyền hạn" riêng biệt

1. @Component - "Người đa năng"

  • Vai trò: đây là annotation gốc*. Nó đánh dấu một class là một Spring Bean để đưa vào IoC Container quản lý
  • Khi nào dùng: Dùng cho các class tiện ích, các class cấu hình chung chung không thuộc về tầng Business, Data hay Web
  • vd: một class gửi email (EmailSender), một class mã hoá mật khẩu (PasswordEncoder)
import org.springframework.stereotype.Component;

@Component
// Lý do dùng: Đây là class tiện ích chung, không chứa logic nghiệp vụ đặc thù,
// cũng không gọi database, chỉ đơn thuần là một công cụ hỗ trợ.
public class EmailValidator {

    public boolean isValid(String email) {
        return email.contains("@") && email.contains(".");
    }
}

2. @Repository - "Thủ kho"

Đây là tầng tiếp xúc trực tiếp với Database

  • Tại sao không dùng @Component ở đây ????
    • Đây là điểm khác biệt kỹ thuật : @Repository có cơ chế Exception Translation (dịch lỗi)
    • Vấn đề: mỗi đoạn Database (MySQL, PostgreSQL, Oracle) ném ra lỗi SQL khác nhau (SQL Exception). Điều này làm code xử lý ở tầng trên rất khổ sở
    • Giải pháp: @Repository tự động bắt lỗi khó hiểu đó và chuyển đổi chúng thành các lỗi chuẩn của Spring. Nhờ đó, bạn chỉ cần bắt 1 loại lỗi duy nhất
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;

@Repository 
// Lý do dùng: Để Spring tự động bắt lỗi Database (SQL Exception) 
// và chuyển thành DataAccessException của Java.
public interface UserRepository extends JpaRepository<User, Long> {

    // Tìm user theo email
    User findByEmail(String email);
}

=> Tóm lại : dùng @Repository giúp code của bạn phụ thuộc vào loại Database đang dùng

3. @Service-"Quản lý nghiệp vụ"

  • Vai trò: đánh dấu tầng xử lý logic nghiệp vụ
  • Ở thời điểm hiện tại, về mặt kỹ thuật @Service hoạt động y hệt @Component . và nó chưa có tính năng đặc biệt nhu là @Repository 🙃
  • Thế tại sao vẫn dùng @Sevice thay vì cho thẳng luôn @Component???? 🤔
    1. Tính rõ nghĩa : AE nhìn vào biết ngay đây là nơi chưa logic quan trọng
    2. Tương lai: Spring để ngỏ khả năng thêm các tính năng đặc biệt cho @Service trong các phiên bản sau
import org.springframework.stereotype.Service;

@Service
// Lý do dùng: Đánh dấu đây là nơi xử lý logic. 
// Sau này có thể dùng AOP để đo tốc độ xử lý hoặc quản lý Transaction tại đây.
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User registerNewUser(User user) {
        // Logic nghiệp vụ: Kiểm tra email trùng
        if (userRepository.findByEmail(user.getEmail()) != null) {
            throw new RuntimeException("Email đã tồn tại!");
        }
        return userRepository.save(user);
    }
}

4. @Controller vs @RestController- "Lễ tân"

Đây là tầng giao tiếp với bên ngoài. Có sự phân việt rõ ràng giữa 2 đồng chí này

  • @Controller (truyền thống)
    • Dùng trong mô hình MVC cũ (trả về giao diện HTML/JSP)
    • Nếu muốn trả về dữ liệu JSON, ta phải kẹp thêm @ResponseBody và từng hàm
  • @RestController (Hiện tại- chuẩn cho APi)
import org.springframework.web.bind.annotation.*;

@RestController
// Lý do dùng: Kết hợp @Controller + @ResponseBody. 
// Giúp trả về dữ liệu JSON ngay lập tức mà không cần cấu hình thêm.
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/register")
    public User register(@RequestBody User user) {
        // Gọi xuống tầng Service để xử lý
        return userService.registerNewUser(user);
    }
}

Bảng tóm tắt cơ bản nhanh

Annotation Chức năng đặc biệt Khi nào dùng ?
@Component Không có. Là cha của các loại trên Các class tiện ích, config chung
@Repository Tự động dịch lỗi DB sang Spring DataAccessException Class truy cập Database
@Service Chưa có Class xử lý logic nghiệp vụ
@RestController Tự động đóng gói kết quả trả về thành JSON/XML Viết RESTful API

III . Nhóm tiêm phụ thuộc (Dependency Injection)

DI là nguyên lý cốt lỗi của Spring. Nó đảm bảo các đối tượng (Bean) không cần tự đi tìm đối tướng khác mà chúng cần (Dependency), thay vào đó, Sping (IoC Container) sẽ tự động cung cấp chúng

1. @Autowired - "keo dán "

  • Chức năng: yêu cầu Spring tự động tìm kiếm một Bean có kiểu dữ liệu phù hợp trong Container và tiêm(inject) vào vị trí khai báo.
  • Cơ chế tìm kiếm:
    • Theo kiểu dữ liệu : Spring tìm Bean có kiểu dữ liệu phù hợp
    • Theo tên: nếu có nhiều Bean cùng kiểu, Spring sẽ tìm Bean có tên trùng với tên biến

Các phương pháp @Autowired

Mặc dù @Autowired có thể đặt ở 3 vị trí.

Phương pháp Vị trí đặt @Autowired Ưu điểm&khuyến nghị
Constructor Trên hàm khởi tạo (public className(....)) Phương pháp chuẩn nhất
Setter Trên hàm Setter (public void set ...() ) Dùng cho các Dependency tuỳ chọn
Field Trên biến/thuộc tính Không khuyến nghị
@Service
public class UserService {
    // Nên dùng final để đảm bảo userRepository không bao giờ bị thay đổi
    private final UserRepository userRepository;

    // Spring tự động tiêm Bean UserRepository vào Constructor
    // (Không cần @Autowired)
    public UserService(UserRepository userRepository) { 
        this.userRepository = userRepository;
    }

    // ... code sử dụng userRepository
}

2. @Qualifier("tenbean")

  • Chức năng: khi có nhiều hơn một Bean cùng kiểu dữ liệu, Spring sẽ không biết chọn Bean nào.@Qualifier giúp bạn chỉ định chính xác tên của bean ta muốn tiêm vào
  • Khi nào dung: khi sử dụng interface có nhiều implement (đa hình)

Định nghĩa Beans

// Dịch vụ gửi qua Email
@Component("emailService") // Tên Bean là "emailService"
public class EmailNotification implements NotificationService { ... }

// Dịch vụ gửi qua SMS
@Component("smsService") // Tên Bean là "smsService"
public class SmsNotification implements NotificationService { ... }

Sử dụng @Qualifier

@Service
public class AlertService {

    private final NotificationService notificationService;

    // Chỉ định: Tôi muốn dùng Bean tên là "emailService"
    public AlertService(@Qualifier("emailService") NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    public void sendAlert(String msg) {
        notificationService.send(msg); // Lệnh này sẽ gửi qua Email
    }
}

3. @RequiedArgsConstructor (Lombok)

  • Vị trí : Đặt ngay trên đầu Class (Controller, Service)
  • Tác dụng: Tự động tạo hàm khởi tạo cho tất cả các biến được khai báo là final
  • Tại sao nên dùng? Giúp code ngắn gọn hơn, không cần viết Constructor thủ công và không cần rải @Autowired khắp nơi

ví dụ so sánh

  • Cách cũ (dùng Field Ịnection)
@Service
public class UserService {
    @Autowired // Cách này không được khuyên dùng nữa
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;
}
  • Cách mới (Dùng lombok )
@Service
@RequiredArgsConstructor // <--- Đặt ở đây
public class UserService {

    // Bắt buộc phải có chữ 'final' để Lombok nhận diện
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder; 

    // Không cần viết Constructor, Lombok tự làm ngầm bên dưới!
}

IV . Nhóm xử lý API (Controlller layer)

Trong kiến trúc Spring Boot, Controller đóng vai trò như một nhân viên lễ tân: Nhận yêu cầu (Request) -> Gọi bộ phận xử lý (Service) -> Trả kết quả (Response)

1. Định nghĩa Method Các Annotation này giúp xác định xem API này sẽ làm gì (lấy dữ liệu, thêm mới hay Xoá.....)

  • @RequestMapping: Annotation gốc. Thường đặt ở đầu class để quy định đường dẫn chung cho tất cả các API bên trong

  • @GetMapping: Dùng để lấy dữ liệu (tương ứng với lệnh SELECT trong DB)

  • @PostMapping: Dùng để thêm mới dữ liệu (tương ứng với lệnh INSERT)

  • @PutMapping: Dùng để cập nhật toàn bộ thông tin đối tượng (tương ứng với UPDATE)

  • @DeleteMapping: Dùng để xoá dữ liệu (tương ứng với DELETE)

2. Hứng dữ liệu từ Client Làm sao để lấy dúng dữ liệu người dùng gửi lên? Ta cần phân biệt rõ 3 loại sau

Annotation vị trí dữ liệu Khi nào dùng???
@PathVariable Nằm ngay trên đường dẫn (vd: /users/101) Dùng khi tham số là định danh duy nhất của tài nguyên (ID, Username)
@RequestParam Nằm sau dấu hỏi chấm ? (/user?role=admin) Dùng cho các tiêu chí phụ: Lọc , sắp xếp, phân trang
@RequestBody Nằm ẩn trong Body ({"name":"Hoàn","age":21}) Dùng khi cần gửi đối tượng lớn, phức tạp.Thường dùng cho Tạo mới (POST) hoặc cập nhật(PUT)

ví dụ

@RestController
@RequestMapping("/api/products") // Đường dẫn chung cho cả class
public class ProductController {

    // TRƯỜNG HỢP 1: Lấy chi tiết 1 sản phẩm
    // -> Dùng @PathVariable vì ID là định danh duy nhất.
    @GetMapping("/{id}")
    public Product getById(@PathVariable Long id) {
        return productService.findById(id);
    }

    // TRƯỜNG HỢP 2: Tìm kiếm/Lọc sản phẩm
    // -> Dùng @RequestParam vì đây là điều kiện lọc (có cũng được, không có cũng được).
    // URL ví dụ: /api/products/search?keyword=laptop
    @GetMapping("/search")
    public List<Product> search(@RequestParam String keyword) {
        return productService.searchByName(keyword);
    }

    // TRƯỜNG HỢP 3: Tạo mới sản phẩm
    // -> Dùng @RequestBody vì dữ liệu sản phẩm có nhiều trường (tên, giá, mô tả...), gửi dạng JSON.
    @PostMapping
    public Product create(@RequestBody ProductDto productDto) {
        return productService.create(productDto);
    }
}

V .Nhóm ánh xạ Database (Entity Layer)

Trong Spring Boot, chúng ta không tạo bảng bằng lệnh SQL CREATE TABLE thủ công nữa. Thay vào đó, chúng ta tạo ra các Class Java và đánh dấu chúng. Spring sẽ nhìn vào đó và tự động sinh ra bảng trong Database. Kỹ thuật này gọi là ORM (Object Relational Mapping)

Bộ 5 annotation "bất ly thân" khi định nghãi một bảng (Entity)

  • @Entity
    • Bắt buộc : có
    • Ý nghĩa: đánh dấu class này là một "thực thể". Spring sẽ hiểu đây là một bảng trong Database
  • @Table(name="tenbang")
    • Bắt buộc: nên dùng
    • Ý nghĩa:Nếu không dùng, Spring sẽ lấy tên Class làm tên bảng (Vd: Class UserInfo -> bảng user_info ). Tuy nhiên , best practice là luôn đặt tên bẳng rõ ràng, số nhiều (vd: users, products)
  • @Id
    • Bắt buộc: có
    • Ý nghĩa: Xác định thuôc tính là khoá chính của bảng
  • @GeneratedValue
    • Ý nghĩa: Quy định cách sinh kháo chính
    • Thông dung: GenerationType.IDENTITY (tương ứng với AUTO_INCREMENT trong MySQL/SQL Server - nghĩa là ID tự tăng 1,2,3,4,...)
  • @Column
    • Ý nghĩa: Tuỳ chỉnh cấu hình cho cột (cột dài, tên cột, ràng buộc,.....)

Có một thuộc tính, nhưng không muốn lưu nó vào database (kiểu biến tạm thời ấy như nhiểu fullname=firtsname+lastname) ->@Transient : đánh dấu thuộc tính này sẽ bị JPA bỏ qua, không tạo cột trong Database

ví dụ

import jakarta.persistence.*; // Spring Boot 3 dùng jakarta, bản cũ dùng javax
import java.util.Date;

@Entity // 1. Đánh dấu đây là Entity
@Table(name = "users") // 2. Tên bảng trong DB sẽ là "users"
public class User {

    @Id // 3. Khóa chính
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 4. ID tự động tăng
    private Long id;

    // 5. Cấu hình chi tiết: Không được null, Tên cột là "user_name", độ dài 50
    @Column(name = "user_name", nullable = false, length = 50)
    private String username;

    // Email phải là duy nhất (unique)
    @Column(unique = true)
    private String email;

    // Mật khẩu không giới hạn độ dài hiển thị code
    @Column(nullable = false)
    private String password;

    // Trường này dùng để tính tuổi hiển thị, KHÔNG lưu vào DB
    @Transient
    private int ageCalculation; 

    // Constructor, Getters, Setters...
}

VI. Các Annotation hỗ trợ "thần thánh gia truyền"

Đây là những vũ khí bí mật giúp bạn xử lý các vấn đề hóc búa như: Giao dịch ngân hàng, đọc cấu hình và xử lý lỗi tập trung 🧐

1. @Transactional - "Được ăn cả, ngã về không" Đây là annotation quan trọng nhất để đảm bảo tính toàn vẹn dữ liệu

  • Vấn đề : Giả sử ta viết hàm chuyenTien(A,B)
    • Trừ tiền tài khoản A
    • ơ ơ tự nhiên server mất điện bị off bất ngờ hay code lỗi gì đó
    • Chưa kịp cộng tiền cho B -> Hậu quả: A mất tiền, B không nhận tiền. Hòi mất tiền rồi còn gì 🤐
  • Giải pháp: Đặt @Transactional lên method
    • Nếu tất cả lệnh ngon lành -> Commit lưu vào DB
    • Nếu có bất kỳ lỗi gì đó -> Rollback khôi phục trạng thái ban đàu, coi như chưa thực hiện hành động nào
@Service
public class BankService {

    @Transactional // Bảo vệ giao dịch
    public void transferMoney(Long fromId, Long toId, Double amount) {
        accountRepository.withdraw(fromId, amount); // Trừ tiền

        // Giả sử lỗi xảy ra ở đây
        if (amount > 1000000) throw new RuntimeException("Lỗi hệ thống!");

        accountRepository.deposit(toId, amount); // Cộng tiền
    }
}

2. @Value - "Đọc cấu hình từ file properties"

Thay vì fix cứng các giá trị như đường dẫn file upload, email server, hay khoá bí mật trong code Java, chúng ta để chúng trong file application.properties và dùng @Value để lấy ra

File application.properties

app.upload-dir=/var/www/uploads
app.max-file-size=10MB

Code Java

@Service
public class FileService {

    // Lấy giá trị từ file config gán vào biến
    @Value("${app.upload-dir}")
    private String uploadDir;

    public void upload() {
        System.out.println("Đang lưu file vào: " + uploadDir);
    }
}

3. @ControllerAdvice & @ExceptionHandler - "xử lý lỗi tập trung"

Thay vì mỗi Controller lại viết try-catch để bắt lỗi, bạn dùng bộ đôi này để bắt lỗi cho TOÀN BỘ hệ thống tại một nơi duy nhất

  • @ControllerAdvice: Khai báo một class chuyên đi "hóng hớt, nghe ngóng" lỗi của toàn bộ ứng dụng
  • @ExceptionHandler: Quy định "Nếu gặp lỗi X thì xử lý như thế nào"

ví dụ

@RestControllerAdvice // Phiên bản REST của @ControllerAdvice
public class GlobalExceptionHandler {

    // Bất cứ đâu ném ra UserNotFoundException, hàm này sẽ chạy
    @ExceptionHandler(UserNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND) // Trả về 404
    public ErrorResponse handleUserNotFound(UserNotFoundException ex) {
        return new ErrorResponse("LỖI_001", ex.getMessage());
    }

    // Bắt tất cả các lỗi còn lại (Exception chung)
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // Trả về 500
    public ErrorResponse handleAllExceptions(Exception ex) {
        return new ErrorResponse("LỖI_HE_THONG", "Đã có lỗi xảy ra, vui lòng thử lại!");
    }
}

VII. Nhóm tiện ích Lombok

Mục đích: Xoá bớt dòng code Getter/Setter/Constructor , giúp class gọn gàng , dễ đọc

Lombok là một thư viện bên thứ 3: cần cái Plugin trong IDE và thêm dependency thì mới hoạt động được. Nó can thiệp vào quá trình biên dịch để tự sinh code

1. @Getter & @Setter

  • Tác dụng: Tự động sinh hàm get....() và set...() cho các thuộc tính
  • Phạm vi: có thể đặt trên đầu Class (áp dụng cho tất cả các biến) hoặc đặt riêng trên từng biến

2. Nhóm Constructor(@NoArgs, @AllArgs, @RequiredArgs)

3. @Data

4. @Builder

  • Tác dụng : Cung cấp Builder Pattern cho class. Thay vì dùng new User(a,b,c,...) dễ nhầm thứ tự, ta có thể code tường minh hơn

ví dụ

// Cách viết thường
User user = new User("Hoan", "admin@test.com", "123456"); // Dễ nhầm thứ tự tham số

// Cách viết Builder (Ngầu hơn, rõ ràng hơn)
User user = User.builder()
    .username("Hoan")
    .email("admin@test.com")
    .password("123456")
    .build();

KẾT LUẬN

Spring Boot Annotation giống như bảng cửu chương vậy.Lúc đầu nhìn thì thấy nhiễu và rối, nhưng khi đã có code quen tay, bạn sẽ thấy chúng là những trợ thủ đắc lực giúp code ngắn gọn và dễ hiểu hơn.

Hy vọng bài tổng hợp này giúp các đồng chí và chính mình có cái nhìn hệ thống hơn về lũ annotation này.Lưu cái Bookmark này để tra cứu khi nào quyên nhớ 🫠

HAPPY CODING🚀


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí