MyBatis - CRUD 操作

MyBatis 支持通过 XML 和注解两种方式来配置映射。从早期的 iBatis 开始,XML 的功能就已经十分齐全,而注解的方式则是后来才出现的。至于使用 XML 方式还是注解方式,完全取决于个人偏好。

1.环境配置

创建一个 Spring Boot 项目,这里以 Spring Boot 2.7.5 + JDK1.8 为例。

1.1 导入相关依赖

首先,我们需要在 pom.xml 文件中添加 MyBatis 的 Starter 、数据源(以 Druid 为例)和对应的 MySQL 驱动依赖:

<!-- MyBatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

<!-- 数据库驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>

<!-- 数据库连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.6</version>
</dependency>

1.2 基本配置

下面以 application.yml 为例进行一些基本的配置:

# 配置数据库
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/<your-database>?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: <password>

# 配置mybatis
mybatis:
  # 配置别名
  type-aliases-package: cn.javgo.learningmybatis.model
  configuration:
    # 开启驼峰命名
    map-underscore-to-camel-case: true

注意:需要将上述 <your-database> 替换为自己需要连接的数据库,并将 <password> 替换为正确的数据库连接密码。

1.3 数据模型

准备测试需要使用的数据表,我们以 Student 相关操作为例,对应的 student 数据表如下:

在 MyBatis 中,我们对实体类没有什么要求,也无须添加特定的注解,各种映射都是通过 Mapper 接口来定义的。对应的 cn.javgo.learningmybatis.model.Student 实体类如下:

@Data
public class Student {
    
    
    private Long id;
    private String name;
    private Integer age;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

2.基于 XML 开发

2.1 创建 Mapper 接口

由于使用了 MyBatis 为了更加体现其特点,笔者将原本持久层的 dao 包名对应修改为了 mapper,并在其中创建了一个 Mapper 接口 StudentMapper。在该接口中我们就可以定义一些基本的 CRUD 操作了,示例代码如下:

@Repository
public interface StudentMapper {
    
    
    
    /**
     * 插入学生
     * @param student 学生
     * @return 影响行数
     */
    int insert(Student student);

    /**
     * 根据id查询学生
     * @param id 学生id
     * @return 学生
     */
    Student selectById(Long id);

    /**
     * 查询所有学生
     * @param student 学生
     * @return 学生列表
     */
    List<Student> selectAll();

    /**
     * 根据id更新学生
     * @param student 学生
     * @return 影响行数
     */
    int updateById(Student student);

    /**
     * 根据id删除学生
     * @param id 学生id
     * @return 影响行数
     */
    int deleteById(Long id);
}

然后添加 Java 配置,通过 @MapperScan 配置好 Mapper 接口路径,让 Spring Boot 能自动扫描到:

@MapperScan("cn.javgo.learningmybatis.mapper")
@SpringBootConfiguration
public class MyBatisConfig {
    
    
    
}

2.2 创建 XML 映射文件

Mapper 接口创建好之后,我们需要准备一个同名的 XML 文件并在其中编写具体的 XML 语句。对于映射文件的位置老项目一般就与 Mapper 接口放在同一个包下,但是更推荐将所有映射文件放在 resources 资源目录下,例如我们就将所有的映射文件放在了 resources/mapper 目录下:

为了能够找到这些映射文件,我们需要在配置文件中添加相对应的配置:

# 配置mybatis
mybatis:
  # 配置别名
  type-aliases-package: cn.javgo.learningmybatis.model
  # 配置mapper的扫描路径(使用通配符)
  mapper-locations: classpath:mapper/*.xml
  configuration:
    # 开启驼峰命名
    map-underscore-to-camel-case: true

一个基本的 XML 映射文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.javgo.learningmybatis.mapper.StudentMapper">
    <!-- 编写各种 XML 语句 -->
</mapper>

上面的 namespace 的值就是对应的 Mapper 接口的全路径类名。

2.3 insert

<insert id="insert" parameterType="Student">
    insert into student (name, age,create_time,update_time) values (#{name}, #{age},now(),now())
</insert>

上述新增语句中的 id 对应的就是 StudentMapper 接口中的方法名,parameterType 对应的是 StudentMapper 接口中的方法参数类型。原则上应该写全类名 cn.javgo.learningmybatis.model.Student,但是由于我们在配置文件中配置了 mybatis.type-aliases-package=cn.javgo.learningmybatis.model 配置了包路径的别名,MyBatis 就会按照该路径进行查找了。

当在 MyBatis 中方法参数多于一个时,可以使用 @Param 注解来指定每个参数的名称,以便与 SQL 语句中的占位符进行匹配。下面是使用 @Param 注解的示例:

int insert(@Param("name") String name,@Param("age") Integer age);

一般来说,在插入语句执行后,我们都需要返回生成的自增 ID。在 XML 映射文件中,可以使用 useGeneratedKeys 属性和 keyProperty 属性来配置自增 ID 的获取。这种方式适用于支持 JDBC 3.0 以上版本的数据库。

<insert id="insert" parameterType="Student" useGeneratedKeys="true" keyProperty="id">
    insert into student (name, age,create_time,update_time) values (#{name}, #{age},now(),now())
</insert>

在执行完插入语句后,就可以通过 user.getId() 方法获取自增 ID。

除了上面的方式外,我们还可以使用 selectKey 元素来获取自增 ID。这种方式适用于不支持 JDBC 3.0 以上版本的数据库或者需要在插入语句执行前获取自增 ID 的情况。

<insert id="insert" parameterType="Student" useGeneratedKeys="true" keyProperty="id">
    insert into student (name, age,create_time,update_time) values (#{name}, #{age},now(),now())
    <selectKey keyProperty="id" resultType="long" order="AFTER">
        select last_insert_id()
    </selectKey>
</insert>

TIP:

在 MyBatis 中,可以在映射文件中省略 parameterType 的情况有以下几种:

  1. 使用注解方式:如果你使用 MyBatis 的注解方式进行 SQL 的编写,映射文件是可选的,因此不需要指定 parameterType
  2. 使用参数注解:当方法只有一个参数,并且该参数在 SQL 语句中被使用时,可以省略 parameterType。MyBatis 会自动将参数对象传递给 SQL 语句。

基于上述知识点的学习,对于插入方法我们的 SQL 可以按如下方式写:

<insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert into student (name, age,create_time,update_time) values (#{name}, #{age},now(),now())
</insert>

2.4 select

<!-- 使用resultType定义返回的结果类型 -->
<select id="selectById" resultType="Student">
    select id,name,age,create_time,update_time from student where id = #{id}
</select>

<!-- 使用resultMap定义映射关系 -->
<select id="selectAll" resultMap="studentMap">
    select id,name,age,create_time,update_time from student
</select>

<!-- 定义返回的结果集映射
    id:结果集映射的唯一标识
    type:结果集映射的类型
    property:结果集映射的属性
    column:结果集映射的列
-->
<resultMap id="studentMap" type="Student">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <result property="createTime" column="create_time"/>
    <result property="updateTime" column="update_time"/>
</resultMap>

上述 SQL 中,我们使用 resultType 指定方法的返回值类型,同时通过 resultMap 元素定义了一个返回结果集将查询到的字段进行一一映射为一个 Student 对象。

TIP:

  1. 如果你不写 resultMap,MyBatis 仍然会自动将查询到的数据封装为 Student 对象,但是会按照默认规则进行映射。默认规则是将查询结果的列名与 Student 对象的属性名进行匹配。
  2. 通过编写 resultMap,你可以定义自己的映射规则,可以进行更细粒度的控制。例如,你可以指定不同的列名与属性名之间的映射关系,可以处理一对多关系,可以处理复杂类型等等。此外,你还可以在 resultMap 中定义一些特殊的映射处理逻辑,如类型转换、联合查询等。

由于上述示例我们使用了默认的映射规则,故可以进行简化如下:

<select id="selectById" resultType="Student">
    select id,name,age,create_time,update_time from student where id = #{
    
    id}
</select>

<select id="selectAll" resultType="Student">
    select id,name,age,create_time,update_time from student
</select>

2.5 delete

删除操作使用的知识点与插入操作一致,无非就是换了以下标签名,此处不再赘述。

<delete id="deleteById">
    delete from student where id = #{id}
</delete>

2.6 update

更新操作使用的知识点与插入操作一致,无非就是换了以下标签名,此处不再赘述。

<update id="updateById">
    update student set name = #{name}, age = #{age}, update_time = now() where id = #{id}
</update>

2.7 编写单元测试

下面是一个完整的单元测试案例,用于测试上述每个方法:

@SpringBootTest
public class StudentMapperTest {
    
    
    @Autowired
    private StudentMapper studentMapper;

    @Test
    void testInsert() {
    
    
        // 创建Student对象
        Student student = new Student();
        student.setName("张三");
        student.setAge(20);

        // 插入数据
        int result = studentMapper.insert(student);

        // 判断是否插入成功
        assertEquals(1, result);
        assertNotNull(student.getId());
    }

    @Test
    void testSelectById() {
    
    
        // 根据id查询
        Student student = studentMapper.selectById(1L);

        // 判断是否查询到数据
        assertNotNull(student);
        assertEquals("张三", student.getName());
        assertEquals(20, student.getAge());
    }

    @Test
    void testSelectAll() {
    
    
        // 查询所有学生
        List<Student> studentList = studentMapper.selectAll();

        // 判断是否查询到数据
        assertNotNull(studentList);
        assertEquals(1, studentList.size());
    }

    @Test
    void testUpdateById() {
    
    
        // 根据id查询
        Student student = studentMapper.selectById(1L);

        // 修改学生信息
        student.setName("李四");
        student.setAge(30);

        // 更新数据
        int result = studentMapper.updateById(student);

        // 判断是否更新成功
        assertEquals(1, result);

        // 再次查询
        student = studentMapper.selectById(1L);

        // 判断是否更新成功
        assertNotNull(student);
        assertEquals("李四", student.getName());
        assertEquals(30, student.getAge());
    }

    @Test
    void testDeleteById(){
    
    
        // 根据id查询
        Student student = studentMapper.selectById(1L);

        // 判断是否查询到数据
        assertNotNull(student);

        // 删除数据
        int result = studentMapper.deleteById(1L);

        // 判断是否删除成功
        assertEquals(1, result);
    }
}

TIP:

Spring Framework 为测试提供了强大的支持,在涉及数据库操作时,为了保证每个测试的运行不会对其他测试产生影响,它可以直接回滚测试中的操作,这也是默认的逻辑。因此,即使不添加 @Rollback 注解,也能达到同样的效果。如果希望测试代码的变动被提交到数据库中,可以使用 @Commit@Rollback(false)。当然,这里的前提是必须存在事务,因此可以在测试方法尚上添加 @Transactional 注解开启此效果。

3.基于注解开发

3.1 常用注解

以下是 Spring Boot 集成 MyBatis 常用的一些注解:

注解 说明
@Mapper 标识该接口是一个 MyBatis Mapper 接口,Spring Boot 将自动扫描到这个接口并将其实例化为一个 Bean 以供使用。
@Select 提供 SQL 查询语句,用于执行 SELECT 操作。
@Update 提供 SQL 更新语句,用于执行 UPDATE 操作。
@Insert 提供 SQL 插入语句,用于执行 INSERT 操作。
@Delete 提供 SQL 删除语句,用于执行 DELETE 操作。
@Results 结合 @Result 使用,提供复杂的结果映射。
@Result 结合 @Results 使用,为 @Select 提供结果映射。
@Param 在方法参数前使用,为参数命名,以使 SQL 语句中引用参数更清晰。
@Options 提供对调用数据库函数时的更多选项,如自动生成键、执行前刷新缓存等。
@ResultMap 引用一个已经定义的 @Results 注解,避免重复定义。
@MapperScan 在 Spring Boot 应用启动类上使用,用于指定 MyBatis Mapper 接口的包路径,让 Spring Boot 能自动扫描到。
@One 用于配置一对一的关联关系
@Many 用于配置一对多的关联关系

3.2 创建 Mapper 接口

使用基于注解的方式我们便不再需要编写 XML 映射文件了,而是直接将 SQL 通过一系列注解直接卸载 Mapper 接口对应的方法上即可。既然不需要 XLM 映射文件,对应的我们也就没必要配置 mybatis.mapper-locations=classpath:mapper/*.xml 了。同时将前面的 @Repository 注解对应替换为 @Mapper,标识该接口是一个 MyBatis Mapper 接口,Spring Boot 将自动扫描到这个接口并将其实例化为一个 Bean 以供使用。

下面是一个等效的 StudentMapper

@Mapper
public interface StudentMapper {
    
    
    /**
     * 插入学生并返回主键
     * @param student 学生
     * @return 影响行数
     */
    @Insert("insert into student(name, age,create_time,update_time) values(#{name}, #{age},now(),now())")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(Student student);

    /**
     * 根据id查询学生
     * @param id 学生id
     * @return 学生
     */
    @Select("select * from student where id = #{id}")
    @ResultMap("studentMap")
    Student selectById(Long id);

    /**
     * 查询所有学生
     * @return 学生列表
     */
    @Select("select * from student")
    @Results(id = "studentMap", value ={
    
    
            @Result(property = "id", column = "id"),
            @Result(property = "name", column = "name"),
            @Result(property = "age", column = "age"),
            @Result(property = "createTime", column = "create_time"),
            @Result(property = "updateTime", column = "update_time")
    })
    List<Student> selectAll();

    /**
     * 根据id更新学生
     * @param student 学生
     * @return 影响行数
     */
    @Update("update student set name = #{name}, age = #{age}, update_time = now() where id = #{id}")
    int updateById(Student student);

    /**
     * 根据id删除学生
     * @param id 学生id
     * @return 影响行数
     */
    @Delete("delete from student where id = #{id}")
    int deleteById(Long id);
}

测试类可以使用同一套,但是需要进行一些修改,因为自增 ID 肯定不是之前的了,胖友自行进行单元测试。

猜你喜欢

转载自blog.csdn.net/ly1347889755/article/details/130995138