Spring Boot bài 13: Kết nối MySQL, CRUD và transaction trong Service
- 18-10-2026
- Toanngo92
- 0 Comments
Giáo trình Spring Boot MVC và REST API · Chương 6/12: JPA, MySQL và transaction · Bài 13/28 · Biên soạn tháng 10/2026.
Mục lục
Mục tiêu bài học
Chuyển từ H2 sang MySQL, cấu hình tài khoản DB riêng và hiểu thời điểm commit hoặc rollback.
Chuẩn bị một database dành riêng cho ví dụ
Cài MySQL 8.4 LTS hoặc dùng máy chủ tương thích đã được bạn quản lý. Tạo database hvc_academy với utf8mb4 và tài khoản ứng dụng riêng; không chạy ứng dụng bằng tài khoản root. Tài khoản chạy migration của bài cần quyền tạo bảng trong đúng database mẫu. Hệ thống thực tế có thể tách tài khoản migration và tài khoản runtime với quyền hẹp hơn.
Đặt HVC_DB_URL theo dạng jdbc:mysql://localhost:3306/hvc_academy, HVC_DB_USER và HVC_DB_PASSWORD trong môi trường. Bật SPRING_PROFILES_ACTIVE=mysql, giữ HVC_DEMO_PASSWORD như lúc chạy H2. Nếu MySQL ở máy khác, cấu hình TLS và xác minh chứng chỉ phù hợp; không thêm tùy chọn tắt bảo mật chỉ để loại bỏ cảnh báo kết nối.
Tệp trong mã nguồn mẫu: src/main/resources/application-mysql.properties. Đoạn dưới được trích từ tệp này; xem bản tải về để có đủ import và phần còn lại.
spring.datasource.url=${HVC_DB_URL}
spring.datasource.username=${HVC_DB_USER}
spring.datasource.password=${HVC_DB_PASSWORD}
spring.jpa.hibernate.ddl-auto=validate
Biên transaction nằm ở nghiệp vụ
CourseService mặc định @Transactional(readOnly=true) cho thao tác đọc. Những phương thức create, update và delete ghi đè bằng @Transactional để cho phép ghi. Một lần gọi nghiệp vụ có thể cần nhiều repository; transaction giúp các bước dữ liệu hoàn tất cùng nhau hoặc rollback khi thất bại theo quy tắc của Spring.
Ví dụ update tìm Course, kiểm tra Category rồi đổi thuộc tính. Entity đang được quản lý trong transaction nên Hibernate phát hiện thay đổi và ghi khi flush/commit. Không cần gọi save lại chỉ để thông báo mỗi setter. Điều này khác với việc tạo một đối tượng mới hoặc sửa entity đã tách khỏi persistence context.
Tệp trong mã nguồn mẫu: src/main/java/com/hocvietcode/academy/CourseService.java. Đoạn dưới được trích từ tệp này; xem bản tải về để có đủ import và phần còn lại.
@Transactional
public CourseDto update(Long id,CourseForm form){
Course c=courses.findById(id).orElseThrow(CourseNotFound::new);
c.update(form.getTitle(),form.getPrice(),category(form)); return CourseDto.from(c);
}
Rollback và lỗi không nên bị nuốt
Theo mặc định, runtime exception và Error làm transaction rollback; checked exception cần chính sách rollback phù hợp nếu nghiệp vụ yêu cầu. Nếu bắt lỗi bên trong Service và im lặng trả thành công, interceptor có thể không còn thấy lỗi để rollback như bạn mong muốn. Thiết kế response thành công phải gắn với kết quả giao dịch thật.
@Transactional thường hoạt động qua proxy khi bean được gọi từ bên ngoài. Một phương thức tự gọi phương thức khác trên cùng instance không tự tạo một lần chặn proxy mới. Đừng dựa vào annotation rải rác để suy ra transaction đã hoạt động; hãy xác định điểm gọi vào bean và kiểm thử hành vi dữ liệu.
Kiểm tra lưu bền và chẩn đoán kết nối
Tạo một khóa học qua giao diện, dừng ứng dụng, chạy lại với cùng database và xác nhận bản ghi còn nguyên. So sánh với H2 giúp phân biệt lưu bền của database với vòng đời của tiến trình Java. Chỉ dùng database thử riêng khi chạy migration hoặc test có thao tác xóa.
Access denied thường liên quan tài khoản, mật khẩu hoặc host được phép; communications link failure thường liên quan máy chủ, cổng hoặc mạng. Schema validation báo kiểu cột không khớp là vấn đề schema khác với hai lỗi trên. Đọc nguyên nhân cụ thể trước khi thay driver hoặc thêm hàng loạt tham số JDBC không rõ tác dụng.
Lỗi thường gặp và lưu ý
- readOnly=true là gợi ý tối ưu và ý định giao dịch, không phải cơ chế phân quyền người dùng.
- Không đưa mật khẩu MySQL vào URL, bài viết công khai hoặc repository Git.
Ghi nhớ
Profile chọn database; Service xác định transaction; MySQL giữ dữ liệu sau khi ứng dụng dừng.

