MyBatis快速入门

197 阅读8分钟

MyBatis入门程序

什么是MyBatis?

创建SpringBoot项目

image-20250720145928090

勾选MyBatis和MySQL选项,然后创建

确认语言标准

image-20250720150040069

将语言级别统一规定为JDK版本 , 否则会报错

image-20250720150919118

创建一个如图所示的文件目录结构

//TeacherMapper
@Mapper
public interface TeacherMapper {
    @Select("select * from teachers")
    public List<Teacher> list ();
}
// pojo
public class Teacher {
    private Integer id;
    private String tname;
    private int age;
    private String object;
    private int salary;
    private String email;
}
//添加getter setter 有参无参构造 toString 或者使用 lombok, 详情见下文
//test/spring-mybatis-quickstart
@SpringBootTest
class SpringMybatisApplicationTests {

    @Autowired
    private TeacherMapper teacherMapper;
    @Test
    public void testListTeacher(){
        List<Teacher> teacherList = teacherMapper.list();

        teacherList.stream().forEach(
                teacher -> {
                    System.out.println(teacher);
                }
        );
    }

}

@Mapper 注解是由MyBatis提供的,用于标注一个接口时 MyBatis 的 Mapper接口 , 主要作用是 将接口交给 Spring 容器管理 , 并自动生成该接口的实现类代理对象

传统做法实现Dao层要写接口和实现类 , 使用@Mapper注解 , 可以省去写实现类

image-20250720152847772

配置数据源

image-20250720153552060

点击右侧的数据库 , 点击左上方加号 数据源 , mysql , 按照提示自动下载驱动文件

配置自己的数据库名 , 账户 , 密码

lombok注解

为了简化pojo对象的创建 , 使用 lombok 的注解可以简化 getter setter 有参 ,全参构造

@Data

@Getter @Setter @toString equals() hashcode() @RequiredArgsConstructor 的复合版本

只需要标注@Data Spring就会自动实现 上述的方法

@NoArgsConstructor

自动生成无参构造

@AllArgsConstructor

自动生成全参构造


MyBatis基础操作

mybatis 链接: pan.baidu.com/s/1stOPPZCV… 提取码: 5q1d

参数占位符

#{...}

  • 采用 预编译的方式(PreparedStatement) 将 #{...}自动替换为 ?
  • 有效防止SQL注入

${...}

  • 使用拼接字符的方式 直接将 ${...} 拼接在 Statement中 , 存在SQL注入的问题
    • 比如 where 语句中 where name = '士大夫萨芬萨芬' and password = ' ' or '1'='1 '
    • ' or '1'='1 将前面的 password 查询条件 变成空字符串 , 然后 用 or 把 语句变成 永真式
  • 对表名和列表进行动态设置时使用
插入
//EmpMapper.java 
@Insert("insert into emp(username,name,gender,image,job,entrydate,dept_id,create_time,update_time)\n" +
            "    values(#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{dept_id}, #{create_time}, #{update_time});")
    public void insert(Emp emp);
//test/springmybatisTest.java
@Test
public void testInsert() {
    Emp emp = new Emp();
    emp.setUsername("Tom");
    emp.setName("汤姆");
    emp.setGender((short) 1);
    emp.setImage("1.jpg");
    emp.setJob((short) 1);
    emp.setEntrydate(LocalDate.parse("2005-01-01"));
    emp.setDept_id(1);
    // 注意: create_time 和 update_time 通常由数据库自动设置
     emp.setCreate_time(LocalDateTime.now());
     emp.setUpdate_time(LocalDateTime.now());
    empMapper.insert(emp);
}

MyBatis 主键回填;

Mybatis 是"半自动化" 的ORM框架 , 默认不会把数据库自动生成的主键封装到java对象中,除非明确配置主角主键回填

方法:

使用 @Options注解 @Options(keyProperty = "id"(主键名称) ,useGeneratedKeys = true)

  • keyProperty 将数据库生成的主键设置回 emp 对象 的"id" 属性
  • useGeneratedKeys 告诉MyBatis JDBC的主键生成策略
  • 前提是主键自增
删除
//mapper
@Delete("delete from emp where id = #{id} ")
public  int  delete(Integer id );
//test
   @Test
    public void testDelete() {
        int delete = empMapper.delete(19);
        System.out.println(delete);
    }

由于JDBC插入后删除数据后主键 的 auto_increment 并不会变化 , 所以需要改变它的值

简单写一个JDBC的自动更新 auto_increment的测试 , 每次删除后都要跟着启动


@Test
    public void testAlterAutoIncrement() throws Exception {
        Connection conn6 = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/mybatis_learning",
                "root",
                "123456"
        );
        PreparedStatement ps6 = conn6.prepareStatement("alter table emp auto_increment = ?");
        PreparedStatement preparedStatement = conn6.prepareStatement("select max(id) from emp");
        ResultSet rs1 = preparedStatement.executeQuery();
        int num = 0;
        if (rs1.next()) {
            num = rs1.getInt(1) + 1;
        }

        ps6.setInt(1, num);
        int count = ps6.executeUpdate();
        ps6.execute("FLUSH TABLES");
        System.out.println("success alter auto increment into " + num);


        rs1.close();
        preparedStatement.close();
        ps6.close();
        conn6.close();
    }

更新
//mapper
@Options(keyProperty = "id", useGeneratedKeys = true)
    @Update("update emp set username = #{emp.username}, name = #{emp.name}, gender = #{emp.gender}, image = #{emp.image}, " +
            "job = #{emp.job}, entrydate = #{emp.entrydate}, dept_id = #{emp.deptId}, create_time = #{emp.createTime}, " +
            "update_time = #{emp.updateTime} where id = #{id}")
    public void update(@Param("emp") Emp emp, @Param("id") Integer id);


//test
@Test
    public void testUpdate() {
        Emp emp = new Emp();
        emp.setUsername("cola");
        emp.setName("可乐");
        emp.setGender((short)1);
        emp.setImage("1.jpg");
        emp.setJob((short)2);
        emp.setEntrydate(LocalDate.parse("2005-03-02"));
        emp.setDeptId(2);
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        //method 1 : emp.setId(17);
        //method 2: 
        Integer id = 17;
        empMapper.update(emp,id);
    }

为什么需要setId来设置ID呢

当使用@Update注解时,MyBatis期望提供一个包含所有需要更新字段的对象

如果直接传递一个id作为单独的参数 , MyBatis将无法自动识别它 , 因为注解中的SQL语句引用的时对象的属性

手动设置ID的方法

使用@Param 注解 来规定填充的内容

public void update(@Param("emp") Emp emp, @Param("id") Integer id);

@Param和@RequestParam

  • @RequestParam是Spring 用于Controller层接收HTTP请求参数
  • @Param 用于MyBatis 的Mapper 接口 接收传给 SQL 的参数
  • 一个是请求处理操作 , 一个是数据库操作

数据封装的问题

image-20250721163030398

可以看到 后三没有从数据库映射到Emp对象中

实体类的属性 和 数据库表的字段 名成可能不一样 , myBatis 无法封装 的问题

同样的问题 在JDBC数据库连接池 的 Dao层 实现类中也有 类似的问题

那时的解决方案是 使用 字段别名

前面为了 方便数据封装 , 把实体类的 属性和字段名统一 设置为字段名

但是实际开发时往往应用的是驼峰命名法 , 为了规范变量的命名 , 我们需要把实体类的属性名称修改

dept_id ----> deptId

create_time --> createTime

update_time --> updateTime

//method 1 : 起别名
    @Select("select id ,username ,password,name,gender,image,job," +
            "entrydate entryDate,dept_id deptId,create_time createTime,update_time updateTime from emp where id = #{id}")
    public  Emp  getById(Integer id );


//method 2: 手动处理映射

    @Results({
            @Result(column = "dept_id", property = "deptId"),
            @Result(column = "create_time" , property = "createTime"),
            @Result(column = "update_time" ,property = "updateTime")

    })
    @Select("select * from emp where id = #{id}")
    public  Emp  getById(Integer id );

//method 2 或者  把emp实体对象传入用参数占位符的方法手动设置映射关系


   @Insert("insert into 		   emp(username,name,gender,image,job,entrydate,dept_id,create_time,update_time)\n" +
            "    values(#{username}, #{name}, #{gender}, #{image}, #{job}, #{entryDate}, #{deptId}, #{createTime}, #{updateTime});")
    public void insert(Emp emp);


//method 3  开启mybatis的自动的驼峰命名的自动映射开关
//application.properties
mybatis.configuration.map-underscore-to-camel-case=true

查询
根据id查询
//mapper  (已经设置过 mybaits驼峰命名映射)
@Select("select * from emp where id = #{id}")
    public Emp getById(Integer id);
//test

    @Test
    public void testGetById() {
        Integer id = 16;
        Emp empSelected = empMapper.getById(id);
        System.out.println(empSelected);
模糊查询
//mapper
    @Select("select * from emp where name like '%${name}%' and gender = #{gender} and " +
            "entrydate between #{begin} and #{end}")
    public List<Emp> scope(String name , short gender, LocalDate begin , LocalDate end);
//test
@Test
    public void testScope() {
        List<Emp> empList = empMapper.scope("张", (short) 1, LocalDate.of(2010, 1, 1), LocalDate.of(2020, 1, 1));
        empList.stream().forEach(
                emp -> {
                    System.out.println(emp);
                }
        );
    }

模糊查询中 "张" 要作为字符串插入'% %'中间 , 使用 拼接型占位符 ${...}

XML映射实现动态SQL

命名规范

  • xml 映射文件的名称与mapper的接口保持一致
  • xml 映射文件的存放位置和mapper的包名保持相同
  • xml映射文件的namespace属性为mapper接口全限定名保持一致
  • xml映射文件的 sql语句的id 与 Mapper 接口的方法要保持一致 , 并且返回类型要一致
    示例
//EmpMapper
public List<Emp> scope(String name, short gender, LocalDate begin, LocalDate end);
}
//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="com.zpero.mapper.EmpMapper">

    <select id="scope" resultType="com.zpero.pojo.Emp">
        select * from emp where name like concat('%',#{name},'%') and
                 gender = #{gender} and entrydate between #{begin} and #{end}
                    order by update_time desc
    </select>
</mapper>

去掉 select 注解 后 , mybatis 会根据 包名到resourse 配置文件夹中去寻找 相应的SQL语句

我们 方法名字是 scope , xml文件中的 id 也应该是 scope , resultType是 对应 返回的实体类型的路径 . mybatis 需要根据路径映射 相应的实体

image-20250724125217376

插件

image-20250724124506256

MyBatisX 插件 功能;

  • mapper 接口和 xml文件 之间可以来回跳转

  • mybaits.xml , mapper.xml 智能提示词

  • mapper 和 xml 支持自动提示补全

  • 继承Mybatis Generator 的图形界面

使用后 界面会出现 mybatis 的图标 ,点击可以跳转

image-20250724125315819

标签

<if>标签

用于判断条件是否成立 , test属性 是条件表达式 , 返回true 才会渲染标签里的SQL 内容

<select id="scope" resultType="com.zpero.pojo.Emp">
        select * from emp
        where
            <if test="name != null and name != ''">
                name like concat('%',#{name},'%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="begin != null and end != null">
                and entrydate between #{begin} and #{end}
            </if>
        order by update_time desc
    </select>

原来的 where 条件被 <if> 标签隔开 , 动态拼接SQL语句

如果测试用例是 "张 , null,null,null" 筛选出 名字中带有 张 的员工

image-20250724141229759

可以看到筛选条件只有一个 name

但是 只使用 <if> 标签 是拼接sql语句的 , 所以 如果筛选条件变成 null , (short) 1 ,null,null sql语句被拼接成了 where and gender = 1 , 会出现语法错误 . 所以 <if> 标签要搭配 <where> 使用

<where> 标签

用于动态自动拼接where 子句 避免多余的 and 和 or , 使sql 更加健壮整洁

<where>
    <if test="name != null and name != ''">
        name like concat('%',#{name},'%')
    </if>
    <if test="gender != null">
        and gender = #{gender}
    </if>
    <if test="begin != null and end != null">
        and entrydate between #{begin} and #{end}
    </if>
</where>
<set>标签

和where同理, 存在拼接错误 , 出现 ,在where之前 , 报语法错误, set 后很多个sql 拼接时 要用 <set>标签包裹

添加 update2 方法

    public void update2(Emp emp);
 @Test
    public  void testUpdate2(){
        Emp emp = new Emp();
        emp.setId(18);
        emp.setUsername("tom1111");
        emp.setGender((short)2);
        emp.setUpdateTime(LocalDateTime.now());

        empMapper.update2(emp);
        System.out.println(emp);
    }
 <update id="update2">
        update emp
        <set>
        <if test="username != null ">username = #{username},</if>
        <if test="name != null ">name = #{name},</if>
        <if test="gender != null ">gender = #{gender},</if>
        <if test="image != null ">image = #{image},</if>
        <if test="job != null ">job = #{job},</if>
        <if test="entryDate != null ">entrydate = #{entryDate},</if>
        <if test="deptId != null ">dept_id = #{deptId},</if>
        <if test="createTime != null ">create_time = #{createTime},</if>
        <if test="updateTime != null ">update_time = #{updateTime} </if>
		<set>
        where id = #{id}
    </update>

传入的参数 是 Emp 对象 , test属性里 的 变量名称应该和 Emp对象中定义的一致 , 必须手动指定 属性名 , 无法使用驼峰命名映射

<foreach>标签
  • collection: 遍历的集合
  • item: 遍历的数组
  • separator: 分隔符
  • open : 遍历开始前拼接的sql片段
  • close : 遍历结束后拼接的sql片段
//mapper
public void deleteBatch(List(Integer) ids);
//xml
<delete id="deleteBatch">
        delete  from emp
        where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>
//test
@Test
public void testDeleteBatch(){
    List<Integer> ids  = Arrays.asList(18,19,20);
    empMapper.deleteBatch(ids);
}

delete from emp where id in (18,19,20)

<sql> <include>标签

一个可复用sql片段 , 定义一段可以被其他失去了语句 重复使用的 sql代码 来避免重复写相同的字段或条件,提高代码的可维护性

  • id 字段 : sql 片段的唯一标识
  • refid: include 标签中的 唯一标识

例如

    <sql id="CommonSelect">
        select id,username,password,name,gender,image,job,entrydate,dept_id,create_time,update_time
        from emp
    </sql>

使用<include>导入

<select id="scope" resultType="com.zpero.pojo.Emp">
    <include refid="CommonSelect"></include>
    where
    <if test="name != null ">
        name like concat('%',#{name},'%')
    </if>
    <!-- ... -->