文章目录
- 1.环境配置
- 1.1 导入相关依赖
- 1.2 基本配置
- 1.3 数据模型
- 2.基于 XML 开发
- 2.1 创建 Mapper 接口
- 2.2 创建 XML 映射文件
- 2.3 insert
- 2.4 select
- 2.5 delete
- 2.6 update
- 2.7 编写单元测试
- 3.基于注解开发
- 3.1 常用注解
- 3.2 创建 Mapper 接口
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
的情况有以下几种:
- 使用注解方式:如果你使用 MyBatis 的注解方式进行 SQL 的编写,映射文件是可选的,因此不需要指定
parameterType
。- 使用参数注解:当方法只有一个参数,并且该参数在 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:
- 如果你不写
resultMap
,MyBatis 仍然会自动将查询到的数据封装为Student
对象,但是会按照默认规则进行映射。默认规则是将查询结果的列名与Student
对象的属性名进行匹配。- 通过编写
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 肯定不是之前的了,胖友自行进行单元测试。