前言

本文通过一个简单的Demo(功能并不成熟)来快速上手MyBatis,实现增、删、改、查。

更多详细的使用细节可点击如下链接:

版本:

  • Maven:3.8.1
  • JDK:17
  • SpringBoot:3.3.2
  • API测试工具:Postman

项目实例构建

新建一个名为springboot_demo的项目,并勾选添加如下依赖:

  • Developer Tools
    • Lombok
  • Web
    • Spring Web
  • SQL
    • MyBatis Framework
    • MySQL Driver

项目目录如下:

User类:

1
2
3
4
5
6
7
8
9
10
11
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
private Short gender; //1 -> 男, 0 -> 女
private LocalDateTime createTime; //创建时间(驼峰命名)
private LocalDateTime updateTime; //修改时间(驼峰命名)
}

Result类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Integer code;
private String msg;
private Object data;

// 用于快速返回Result对象的三个静态方法
public static Result success(Object data) {
return new Result(1, "success", data);
}

public static Result success() {
return new Result(1, "success", null);
}

public static Result error(String msg) {
return new Result(0, msg, null);
}
}

application.properties配置数据库连接信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring.application.name=springboot_demo

# 驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_demo
# 连接数据库的用户名
spring.datasource.username=root
# 连接数据库的密码
spring.datasource.password=root

# 配置mybatis的日志,指定输出到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 开启mybatis的驼峰命名自动映射开关 a_column --> aCloumn
mybatis.configuration.map-underscore-to-camel-case=true

数据库表中的字段为下划线_命名的方式,而定义的Bean中的属性名采用驼峰命名方式,mybatis.configuration.map-underscore-to-camel-case=true可以在mapper中自动完成两种命名方式的映射。

创建一个名为springboot_demo的数据库,在其中新建一个user表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`age` int(11) NOT NULL,
`gender` tinyint(4) NOT NULL,
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '张三', 19, 1, '2024-08-02 17:41:33', '2024-08-02 18:14:29');
INSERT INTO `user` VALUES (2, '李四', 20, 0, '2024-08-02 14:55:25', '2024-08-02 14:55:27');
INSERT INTO `user` VALUES (3, '王五', 19, 1, '2024-08-02 16:49:59', '2024-08-02 16:50:02');
INSERT INTO `user` VALUES (4, '赵六', 30, 0, '2024-08-02 16:50:20', '2024-08-02 16:50:22');

SET FOREIGN_KEY_CHECKS = 1;

功能实现

说明:

本节内容分别通过注解xml配置文件的方式实现对数据库中user表数据的增、删、改、查。(两种实现方式可以根据实际情况选择使用(通常来说:简单的sql实现使用注解方式实现,复杂的sql实现使用xml配置文件方式实现。)注意:同一种方法不可以同时使用两种实现方式,否则会报错!)

值得说明的是XML映射文件须遵循以下规范:

  • XML映射文件的名称需与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同的包下(同包同名
  • XML映射文件<Mapper>标签的namespace属性需与Mapper接口全限定名一致
  • XML映射文件中sql语句的id需与Mapper接口中的方法名一致,并保持返回类型一致

配置XML映射文件首先需要在一开始配置约束,该约束是固定格式,可参考官网:https://mybatis.net.cn/getting-started.html#:~:text=探究已映射的 SQL 语句

1
2
3
4
5
6
7
8
<?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="_1004.mapper.UserMapper">
<!--添加实现方法-->
</mapper>

需求:根据前端传送的nameagegender字段新增user数据。

Controller层

UserController类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@RestController
public class UserController {

@Autowired
private UserService userService;

@PostMapping("/addUser") //采用POST方法接收(只接收到三个属性)
public Result addUser(@RequestBody User user) {
log.info("新增员工,user:{}", user);
userService.addUser(user);
return Result.success();
}
}

Service层

UserService接口:

1
2
3
public interface UserService {
void addUser(User user);
}

UserServiceImpl实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserMapper userMapper;

@Override
public void addUser(User user) {
user.setCreateTime(LocalDateTime.now()); //在Controller传来的user对象中增加createTime属性
user.setUpdateTime(LocalDateTime.now()); //在Controller传来的user对象中增加updateTime属性
userMapper.addUser(user);
}
}

Mapper层

注解实现

UserMapper类:

1
2
3
4
5
6
7
@Mapper
public interface UserMapper {
@Options(keyProperty = "id", useGeneratedKeys = true)
@Insert("insert into user(name, age, gender, create_time, update_time) " +
"values (#{name}, #{age}, #{gender}, #{createTime}, #{updateTime})")
void addUser(User user);
}

说明:注解@Options可以设置获取添加对象返回的主键

  • useGeneratedKeys = true:表示要拿到主键值
  • keyProperty = "id":表示拿到的主键最终会封装到id属性中

拿到的主键值可以直接在Controller中调用user.getId()方法获取。

XML实现

UserMapper类:

1
2
3
4
5
6
@Mapper
public interface UserMapper {
@Insert("insert into user(name, age, gender, create_time, update_time) " +
"values (#{name}, #{age}, #{gender}, #{createTime}, #{updateTime})") //使用@Insert注解
void addUser(User user);
}

UserMapper.xml

1
2
3
4
5
6
7
8
<!--namespace需要与Mapper接口名称一致-->
<mapper namespace="_1004.mapper.UserMapper">
<!--id需要与接口中的方法名一致-->
<insert id="addUser" keyProperty="id" useGeneratedKeys="true"> <!--使用<insert>标签-->
insert into user(name, age, gender, create_time, update_time)
values (#{name}, #{age}, #{gender}, #{createTime}, #{updateTime})
</insert>
</mapper>

同样可以利用keyPropertyuseGeneratedKeys获取返回的主键值。

本示例中id为自增,因此不需要添加id信息。

假设id非自增,需要我们手动添加bean对象的所有属性,那么可以利用parameterType属性指定要插入的数据类型,舍去要插入的字段:

1
2
3
<insert id="addUser" parameterType="_1004.pojo.User">
insert into user values (#{id}, #{name}, #{age}, #{gender}, #{createTime}, #{updateTime})
</insert>
  • POST请求:

需求:根据前端传送的ids值(可以是多个id)批量删除对应的user数据。

Controller层

UserController类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@RestController
public class UserController {

@Autowired
private UserService userService;

@DeleteMapping("/deleteByIds/{ids}") //采用DELETE方法接收
public Result deleteById(@PathVariable List<Integer> ids) { //根据接收的id值,将其封装为ids集合
log.info("根据id删除用户:{}", ids);
userService.deleteById(ids);
return Result.success();
}
}

Service层

UserService接口:

1
2
3
public interface UserService {
void deleteById(List<Integer> ids);
}

UserServiceImpl实现类:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserMapper userMapper;

@Override
public void deleteById(List<Integer> ids) {
userMapper.deleteById(ids);
}
}

Mapper层

注解实现

UserMapper类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Mapper
public interface UserMapper {
//通过@DeleteProvider 注解使用动态SQL (参数type:指定包含提供方法的类;参数method:指定生成 SQL 的方法名)
@DeleteProvider(type = SqlProvider.class, method = "deleteByIds")
void deleteById(@Param("ids") List<Integer> ids);

// 内部类,用于提供动态 SQL
class SqlProvider implements ProviderMethodResolver {
public static String deleteByIds(@Param("ids") List<Integer> ids) {
// 将 ids 列表转换为逗号分隔的字符串
String idsString = ids.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
// 动态生成 SQL 语句
return new SQL() {{
DELETE_FROM("user");
WHERE("id IN (" + idsString + ")");
}}.toString();
}
}
}

说明:该动态SQL要求传入的列表不能为空,否则会报错!(通常动态SQL推荐使用XML配置文件的方式实现)

XML实现

UserMapper.xml

1
2
3
4
5
6
7
8
9
10
<mapper namespace="_1004.mapper.UserMapper">
<delete id="deleteById"> <!--使用<delete>标签-->
delete
from user
where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
</mapper>

<foreach>标签说明:

  • collection属性:要遍历的集合
  • item属性:遍历的元素
  • separator属性:指定每一次遍历元素的分隔符
  • open属性:遍历开始前拼接的SQL片段
  • close属性:遍历结束后拼接的SQL片段

该动态SQL同样要求传入的列表不能为空,否则会报错!

  • DELETE请求:

需求:根据前端传来的idnameagegender字段(字段可选择)更新修改对应id的uesr数据。

Controller层

UserController类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@RestController
public class UserController {

@Autowired
private UserService userService;

@PutMapping("/updateUser")
public Result updateUser(@RequestBody User user) {
log.info("更新员工信息:{}", user);
userService.updateUser(user);
return Result.success();
}
}

Service层

UserService接口:

1
2
3
public interface UserService {
void updateUser(User user);
}

UserServiceImpl实现类:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserMapper userMapper;

@Override
public void updateUser(User user) {
user.setUpdateTime(LocalDateTime.now()); //在Controller传来的user对象中更新updateTime属性
userMapper.updateUser(user);
}
}

Mapper层

注解实现

UserMapper类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Mapper
public interface UserMapper {
//通过@UpdateProvider 注解使用动态SQL (参数type:指定包含提供方法的类;参数method:指定生成 SQL 的方法名)
@UpdateProvider(type = SqlProvider.class, method = "updateUser")
void updateUser(@Param("user") User user);

class SqlProvider {
public static String updateUser(@Param("user") User user) {
StringBuilder sql = new StringBuilder();
sql.append("UPDATE user SET ");

if (user.getName() != null) {
sql.append("name = #{user.name}, ");
}
if (user.getAge() != null) {
sql.append("age = #{user.age}, ");
}
if (user.getGender() != null) {
sql.append("gender = #{user.gender}, ");
}
if (user.getUpdateTime() != null) {
sql.append("update_time = #{user.updateTime}, ");
}

// 删除最后一个逗号和空格
sql.setLength(sql.length() - 2);

sql.append(" WHERE id = #{user.id}");

return sql.toString();
}
}
}

该方法不建议使用注解的方式

XML实现

UserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<mapper namespace="_1004.mapper.UserMapper">
<update id="updateUser"> <!--使用<update>标签-->
update user
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="gender != null">
gender = #{gender},
</if>
<if test="updateTime != null">
update_time = #{updateTime}
</if>
</set>
where id = #{id}
</update>
</mapper>

<if>标签说明:根据test属性进行条件判断,如果为true,则拼接包裹的SQL语句

<set>标签说明:以上述代码为例,如果update_time不更新,那么SQL语句:gender = #{gender},就会多一个.,致使SQL语句出现错误,为此可以利用<set>标签进行包裹,可以自动地将正确的<if>项进行添加。

<where>标签作用同理)

  • PUT请求:

需求:根据前端传送的id字段更新对应的user数据。

Controller层

UserController类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@RestController
public class UserController {

@Autowired
private UserService userService;

@GetMapping("/getById/{id}")
public Result getById(@PathVariable Integer id) {
log.info("根据ID查询员工信息,id:{}", id);
User user = userService.getById(id);
return Result.success(user);
}
}

Service层

UserService接口:

1
2
3
public interface UserService {
User getById(Integer id);
}

UserServiceImpl实现类:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserMapper userMapper;

@Override
public User getById(Integer id) {
return userMapper.getById(id);
}
}

Mapper层

注解实现

UserMapper类:

1
2
3
4
5
@Mapper
public interface UserMapper {
@Select("select * from user where id = #{id}")
User getById(Integer id);
}

XML实现

UserMapper.xml

1
2
3
4
5
6
<mapper namespace="_1004.mapper.UserMapper">
<!--resultType为单条记录封装的类型名称-->
<select id="getById" resultType="_1004.pojo.User"> <!--使用<select>标签-->
select * from user where id = #{id}
</select>
</mapper>
  • GET请求:

后记

在XML中为了抽取相同的SQL语句片段,实现良好的复用性可以使用<sql>标签:

1
2
3
4
5
<sql id = "sqlFragmentName">  <!--id:指定复用的sql片段的唯一标识-->
<!--抽取的SQL片段-->
</sql>

<include refid="sqlFragmentName"/> <!--直接使用include标签(自闭合)复用指定的SQL片段-->