Hiểu Rõ Nguyên Lý Làm Việc Của MyBatis
MyBatis là một framework hỗ trợ mạnh mẽ cho việc thao tác với cơ sở dữ liệu trong các ứng dụng Java. Thay vì viết JDBC thuần, MyBatis giúp giảm thiểu lượng code lặp lại và tăng tính dễ bảo trì bằng cách ánh xạ câu lệnh SQL với các phương thức trong interface.
Cốt Lõi Của MyBatis
- Mapper Interface: Tất cả các phương thức thao tác CSDL đều được khai báo ở dạng abstract method trong interface (gọi là Mapper).
- SQL Mapping: Mỗi phương thức sẽ tương ứng với một câu lệnh SQL được định nghĩa trong file XML hoặc qua annotation.
- Tự động xử lý chi tiết: MyBatis tự quản lý kết nối, transaction, đóng tài nguyên và đặc biệt là ánh xạ kết quả truy vấn sang đối tượng Java.
Khai Báo Phương Thức Trong Mapper
Khi thiết kế phương thức trong Mapper, cần tuân thủ một số quy tắc:
- Trả về:
- Thao tác INSERT/UPDATE/DELETE: Dùng
Integerđể nhận số dòng bị ảnh hưởng (hoặcvoidnếu không cần). - Thao tác SELECT: Dùng kiểu dữ liệu mong muốn. Nếu lấy nhiều bản ghi, dùng
List<T>, còn lại dùng trực tiếp class POJO.
- Thao tác INSERT/UPDATE/DELETE: Dùng
- Tên phương thức: Tự đặt, nhưng không được trùng tên (không cho phép overloading).
- Tham số: Thiết kế theo nhu cầu. Nếu có từ 2 tham số trở lên, bắt buộc phải dùng
@Paramđể đặt tên rõ ràng.
Ví dụ Khai Báo Phương Thức
public interface UserMapper {
// Đếm số người dùng
Integer countUsers();
// Tìm người dùng theo ID
User findById(Integer id);
// Cập nhật email theo ID
Integer updateEmailById(@Param("id") Integer id, @Param("email") String email);
}
Cấu Hình File XML Mapping
Mỗi Mapper interface cần có một file XML tương ứng để định nghĩa SQL. Cấu trúc cơ bản:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.UserMapper">
<select id="countUsers" resultType="java.lang.Integer">
SELECT COUNT(*) FROM t_user
</select>
<select id="findById" resultType="com.example.User">
SELECT * FROM t_user WHERE id = #{id}
</select>
</mapper>
namespace: Phải là tên đầy đủ (fully qualified name) của interface Mapper.id: Tên phương thức tương ứng.resultType: Kiểu trả về. VớiList, chỉ cần khai báo kiểu phần tử.
Xử Lý Nhiều Tham Số – Dùng @Param
Khi có nhiều hơn một tham số, Java không lưu tên biến sau khi biên dịch (chỉ còn arg0, arg1...), nên MyBatis không thể ánh xạ đúng. Giải pháp: dùng @Param.
Integer updateUser(
@Param("id") Integer id,
@Param("name") String name,
@Param("email") String email
);
Trong file XML:
<update id="updateUser">
UPDATE t_user
SET username = #{name}, email = #{email}
WHERE id = #{id}
</update>
Sử Dụng Dynamic SQL
1. <foreach> – Xử Lý Danh Sách
Dùng để tạo câu lệnh IN() với danh sách giá trị động.
<delete id="deleteByIds">
DELETE FROM t_user
WHERE id IN
<foreach collection="list" item="uid" open="(" separator="," close=")">
#{uid}
</foreach>
</delete>
collection: Với tham số đơn, dùnglist(nếu là List),array(nếu là mảng).item: Tên tạm dùng trong vòng lặp.separator: Ký tự phân cách giữa các phần tử.open/close: Mở và đóng ngoặc.
2. <if> Và <choose> – Điều Kiện
<select id="findUsersByCondition" resultType="User">
SELECT * FROM t_user WHERE 1=1
<if test="name != null and name != ''">
AND username LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age >= #{age}
</if>
</select>
Nếu cần if-else, dùng <choose>:
<choose>
<when test="type == 'admin'">
AND role = 'ADMIN'
</when>
<otherwise>
AND status = 'ACTIVE'
</otherwise>
</choose>
#{} vs ${} – Khác Biệt Về Bảo Mật
- #{}: PreparedStatement. An toàn, chống SQL Injection. Chỉ dùng cho giá trị.
- ${}: String interpolation. Dễ bị tấn công SQL Injection. Chỉ dùng khi cần chèn đoạn SQL (ví dụ: tên bảng, cột, ORDER BY).
Khuyến cáo: Luôn ưu tiên dùng #{}, chỉ dùng ${} khi thực sự cần.
Xử Lý Sai Lệch Tên Cột Và Thuộc Tính
Khi tên cột DB khác tên thuộc tính Java (ví dụ: department_id ↔ departmentId), có hai cách giải quyết:
Cách 1: Dùng Biệt Danh (Alias)
<select id="findAll" resultType="User">
SELECT id, department_id AS departmentId, username
FROM t_user
</select>
Cách 2: Dùng <resultMap>
<resultMap id="UserResultMap" type="User">
<id property="id" column="id"/>
<result property="departmentId" column="department_id"/>
<result property="username" column="username"/>
</resultMap>
<select id="findAll" resultMap="UserResultMap">
SELECT * FROM t_user
</select>
- Dùng
<id>cho khóa chính để tối ưu cache. - Chỉ cần khai báo những trường sai lệch.
Truy Vấn Liên Kết Bảng
1. Một Chiều – Dùng VO Class
Khi truy vấn nhiều bảng, cần tạo lớp VO (Value Object) để chứa kết quả.
public class UserVO {
private Integer id;
private String username;
private String departmentName;
// getter/setter...
}
Mapper:
UserVO findUserWithDept(@Param("id") Integer id);
SQL:
<select id="findUserWithDept" resultType="UserVO">
SELECT u.id, u.username, d.name AS departmentName
FROM t_user u
LEFT JOIN t_department d ON u.department_id = d.id
WHERE u.id = #{id}
</select>
2. Một-Nhiều – Dùng <collection>
Ví dụ: Lấy thông tin phòng ban kèm danh sách nhân viên.
public class DepartmentVO {
private Integer id;
private String name;
private List<User> users;
// getter/setter...
}
Dùng <resultMap> với <collection>:
<resultMap id="DeptWithUsersMap" type="DepartmentVO">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
<collection property="users" ofType="User">
<id property="id" column="user_id"/>
<result property="username" column="user_name"/>
</collection>
</resultMap>
<select id="findDeptWithUsers" resultMap="DeptWithUsersMap">
SELECT
d.id AS dept_id, d.name AS dept_name,
u.id AS user_id, u.username AS user_name
FROM t_department d
LEFT JOIN t_user u ON d.id = u.department_id
WHERE d.id = #{id}
</select>
Lỗi Thường Gặp Và Cách Xử Lý
- Invalid bound statement (not found): Kiểm tra:
- Đường dẫn file XML có đúng trong cấu hình
mapperLocations? namespacetrong XML có khớp với interface?idcủa node có trùng tên phương thức?
- Đường dẫn file XML có đúng trong cấu hình
- Không ánh xạ được dữ liệu: Kiểm tra tên cột, alias, hoặc dùng
resultMap.