【MyBatis保姆级教程上】近万字从零开始手把手教你玩转数据库操作!配置+CRUD+日志+参数传递全解析


目录

1.前言

2.正文

2.1MyBatis与JDBC

2.2MyBatis入门

2.2.1准备工作

2.2.2配置数据库

2.2.3持久层代码

2.2.4测试

2.3MyBatis基础操作

2.3.1打印日志

2.3.2参数传递

2.3.3增

2.3.4删(简)

2.3.5改(简)

2.3.6查

2.3.6.1起别名

2.3.6.2结果映射 

2.3.6.3开启驼峰命名 

3.小结


1.前言

哈喽大家好吖,今天我们开始学习MyBatis,今天我们先学习如何用注解的方式完成代码的编写,下一篇博文在完成如何使用XML文件实现MyBatis的编程。无论你是想10分钟快速上手,还是彻底搞懂MyBatis的底层逻辑,无需任何MyBatis基础,我将从环境搭建、日志打印、参数绑定,一路带你手撕增删改查核心操作。


插播一条消息~

发现一个系统化的人工智能学习平台,涵盖从基础理论到工业级项目实战(人脸识别/自动驾驶/GANs等),内容由浅入深结构清晰,特别适合想体系化提升AI开发能力的朋友,忍不住把干货分享给大家👉

人工智能教学网站https://www.captainbed.cn/scy


2.正文

官网:MyBatis中文网(广告有点多看着心刺挠~)

2.1MyBatis与JDBC

MyBatis 和 JDBC 是 Java 中操作数据库的两个不同层级的工具,它们之间既有继承关系,也有显著的差异:

基础关系:

  • JDBC(Java Database Connectivity)是 Java 提供的标准数据库访问接口,定义了操作数据库的通用 API(如 ConnectionStatementResultSet),但需要开发者手动编写所有数据库操作代码。

  • MyBatis 是基于 JDBC 的持久层框架,对 JDBC 进行了高级封装,简化了数据库操作流程,同时保留了直接编写 SQL 的灵活性。

关系总结
MyBatis 底层依赖 JDBC 驱动与数据库交互,但通过封装和扩展,隐藏了 JDBC 的复杂性。


特性JDBCMyBatis
代码复杂度需要手动管理连接、语句、结果集,代码冗余。自动管理资源(如连接、事务),减少样板代码。
SQL 与代码耦合SQL 嵌入 Java 代码中,难以维护。SQL 与 Java 代码解耦,通过 XML 或注解配置。
结果集映射需手动遍历 ResultSet 并映射到对象。自动将结果集映射到 Java 对象(ORM 特性)。
动态 SQL需手动拼接字符串,易出错且不安全。支持动态 SQL 标签(如 <if><foreach>)。
事务管理需手动提交/回滚事务。支持声明式事务管理(整合 Spring 时更灵活)。
缓存机制无内置缓存。提供一级缓存(SqlSession 级别)和二级缓存。
扩展性直接控制底层,灵活性高但开发效率低。通过插件机制扩展功能(如分页、日志)。

2.2MyBatis入门

2.2.1准备工作

配置项目用到的jar包:

sql测试代码:

-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;

CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;

-- 使用数据数据
USE mybatis_test;

-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
        `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
        `username` VARCHAR ( 127 ) NOT NULL,
        `password` VARCHAR ( 127 ) NOT NULL,
        `age` TINYINT ( 4 ) NOT NULL,
        `gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默认',
        `phone` VARCHAR ( 15 ) DEFAULT NULL,
        `delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
        `create_time` DATETIME DEFAULT now(),
        `update_time` DATETIME DEFAULT now() ON UPDATE now(),
        PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; 

-- 添加用户信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );


-- 创建文章表
DROP TABLE IF EXISTS article_info;
        
CREATE TABLE article_info (
        id INT PRIMARY KEY auto_increment,
        title VARCHAR ( 100 ) NOT NULL,
        content TEXT NOT NULL,
        uid INT NOT NULL,
        delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
        create_time DATETIME DEFAULT now(),
        update_time DATETIME DEFAULT now() 
) DEFAULT charset 'utf8mb4';

-- 插入测试数据
INSERT INTO article_info ( title, content, uid ) VALUES ( 'Java', 'Java正文', 1 );

2.2.2配置数据库

配置yml文件:

spring:
  application:
    name: mybatis-demo
  # 数据库配置
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

2.2.3持久层代码

编写代码规范:

数据库字段:全部小写,单词之间使用_分割

Java规范:属性使用小驼峰来表示 

model包(采用注解的方式实现):

@Mapper
public interface UserInfoMapper {

    @Select("select * from user_info")
    List<UserInfo> selectAll();
}

mapper包:

@Data
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Integer gender;
    private String phone;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

2.2.4测试

第一种测试方法(在Test文件夹写测试代码):

在test文件下创建mapper包,再在mapper包中创建Test用于测试数据库数据:

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void selectAll() {
        System.out.println(userInfoMapper.selectAll());
    }
}

测试结果: 

 或者直接在test文件夹下原有的Tests文件进行测试:

@SpringBootTest
class MybatisDemoApplicationTests {

	@Autowired
	private ApplicationContext context;//在测试代码中直接注入

	@Test
	void contextLoads() {
		UserInfoMapper bean = context.getBean(UserInfoMapper.class);
		bean.selectAll().stream().forEach(x-> System.out.println(x));
	}

}

也可以正常输出结果。 

这里讲解两个新的注解

@Test 注解:

  • 作用:标记一个方法为单元测试方法,用于验证单个代码单元的逻辑(如一个方法、一个类)。

  • 特点

    • 不依赖 Spring 上下文:测试方法独立运行,不启动 Spring 容器。

    • 轻量快速:适合测试纯 Java 代码逻辑,无需加载 Spring 相关组件。

    • 需手动处理依赖:如果被测试的类依赖其他组件(如 Service、Repository),需要手动 Mock(例如用 Mockito)。


@SpringBootTest 注解:

  • 作用:标记一个测试类为集成测试,启动完整的 Spring 应用上下文,模拟真实运行环境。

  • 特点

    • 加载完整 Spring 上下文:自动扫描配置、Bean、数据库、外部服务等。

    • 支持依赖注入:可以直接使用 @Autowired 注入 Bean。

    • 配置灵活:通过 webEnvironment 指定 Web 环境(如模拟 Servlet 环境、真实端口等)。

    • 较慢:因为加载整个应用,适合测试组件交互、API 接口等。

特性@Test@SpringBootTest
测试类型单元测试集成测试
Spring 上下文不加载加载完整 Spring 上下文
依赖注入不支持(需手动 Mock)支持(通过 @Autowired
执行速度较慢(因加载上下文)
适用场景验证纯 Java 逻辑测试组件交互、数据库操作、API 接口

第二种测试方法(通过Controller调用service中方法): 

代码结构:

userController:

@RequestMapping("/user")
@RestController
public class UserInfoController {

    @Autowired
    private UserService userService;

    @RequestMapping("/getAllUser")
    public List<UserInfo> getAllUser(){
        return userService.getAllUser();
    }
}

userService:

@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    public List<UserInfo> getAllUser() {
        return userInfoMapper.selectAll();
    }
}

运行DemoApplication,并访问网页:

同样测试成功。

此外,我们会还有第三种方法来写测试用例,即让编译器为我们生成:

右键generate选择test出现:

解释@Before与@After:一个测试方法前实现,一个测试方法后实现

将二者勾选上后覆盖:

@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void selectAll() {
        System.out.println(userInfoMapper.selectAll());
    }

    @BeforeEach
    void setUp() {
        System.out.println("before.......");
    }

    @AfterEach
    void tearDown() {
        System.out.println("after......");
    }
}

输出结果如下:

至此,测试流程已讲解完毕,接下来来讲解MyBatis的基础操作。 

2.3MyBatis基础操作

2.3.1打印日志

为了方便学习基础操作,很显然打印日志必不可少,所以我们先配置以下日志的输出格式:

mybatis:
  # 配置 mybatis xml 的文件路径,在 resources/mapper 创建所有表的 xml 文件
  mapper-locations: classpath:mapper/**Mapper.xml
  configuration: 
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true #配置驼峰自动转换

输出测试日志:

在日志中间部分:

==>:表示向数据库输入的

<==:表示数据库返回的

2.3.2参数传递

MyBatis 的参数传递规则围绕 方法参数名与 SQL 占位符的绑定,分为以下几种场景:


1. 无参数方法

@Select("SELECT * FROM user_info")
List<UserInfo> selectAll();
  • 场景:无需传递参数。

  • 要点

    • 直接执行 SQL,无需处理参数绑定。


 2. 单参数方法

案例 1:selectAllById1

@Select("select * from user_info where id = #{id}")
    UserInfo selectAllById1(Integer id);

案例 2:selectAllById2

@Select("select * from user_info where id = #{id}")
    List<UserInfo> selectAllById2(Integer id);
  • 参数规则

    • 单参数时:占位符 #{xxx} 的 xxx 可以任意命名(如 #{id}#{value}#{abc} 均可)。

    • MyBatis 默认按参数顺序绑定,与占位符名称无关。

    • 但为了可读性,建议占位符名称与参数名一致。

  • 返回值差异

    • selectAllById1:返回单个对象(查询结果最多一条时)。

    • selectAllById2:返回列表(即使结果只有一条)。


 3. 多参数方法

@Select("select * from user_info where username = #{username} and password = #{password}")
    List<UserInfo> selectByNameAndPassword(String username, String password);

参数规则

  1. 默认行为(无 @Param 注解)

    • MyBatis 会将多个参数封装为 Map,键为 param1, param2, ... 或 arg0, arg1, ...

    • 此时 SQL 占位符必须使用 {param1}#{param2} 或 #{arg0}#{arg1}

    • 直接使用参数名(如 #{username})会报错,因为默认无法解析参数名。

  2. 推荐做法(使用 @Param 注解)

    @Select("SELECT * FROM user_info WHERE username = #{username} AND password = #{password}")
    List<UserInfo> selectByNameAndPassword(
        @Param("username") String username, 
        @Param("password") String password
    );
    • 通过 @Param 显式指定参数名,占位符 #{xxx} 必须与注解值一致。

    • 本质:MyBatis 会将参数封装为 Map,键为 @Param 指定的名称。

MyBatis 的参数绑定最终是 基于键值对的 Map 结构

  • 单参数:键可以是任意名称,或通过 @Param 指定。

  • 多参数:必须通过 @Param 指定键,否则只能用 param1/arg0 等默认键。

 这里最后对比下不同的参数传递:

场景占位符写法示例是否需要 @Param注意事项
无参数直接执行 SQL
单参数#{任意名称}否(建议与参数名一致)占位符名称无约束,但需保持语义清晰
多参数(默认)#{param1}#{arg0}可读性差,易出错
多参数(推荐)#{自定义名称}需通过 @Param 指定名称

2.3.3增

测试代码:

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert("insert into user_info(username, password, age) values (#{username}, #{password}, #{age})")
    Integer insertUser(UserInfo userInfo);

1. 核心功能

  • 作用:向 user_info 表插入一条用户记录,并回填数据库生成的自增主键到 UserInfo 对象的 id 属性。

  • 返回值:返回插入操作影响的行数(通常为 1)。


2. 注解解析

(1) @Insert 注解

  • 定义 SQL:指定插入语句,使用 #{属性名} 占位符绑定参数。

    • #{username}#{password}#{age} 会调用 UserInfo 对象的 getUsername()getPassword()getAge() 方法获取值。

    • 要求UserInfo 类必须有对应的 getter 方法。


(2) @Options 注解

  • 用途:控制 SQL 执行的附加选项。

  • 关键参数

    • useGeneratedKeys = true:启用数据库自增主键回填。

    • keyProperty = "id":将生成的主键值回填到 UserInfo 对象的 id 属性中。

  • 要求

    • 数据库表的主键字段必须支持自增(如 MySQL 的 AUTO_INCREMENT)。

    • UserInfo 类必须有 id 属性的 setter 方法(即 setId)。

2.3.4删(简)

delete from user_info where id=6

把SQL中的常量替换为动态的参数

Mapper接口:

@Delete("delete from user_info where id = #{id}")
void delete(Integer id);

2.3.5改(简)

update user_info set username="zhaoliu" where id=5

把SQL中的常量替换为动态的参数

Mapper接口:

@Update("update user_info set username=#{username} where id=#{id}")
void update(UserInfo userInfo);

2.3.6查

我们前文讲解参数传递的时候牵扯到一些查询操作,但难免会遇到比如代码中的参数名与表中的参数名不一致的情况接下来我们介绍三种解决该问题的方式。

2.3.6.1起别名

第一个方案这里我们就需要用到别名来进行操作了:

@Select("select id, username,`password`, age, gender,  phone, " +
            "delete_flag as deleteFlag, create_time as createTime, update_time as updateTime" +
            " from user_info")

    List<UserInfo> selectAll();

2.3.6.2结果映射 

当然,上面那个代码显得十分臃肿,这时又有注解来给咱们减轻压力了:

只要告诉注解,哪个参数和哪个参数是一一对应即可。

@Results(id = "BaseMap", value = {
            @Result(column = "delete_flag", property = "deleteFlag"),
            @Result(column = "create_time", property = "createTime"),
            @Result(column = "update_time", property = "updateTime")
    })
    @Select("select * from user_info")
    List<UserInfo> selectAll();

正常运行~

核心思路解析:@Results 注解的作用与使用:

1. @Results 注解的作用

  • 定义结果映射规则:将查询结果中的数据库字段(column)映射到 Java 对象的属性(property)。

  • 解决命名差异:当数据库字段名与 Java 属性名不一致时(如蛇形命名 delete_flag vs 驼峰命名 deleteFlag),需手动指定映射关系。

  • 复用映射配置:通过 id = "BaseMap" 标记此映射规则,可在其他方法中通过 @ResultMap("BaseMap") 复用。


2. 关键注解解析

注解/属性说明
@Results定义一组结果映射规则,包含多个 @Result
id = "BaseMap"为此映射规则命名,方便其他方法通过 @ResultMap 引用。
@Result定义单个字段的映射关系。
column = "delete_flag"数据库字段名(实际查询结果中的列名)。
property = "deleteFlag"Java 实体类属性名(UserInfo 类中的属性)。

3. 执行流程

  1. 执行查询:调用 selectAll() 方法,MyBatis 执行 SQL 查询。

  2. 结果映射

    • 根据 @Results 定义的规则,将结果集中的 delete_flag 映射到 UserInfo.deleteFlag

    • 同理处理 create_time → createTimeupdate_time → updateTime

  3. 返回对象:将映射后的 UserInfo 对象装入列表返回。

2.3.6.3开启驼峰命名 

当然我们可以在yml文件中配置使代码更加便捷:

mybatis:
  # 配置 mybatis xml 的文件路径,在 resources/mapper 创建所有表的 xml 文件
  configuration: # 配置打印 MyBatis日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true #配置驼峰自动转换

当然想使用这个配置,你的变量的命名必须严格按照小驼峰命名法(代码就很简单啦)。

@Select("select * from user_info")
    List<UserInfo> selectAll();

正常输出:

3.小结

今天的分享到这里就结束了,喜欢的小伙伴点点赞点点关注,需要所有的源代码可以去我的gitee上就可以啦~你的支持就是对我最大的鼓励,大家加油!

爱吃烤鸡翅的酸菜鱼 (crjs-hao) - Gitee.comhttps://gitee.com/crjs-hao另外最后的最后,欢迎大家加入我的社区哦,初创社区难免经验不足,请大家多多包涵,也欢迎大家前来多多交流。

爱吃烤鸡翅的酸菜鱼社区-CSDN社区云https://bbs.csdn.net/forums/aaa1f71356f6475db42ea9ea09a392bc?spm=1001.2014.3001.6682

评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱吃烤鸡翅的酸菜鱼

希望大家对我多多支持喔~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值