Cách tích hợp MyBatis vào Spring Boot để truy cập cơ sở dữ liệu - coi đá gà thomo c1

| Feb 1, 2025 min read

17 tháng 3 năm 2024 - Công nghệ thông tin

MyBatis là một khung công tác (framework) bền vững dành cho ngôn ngữ Java. MyBatis hỗ trợ định nghĩa các truy vấn SQL thông qua ghi chú hoặc cấu hình XML, cũng như ánh xạ kết quả truy vấn với các đối tượng Java. So với JPA - một khung công tác phổ biến khác trong Java (xem thêm cách sử dụng Spring Data JPA để truy cập cơ sở dữ liệu), MyBatis nổi bật nhờ khả năng kiểm soát linh hoạt hơn đối với các câu lệnh SQL.

Bài viết này sẽ minh họa cách tích hợp MyBatis vào một dự án Spring Boot sử dụng Maven quản lý, đồng thời làm việc với cơ sở dữ liệu MySQL phiên bản 8.1.0 đã được thiết lập sẵn trên máy cục bộ.

Dưới đây là các phiên bản của JDK, Maven, Spring Boot và MyBatis Starter được sử dụng trong ví dụ này:

JDK: Amazon Corretto 17.0.8
Maven: 3.9.2
Spring Boot: 3.2.3
Mybatis Spring Boot Starter: 3.0.3

Chúng ta sẽ thực hiện các thao tác tăng, sửa, xóa và tra cứu (CRUD) cho bảng User để khám phá cách sử dụng MyBatis.

1 Chuẩn bị dữ liệu thử nghiệm

Trước tiên, hãy thực thi đoạn mã SQL sau trên cơ sở dữ liệu MySQL cục bộ để tạo một cơ sở dữ liệu thử nghiệm, tạo bảng user và chèn 3 dòng dữ liệu thử nghiệm:

CREATE DATABASE test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
DROP TABLE IF EXISTS user;
CREATE TABLE user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(100) NOT NULL,
    name VARCHAR(100) NOT NULL,
    role ENUM('ADMIN', 'EDITOR', 'VIEWER') DEFAULT 'VIEWER',
    description VARCHAR(300) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT '2024-01-01 00:00:00',
    updated_at TIMESTAMP NOT NULL DEFAULT '2024-01-01 00:00:00',
    deleted BOOLEAN DEFAULT FALSE
);
INSERT INTO user (email, name, role, description, created_at, updated_at, deleted)
VALUES 
    ('larry@larry.com', 'Larry', 'ADMIN', 'Tôi là Larry', '2024-01-01 08:00:00', '2024-01-01 08:00:00', false),
    ('jacky@jacky.com', 'Jacky', 'EDITOR', 'Tôi là Jacky', '2024-02-01 08:00:00', '2024-02-01 08:00:00', false),
    ('lucy@lucy.com', 'Lucy', 'VIEWER', 'Tôi là Lucy', '2024-03-01 08:00:00', '2024-03-01 08:00:00', false);

2 Bắt đầu sử dụng MyBatis

Sau khi đã chuẩn bị xong dữ liệu thử nghiệm, chúng ta có thể bắt đầu tìm hiểu cách sử dụng MyBatis.

2.1 Phụ thuộc POM

Dưới đây là thông tin cấu hình pom.xml của dự án mẫu:

<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.3</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-boot-mybatis-integration-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
        <!-- Đơn vị kiểm thử -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.10.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Có thể thấy rằng dự án này dựa trên Java 17, sử dụng Spring Boot phiên bản 3.2.3, và các phụ thuộc bao gồm spring-boot-starter-web, mybatis-spring-boot-starter, mysql-connector-jlombok.

2.2 Cấu hình application.yaml

Dưới đây là thông tin cấu hình trong tệp application.yaml:

# src/main/resources/application.xml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: root
  mybatis:
    mapper-locations: classpath:mapper/*Mapper.xml
logging:
  level:
    com.example.demo.dao.UserDaoMapper: DEBUG

Có thể thấy rằng chúng tôi sử dụng cách cấu hình chuẩn của Spring Boot để chỉ định thông tin kết nối cơ sở dữ liệu. Đối với phần cấu hình MyBatis, chúng tôi sử dụng mybatis.mapper-locations để chỉ định đường dẫn đến các tệp Mapper của MyBatis. Ngoài ra, chúng tôi cũng đặt mức độ nhật ký của interface Mapper sắp được viết thành DEBUG để in ra các câu lệnh SQL tương ứng trên console.

2.3 Lớp POJO Java

Dưới đây là nội dung của lớp Model User:

// src/main/java/com/example/demo/model/User.java
package com.example.demo.model;

import lombok.Data;
import java.util.Date;

@Data
public class User {
    private Long id;
    private String email;
    private String name;
    private Role role;
    private String description;
    private Date createdAt;
    private Date updatedAt;
    private Boolean deleted;

    public enum Role {
        ADMIN, EDITOR, VIEWER
    }
}

Có thể thấy rằng chúng tôi đã tạo một lớp Model User.java tương ứng với bảng user trong cơ sở dữ liệu, với ý nghĩa và kiểu dữ liệu của từng trường phù hợp với câu lệnh tạo bảng trước đó.

2.4 Interface Mapper

Tiếp theo, chúng ta định nghĩa một interface UserDaoMapper.java chứa các phương thức CRUD liên quan đến bảng User:

// src/main/java/com/example/demo/dao/UserDaoMapper.java
package com.example.demo.dao;

import com.example.demo.model.User;
import java.util.List;

public interface UserDaoMapper {
    List<User> list(int offset, int rows);
    long count(); [winvn com9](/post/botanical-garden/) 
    User getById(Long id);
    boolean existsByEmail(String email);
    List<User> searchByName(String name);
    void save(User user);
    void batchSave(List<User> users);
    void update(User user);
    void deleteById(Long id);
}

Có thể thấy rằng chúng tôi đã định nghĩa nhiều phương thức thông dụng cho bảng User, bao gồm truy vấn danh sách phân trang (list), đếm tổng số lượng (count), truy vấn đơn lẻ (getById), kiểm tra tồn tại theo email (existsByEmail), tìm kiếm theo tên (searchByName), lưu đơn lẻ (save), lưu hàng loạt (batchSave), cập nhật (update) và xóa (deleteById).

2.5 Tệp cấu hình Mapper XML

Phần quan trọng nhất của bài viết này là tệp cấu hình Mapper của MyBatis. Tệp này được sử dụng để định nghĩa các câu lệnh SQL cụ thể cho các phương thức được định nghĩa trong phần trước của interface UserDaoMapper, cũng như kiểu hoặc đối tượng nhận kết quả truy vấn. Nội dung của UserDaoMapper.xml như sau:

<!-- src/main/resources/mapper/UserDaoMapper.xml -->
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.dao.UserDaoMapper">
    <!-- Các cột cần chèn -->
    <sql id="insert_columns">
        email,
        name,
        role,
        description,
        created_at,
        updated_at,
        deleted
    </sql>

    <!-- Các cột cần chọn -->
    <sql id="select_columns">
        id,
        email,
        name,
        role,
        description,
        created_at as createdAt,
        updated_at as updatedAt,
        deleted
    </sql>

    <!-- Truy vấn danh sách -->
    <select id="list" resultType="com.example.demo.model.User">
        SELECT
        <include refid="select_columns"/>
        FROM user
        WHERE deleted = false
        ORDER BY created_at DESC
        LIMIT #{offset}, #{rows}
    </select>

    <!-- Đếm tổng số -->
    <select id="count" resultType="long">
        SELECT COUNT(*)
        FROM user
        WHERE deleted = false
    </select>

    <!-- Truy vấn theo ID -->
    <select id="getById" resultType="com.example.demo.model.User">
        SELECT
        <include refid="select_columns"/>
        FROM user
        WHERE deleted = false
        AND id = #{id}
    </select>

    <!-- Kiểm tra tồn tại theo email -->
    <select id="existsByEmail" resultType="boolean">
        SELECT EXISTS (
        SELECT 1
        FROM user
        WHERE deleted = false
        AND email = #{email}
        )
    </select>

    <!-- Tìm kiếm theo tên -->
    <select id="searchByName" resultType="com.example.demo.model.User">
        SELECT
        <include refid="select_columns"/>
        FROM user
        WHERE deleted = false
        <if test="name != null and name != ''">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
    </select>

    <!-- Lưu -->
    <insert id="save" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (
        <include refid="insert_columns"/>
        ) VALUES (
        #{email},
        #{name},
        #{role},
        #{description},
        now(),
        now(),
        false
        )
    </insert>

    <!-- Lưu hàng loạt -->
    <insert id="batchSave" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (
        <include refid="insert_columns"/>
        ) VALUES
        <foreach item="item" collection="list" separator=",">
            (#{item.email}, #{item.name}, #{item.role}, #{item.description}, now(), now(), false)
        </foreach>
    </insert>

    <!-- Cập nhật -->
    <update id="update">
        UPDATE user
        SET
        email = #{email},
        name = #{name},
        role = #{role},
        description = #{description},
        updated_at = now()
        WHERE id = #{id}
    </update>

    <!-- Xóa -->
    <delete id="deleteById">
        DELETE FROM user
        WHERE id = #{id}
    </delete>
</mapper>

Có thể thấy rằng trong tệp cấu hình này, chúng tôi sử dụng namespace để chỉ định rằng đây là implementation của interface UserDaoMapper.java. Các phương thức CRUD được định nghĩa bằng các thẻ <insert>, <delete>, <update><select>. Chúng tôi cũng sử dụng <sql> để trích xuất các phần chung, cùng với các thẻ <foreach><if> để xử lý logic phức tạp hơn. Ngoài ra, chúng tôi còn cấu hình tự động đặt giá trị ID tăng dần sau khi chèn dữ liệu bằng cách sử dụng useGeneratedKeys="true"keyProperty="id".

2.6 Kiểm thử đơn vị

Tại thời điểm này, cấu hình kết nối cơ sở dữ liệu, lớp Model, interface truy vấn và cấu hình Mapper đều đã hoàn tất. Dưới đây là một lớp kiểm thử đơn vị để kiểm tra chức năng của interface UserDaoMapper:

// src/test/java/com/example/demo/dao/UserDaoMapperTest.java
package com.example.demo.dao;

import com.example.demo.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
public class UserDaoMapperTest {

    @Autowired
    private UserDaoMapper userDaoMapper;

    @Test
    public void testList() {
        List<User> users = userDaoMapper.list(2, 10);
        assertFalse(users.isEmpty());
    }

    @Test
    public void testCount() {
        long count = userDaoMapper.count();
        assertTrue(count > 0);
    }

    @Test
    public void testGetById() { [tải tool hack tài xỉu 789 club miễn phí](/post/golang-text-template/) 
        User user = userDaoMapper.getById(1L);
        assertNotNull(user);
    }

    @Test
    public void testExistsByEmail() {
        boolean exists = userDaoMapper.existsByEmail("larry@larry.com");
        assertTrue(exists);
    }

    @Test
    public void testSearchByName() {
        List<User> users = userDaoMapper.searchByName("La");
        assertFalse(users.isEmpty());
    }

    @Test
    public void testSave() {
        User user = new User();
        user.setEmail("david@david.com");
        user.setName("David");
        user.setRole(User.Role.VIEWER);
        user.setDescription("Tôi là David");
        userDaoMapper.save(user);
        assertNotNull(user.getId());
    }

    @Test
    public void testBatchSave() {
        User user1 = new User();
        user1.setEmail("ross@ross.com");
        user1.setName("Ross");
        user1.setRole(User.Role.EDITOR);
        user1.setDescription("Tôi là Ross");

        User user2 = new User();
        user2.setEmail("linda@linda.com");
        user2.setName("Linda");
        user2.setRole(User.Role.VIEWER);
        user2.setDescription("Tôi là Linda");

        List<User> users = List.of(user1, user2);
        userDaoMapper.batchSave(users);

        users.forEach(user -> assertNotNull(user.getId()));
    }

    @Test
    public void testUpdate() {
        User user = userDaoMapper.getById(1L);
        user.setRole(User.Role.EDITOR);
        user.setDescription("Xin chào, tôi là Larry!");
        userDaoMapper.update(user);
    }

    @Test
    public void testDeleteById() {
        userDaoMapper.deleteById(1L);
    }
}

Có thể thấy rằng tất cả các phương thức của interface UserDaoMapper đều hoạt động đúng như mong đợi.

Như vậy, chúng ta đã hoàn thành việc tích hợp MyBatis vào Spring Boot và nắm rõ cách sử dụng các tính năng cơ bản của MyBatis. Dự án mẫu đầy đủ đã được tải lên GitHub cá nhân, mời bạn quan tâm hoặc Fork.

[1] Tài liệu tham khảo MyBatis 3 - [2] MyBatis-Spring-Boot-Starter là gì? -

#Spring #Java