小白学习笔记(MyBatis)

介绍:

mybatis就是对jdbc技术的封装

大体步骤:

举例:

导入依赖:

复制代码
<!--导入mybatis依赖-->
<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.11</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.3.1</version>

    </dependency>


</dependencies>

实体类和mapper接口:

复制代码
package com.atguigu.pojo;

public class Employee {

    private Integer empId;

    private String empName;

    private Double empSalary;
    
    //getter | setter
}

package com.atguigu.mapper;

import com.atguigu.pojo.Employee;

/**
 * ClassName:Employ
 * Package:com.atguigu.mapper
 * Description:
 *
 * @Author 妄汐霜
 * @Create 2026/3/24 16:21
 * @Version 1.0
 */
public interface EmployeeMapper {
    Employee queryById(Integer id);

    int deleteById(Integer id);
}

mapper接口的xml文件:在这个文件里写sql语句

复制代码
<?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">
<!-- namespace等于mapper接口类的全限定名,这样实现对应 -->
<mapper namespace="com.atguigu.mapper.EmployeeMapper">
        <!--xml方式写sql语句 没有Java代码
        mybatis规定在特定的标签下写sql语句
        mapper文件会有约束-->


    <!-- 查询使用 select标签
            id = 方法名
            resultType = 返回值类型
            标签内编写SQL语句

            注意:mapper接口中不能重载


     -->
    <select id="queryById" resultType="com.atguigu.pojo.Employee">
        <!-- #{empId}代表动态传入的参数,并且进行赋值!后面详细讲解 -->
        select emp_id empId,emp_name empName, emp_salary empSalary from
        t_emp where emp_id = #{empId}
    </select>

    <delete id="deleteById" >
        delete from t_emp where emp_id = #{id}
    </delete>
</mapper>

mybatis的配置文件:

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,
    使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
    <environments default="development">
        <!-- environment表示配置Mybatis的一个具体的环境 -->
        <environment id="development">
            <!-- Mybatis的内置的事务管理器 -->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源 -->
            <dataSource type="POOLED">
                <!-- 建立数据库连接的具体信息 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
                <property name="username" value="root"/>
                <property name="password" value="abc123"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
        <!-- mapper标签:配置一个具体的Mapper映射文件 -->
        <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
        <!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
        <mapper resource="mappers/EmployeeMapper.xml"/>
    </mappers>

</configuration>

使用:

复制代码
public class MybatisTest {
     @Test
     public void test_01() throws IOException {
         //1.读取外部配置文件
         InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");

         //2.创建sqlSessionFactory
         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);

         //3.根据sqlSessionFactory创建sqlSession(每次业务创建一个,用完就释放)
         SqlSession sqlSession = sqlSessionFactory.openSession();

         //4.获取接口的代理对象(代理技术)调用代理对象的方法,就会查找mapper接口的方法
         EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
         Employee employee = mapper.queryById(1);
         System.out.println(employee);
         //5.提交事务和释放资源
         sqlSession.close();
     }
}

基本使用:

mybatis日志输出设置:

2.1.1 mybatis日志输出配置

mybatis配置文件设计标签和顶层结构如下:

  • configuration(配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)

我们可以在mybatis的配置文件使用settings标签设置,输出运过程SQL日志!

通过查看日志,我们可以判定#{} 和 ${}的输出效果!

settings设置项:

|logImpl|指定 MyBatis 所用日志的具体实现,未指定时将自动查找。|SLF4J | LOG4J(3.5.9 起废弃) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING|未设置|

|-|-|-|-|

日志配置:

复制代码
<settings>
  <!-- SLF4J 选择slf4j输出! -->
  <setting name="logImpl" value="SLF4J"/>
</settings>

向sql语句传参的两种方式:

#{key}和${key}

mapper的配置文件

复制代码
<?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">
<!-- namespace等于mapper接口类的全限定名,这样实现对应 -->
<mapper namespace="com.atguigu.pojo.mapper.EmployeeMapper">

    <!--
    #{key} :占位符 + 赋值 emp_id = ?   再把?赋值
    ${key} : 字符串拼接  "emp_id = " + id

    推荐使用#{key}防止注入攻击的问题
    总结:如果是动态值,就使用#{key}
        如果是动态的列名,容器名,关键字就使用${key}


    -->
    <select id="queryById"  resultType="com.atguigu.pojo.Employee">
        select emp_id empId,emp_name empName,emp_salary empSalary
            from t_emp where emp_id = #{id}
    </select>


</mapper>

数据输入:

传入一个简单类型数据:

接口:

复制代码
//根据工资查询员工信息
List<Employee> queryBySalary(Double salary);


//插入员工数据
int insertEmp(Employee employee);

mapper配置文件:

复制代码
<!--场景1:传入的单个简单类型 key值随便写 一般情况下就用参数名-->
<delete id="deleteById">
    delete from t_emp where emp_id = #{key}
</delete>

<select id="queryBySalary" resultType="com.atguigu.pojo.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary
    from t_emp where emp_salary = #{salary}
</select>

<!--场景2:如果传入的是一个实体对象,那么key值 = 属性值就可以-->
<insert id="insertEmp">
    insert into t_emp (emp_name,emp_salary) values(#{empName},#{empSalary});
</insert>

传入多个简单类型的数据:

接口:

复制代码
//根据员工姓名和员工工资查询员工信息
List<Employee> queryByNameAndSalary(@Param("a") String name,@Param("b") Double salary);

mapper的xml配置文件:

复制代码
<!--场景3:传入多个简单类型数据 要怎么取key值
    方案1:注解指定 @Param注解指定  可以指定多个简单参数的key key值 = @Param注解("value")括号里的value值
    方案2:mybatis默认机制
-->
<select id="queryByNameAndSalary" resultType="com.atguigu.pojo.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary
    from t_emp where emp_name = #{a} and emp_salary = #{b}
</select>

mapper接口中不允许方法重载!!!!

传入map类型数据数据:

mapper接口:

复制代码
//插入员工数据,传入的是一个map(name = 员工的名字,salary = 员工的薪水)
int insertEmpMap(Map data);

mapper的xml配置文件:

复制代码
<!--场景4:传入map 如何指定key的值
          key = map的key即可
-->
<insert id="insertEmpMap">
    insert into t_emp (emp_name , emp_salary) values(#{name},#{salary})
</insert>

数据输出:

单个简单类型:

mapper接口:

复制代码
public interface EmployeeMapper {
        //如果是dml语句(插入 修改  删除)
        int deleteById(Integer id);

        //指定输出类型 查询语句
        //根据员工的id查询员工的姓名
    String queryNameById(Integer id);

    //根据员工的id查询员工的工资
    Double querySalaryById(Integer id);

}

mapper的xml文件:

复制代码
<mapper namespace="com.atguigu.mapper.EmployeeMapper">
    
    <delete id="deleteById">
        delete from t_emp where emp_id = #{id}
    </delete>


    <select id="queryNameById" resultType="java.lang.String">
        select emp_name from t_emp where emp_id = #{id}
    </select>


</mapper>

定义别名:

在 MyBatis 的 XML 映射文件中,resultTypeparameterType 等属性需要写类的全限定名 (比如 com.atguigu.pojo.Employee),代码冗长且不易维护。

别名 就是给这些全限定名起一个简短的别名,在 XML 中直接用别名代替全类名,简化开发、提升可读性。

复制代码
如果自己要定义别名:
给自己声明的类定义别名:
    给类单独定义别名:
         <typeAliases>
            <typeAlias type="com.atguigu.pojo.Employee" alias="自己定义的别名"/>
         </typeAliases>
    批量设置别名:
    <typeAliases>
         批量将包下的类取别名,别名就是类的首字母大小写
         <package name= "com.atguigu.pojo">
    </typeAliases>

    如果不想使用批量的别名,可以使用注解给包取别名
    @Alias("自己定义的别名")
    这个注解作用在想定义别名的类上
@Alias 注解给类定义别名

适用场景:配合批量包扫描,对个别类自定义别名,避免默认别名冲突或不符合需求。

步骤

  1. 在实体类上添加 @Alias 注解:java运行

    import org.apache.ibatis.type.Alias;

    @Alias("emp") // 自定义别名
    public class Employee {
    // 类属性与方法
    }

  2. 依然使用 <package> 批量扫描包:xml

    <typeAliases> <package name="com.atguigu.pojo"/> </typeAliases>

单个实体类型:

接口:

复制代码
//返回单个自定义实体类型
Employee queryById(Integer id);

配置类:

复制代码
<!--返回单个自定义的实体类型-->
<!--默认要求:如果返回单个实体类型的话,要求列名和属性名要一致
    这样才可以进行实体类的属性映射

    但是可以进行设置  直接设置成支持驼峰式自动映射
    在mybatis配置文件中,使用setting标签设置
-->
<select id="queryById" resultType="com.atguigu.pojo.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary
    from t_emp where emp_id = ${id}
</select>

在mybatis配置文件中开启驼峰式自动映射:

复制代码
<settings>
    <!--开启驼峰式自动映射-->
    <setting name="mapUnderScoreToCamelCase" value="true"/>
</settings>

返回map类型:

当没有实体类可以接值的时候,我们就可以使用map接值

key -> 查询的列

value -> 查询的值

返回list类型:

接口:

复制代码
//查询工资高于传入平均值的员工姓名
List<String> queryNamesBySalary(Double salary);

//查询全部员工信息
List<Employee> queryAll();

xml文件:

复制代码
<!--返回的是list类型

    返回值是list,resultType不需要指定集合类型,只需要指定泛型即可
-->
<select id="queryNameBySalary" resultType="string">
    select emp_name from t_emp where emp_salary > #{salary}
</select>

<select id="queryAll" resultType="employee">
    select * from t_emp
</select>

自增长类型的主键回显和自动提交事务:

什么是主键回显?

在插入数据时,数据库会自动生成自增主键 (比如 MySQL 的 AUTO_INCREMENT),但插入完成后,Java 对象里的主键属性还是空的。主键回显就是让 MyBatis 把数据库生成的主键值,自动回填到传入的 Java 对象的对应属性中,方便后续业务使用(比如关联插入、日志记录)。

  • useGeneratedKeys="true":告诉 MyBatis:「我要获取数据库自动生成的主键值」,仅支持自增主键的数据库(MySQL、SQL Server 等)。
  • keyColumn="emp_id":指定数据库表中主键列的列名 (这里是 t_emp 表的 emp_id 字段)。
  • keyProperty="empId":指定 Java 实体类(Employee)中接收主键值的属性名 (这里是 empId 字段)。

执行后,MyBatis 会把数据库生成的 emp_id 值,自动赋值给传入的 employee 对象的 empId 属性

接口:

复制代码
//员工插入
int insertEmp(Employee employee);

xml文件:

复制代码
<!--
    主键回显,获取插入数据的主键
    1.自增长主键回显
    useGeneratedKeys = "true" 我们想要数据库自动增强的主键值
    keyColumn="emp_id" 主键列的值
    keyProperty="empId" 接收主键列值的属性(就是把keyColumn的值赋值给keyProperty的值)
-->
<insert id="insertEmp" useGeneratedKeys="true" keyColumn="emp_id" keyProperty="empId">
    insert into t_emp (emp_name,emp_salary)
    value(#{empName},#{empSalary})
</insert>

测试类:

复制代码
public class MybatisTest {

    @Test
    public void test_01() throws IOException {
        //读取外部配置文件
        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");

        //创建sqlSessionFactory
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(ips);

        //获取sqlSession对象
        //openSession自动开启事务,不会自动提交
        //需要sqlSession.commit()才会提交
        SqlSession sqlSession = build.openSession();

        //获取代理对象
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

        Employee employee = new Employee();
        employee.setEmpSalary(888.0);
        employee.setEmpName("辛铁柱");

        System.out.println(employee.getEmpId());
        int rows = mapper.insertEmp(employee);
        System.out.println(rows);
        System.out.println("---------");
        System.out.println(employee.getEmpId());

        //释放资源和提交事务
        sqlSession.commit();//当是DML语句的时候,必须提交事务
        sqlSession.close();



    }
}

非自增长类型主键维护:

什么是「非自增类型主键」?

和之前 MySQL 的 AUTO_INCREMENT 自增数字主键不同,非自增主键是指:

  • 主键不是由数据库自动生成的数字
  • 主键类型可以是 String(如 UUID、业务编码)、自定义数字(如雪花算法 ID)等
  • 主键需要手动赋值 或通过数据库函数 / 序列生成,再插入数据库

实体类:

复制代码
public class Teacher {
    private  String tId;
    private String tName;


    public String gettId() {
        return tId;
    }

    public void settId(String tId) {
        this.tId = tId;
    }

    public String gettName() {
        return tName;
    }

    public void settName(String tName) {
        this.tName = tName;
    }
}

接口:

复制代码
public interface TeacherMapper {
    int insertTeacher(Teacher teacher);
}

mapper的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">
<!-- namespace等于mapper接口类的全限定名,这样实现对应 -->
<mapper namespace="com.atguigu.mapper.TeacherMapper">
    <!--
        把非自增长的主键,同样交给mybatis帮助我们维护
    -->
    <insert id="insertTeacher">
        <!--插入之前,先执行一段sql语句,生成一个主键值-->
        <!--使用selectKey标签  order是插入语句之前还是之后执行   resultType是返回值类型  keyProperty 是查询结果给哪个属性赋值-->
        <selectKey order="BEFORE" resultType="string" keyProperty="tId">
            SELECT REPLACE(UUID(),'-','');    <!--有了这句话就不用自己维护非自增长类型主键了-->
        </selectKey>
        INSERT INTO teacher(t_id,t_name)
        value(
        #{tId},#{tName})
    </insert>


</mapper>

mybatis的配置文件:

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--开启驼峰式自动映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
    <environments default="development">
        <!-- environment表示配置Mybatis的一个具体的环境 -->
        <environment id="development">
            <!-- Mybatis的内置的事务管理器 -->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源 -->
            <dataSource type="POOLED">
                <!-- 建立数据库连接的具体信息 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
                <property name="username" value="root"/>
                <property name="password" value="abc123"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
        <!-- mapper标签:配置一个具体的Mapper映射文件 -->
        <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
        <!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
        <mapper resource="mappers/EmployeeMapper.xml"/>
        <mapper resource="mappers/TeacherMapper.xml"/>
    </mappers>

    

</configuration>

测试类:

复制代码
@Test
    public void test_02() throws IOException {
        //读取外部配置文件
        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");

        //创建sqlSessionFactory
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(ips);

        //获取sqlSession对象
        //openSession自动开启事务,不会自动提交
        //需要sqlSession.commit()才会提交
        SqlSession sqlSession = build.openSession();

        //获取代理对象
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);

        Teacher teacher = new Teacher();
        teacher.settName("哈哈哈");

        //自己维护非自增长型主键
//        String id = UUID.randomUUID().toString().replace("-", "");
//        teacher.settId(id);

        int i = mapper.insertTeacher(teacher);
        System.out.println("i = "+ i);



        //释放资源和提交事务
//        sqlSession.commit();//当是DML语句的时候,必须提交事务
        sqlSession.close();
    }

在实际项目中,非自增主键 = 自己掌控 ID 的生成规则,核心是「先给实体类的主键属性赋值,再执行插入」。

根据项目场景不同,有 3 种最常用的落地方式,我按「项目里最常用 → 次常用」的顺序给你讲:


方案 1:Java 代码手动生成 ID

适用场景 :分布式系统、需要全局唯一 ID、不想依赖数据库函数、追求性能和灵活性。常见 ID 生成方式:UUID、雪花算法(Snowflake)、百度 uid-generator 等。

步骤 1:实体类

复制代码
public class Teacher {
    private String tId;  // 非自增主键,String 类型
    private String tName;

    // getter/setter 省略
}

步骤 2:在业务层(Service)生成 ID 并赋值

复制代码
@Service
public class TeacherService {

    @Autowired
    private TeacherMapper teacherMapper;

    // 插入老师
    public int addTeacher(Teacher teacher) {
        // 1. 生成全局唯一 ID(这里用 UUID 举例,也可以换成雪花算法)
        String uniqueId = java.util.UUID.randomUUID().toString();
        // 2. 把 ID 赋值给实体类的主键属性
        teacher.settId(uniqueId);
        // 3. 调用 Mapper 插入数据库
        return teacherMapper.insertTeacher(teacher);
    }
}

步骤 3:Mapper 接口 + XML(普通插入即可)

接口

复制代码
public interface TeacherMapper {
    int insertTeacher(Teacher teacher);
}

XML

复制代码
<insert id="insertTeacher" parameterType="com.xxx.pojo.Teacher">
    insert into t_teacher (t_id, t_name)
    values (#{tId}, #{tName})
</insert>

执行流程

  1. 前端 / 调用方传入 tName → 封装成 Teacher 对象
  2. Service 层生成 UUID → 赋值给 tId
  3. MyBatis 把 tIdtName 一起插入数据库
  4. 插入后,teacher 对象的 tId 就是刚生成的唯一 ID,可直接用于后续业务(比如关联其他表)

方案 2:数据库函数生成 ID(适合 Oracle / 老项目)

适用场景 :不想在 Java 层处理 ID,依赖数据库内置函数 / 序列生成(比如 Oracle 序列、MySQL UUID ())。核心标签<selectKey> ------ 在插入前 / 后执行一条 SQL 生成 ID,并回填给实体类。

示例 1:MySQL 用 UUID() 生成 ID

XML 配置

复制代码
<insert id="insertTeacher" parameterType="com.xxx.pojo.Teacher">
    <!-- 插入前先执行查询,生成 UUID 并赋值给 tId -->
    <selectKey 
        keyProperty="tId"    <!-- 实体类主键属性 -->
        keyColumn="t_id"     <!-- 数据库主键列名 -->
        resultType="string"  <!-- ID 类型 -->
        order="BEFORE">      <!-- 执行时机:插入前生成 -->
        select uuid()
    </selectKey>

    <!-- 执行插入,此时 tId 已经被赋值 -->
    insert into t_teacher (t_id, t_name)
    values (#{tId}, #{tName})
</insert>

示例 2:Oracle 用序列生成 ID

复制代码
<insert id="insertTeacher" parameterType="com.xxx.pojo.Teacher">
    <selectKey 
        keyProperty="tId" 
        keyColumn="t_id" 
        resultType="string" 
        order="BEFORE">
        -- 调用 Oracle 序列生成下一个 ID
        select SEQ_TEACHER.nextval from dual
    </selectKey>

    insert into t_teacher (t_id, t_name)
    values (#{tId}, #{tName})
</insert>

执行流程

  1. MyBatis 先执行 <selectKey> 里的 SQL → 生成 ID
  2. 把 ID 赋值给 teacher.tId
  3. 再执行 insert 语句 → 把 tIdtName 插入数据库
  4. 插入后,teacher.tId 就是数据库生成的 ID,可直接使用

自定义映射关系和resultMap:

复制代码
<!--
列名和属性名不一致的时候:
方案1:给每个变量取别名
方案2:开启驼峰式映射:mybatis文件中:<setting name="mapUnderscoreToCamelCase" value="true"/>
方案3:resultMap自定义映射 可以映射多层结构(resultType和resultMap二选一)
      resultType按照规则自动映射   按照是否开启驼峰式映射,自己映射属性和列名。
      但是只能映射一层结构,比如说:
      orderId  只能映射 order_id
      orderName 只能映射 order_name
     如果order_item里面还有一个item_id,那么就无法映射出item_id这个属性


-->
<select id="queryById" resultType="teacher" >
    <!--方案1:
    select t_id tId,t_name tName from teacher where t_id = #{tId}
    -->
    <!--方案2:
    select t_id tId,t_name tName from teacher where t_id = #{tId}
    -->
</select>

<!--方案3-->
<!--说明:resultMap标签,自己定义映射规则
        id标识 -> select里resultMap = "这里的标识"
        type  -> 具体的返回值类型 全限定符和别名  | 集合的话只写泛型就可以
-->
<resultMap id="tMap" type="teacher">
    <id column="t_id" property="tId"/>  <!--id是主键的映射关系-->
    <result column="t_name" property="tName"/> <!--result普通列的映射关系-->
</resultMap>
<select id="queryById" resultMap="tMap">
    select * from teacher where t_id = #{tId}
    select t_id tId,t_name tName from teacher where t_id = #{tId}
</select>

Mybatis多表映射:

对一映射:

一对一映射是 MyBatis 中最基础的关联关系,核心场景是:一个对象 A 包含一个另一个对象 B(属性关联) 。比如:一个用户(User)对应一个身份证(IdCard)一个老师(Teacher)对应一个详细信息(TeacherDetail)

接口:

复制代码
public interface OrderMapper {
    //根据id查询订单信息和对应的客户
    Order queryOrderById(Integer id);
}

实体类:

复制代码
public class Customer {
    private Integer customerid;
    private String customerName;

    public Integer getCustomerId() {
        return customerid;
    }

    public void setCustomerId(Integer customerid) {
        this.customerid = customerid;
    }

    public String getCustomerName() {
        return customerName;
    }

    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }

}

public class Order {
    private Integer orderid;
    private String orderName;
    private Integer customerId;

    //对一的关系
    private  Customer customer;


    public Integer getOrderId() {
        return orderid;
    }

    public void setOrderId(Integer orderid) {
        this.orderid = orderid;
    }

    public String getOrderName() {
        return orderName;
    }

    public void setOrderName(String orderName) {
        this.orderName = orderName;
    }

    public Integer getCustomerId() {
        return customerId;
    }

    public void setCustomerId(Integer customerId) {
        this.customerId = customerId;
    }
}

mapper的xml文件:

复制代码
<mapper namespace="com.atguigu.mapper.OrderMapper">
    <resultMap id="orderMap" type="order">
        <!--第一层属性 order对象-->
        <!--order的主键 id标签-->
         <id column="order_id" property="orderId"/>
        <!--普通列-->
        <result column="order_name" property="orderName"/>
        <result column="customer_id" property="customerId"/>

        <!--给对象属性赋值
            property 对象属性名
            javaType 对象类型
        -->
        <association property="customer" javaType="customer">
            <!--customer的主键-->
            <id column="customer_id" property="customerId"/>
            <!--普通列-->
            <result column="customer_name" property="customerName"/>

        </association>

    </resultMap>
    <!--根据id查询订单和订单关联的客户信息-->
    <select id="queryOrderById" resultMap="orderMap">
            SELECT * FROM t_order tor JOIN t_customer tur
        ON  tor.customer_id = tur.customer_id
        WHERE tor.order_id = #{id}
    </select>


</mapper>

mybatis的配置文件:

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--开启驼峰式自动映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>

            <!-- 给实体类起别名 -->
            <package name="com.atguigu.pojo"/>

    </typeAliases>
    <!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
    <environments default="development">
        <!-- environment表示配置Mybatis的一个具体的环境 -->
        <environment id="development">
            <!-- Mybatis的内置的事务管理器 -->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源 -->
            <dataSource type="POOLED">
                <!-- 建立数据库连接的具体信息 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
                <property name="username" value="root"/>
                <property name="password" value="abc123"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
        <!-- mapper标签:配置一个具体的Mapper映射文件 -->
        <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
        <!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
        <mapper resource="mappers/OrderMapper.xml"/>

    </mappers>

    

</configuration>

测试类:

复制代码
@Test
public void test_01() throws IOException {
    //1.读取外部配置
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    //2.获取sqlSessionFactory对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    //3.open sqlSessionFactory 以获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    //4.创建mapper代理对象并调用方法
    OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
    Order order = mapper.queryOrderById(1);
    System.out.println(order);
    System.out.println(order.getCustomerId());
    sqlSession.close();
}

假设业务场景:查询老师信息时,同时把老师的详细信息(如地址、邮箱)一起查出来

1. 数据库表设计

  • t_teacher(老师表):主键 t_id
  • t_teacher_detail(老师详情表):主键 id,外键 t_id 关联老师表

2. 实体类设计

Teacher(主对象)
复制代码
public class Teacher {
    private Integer tId;       // 主键
    private String tName;     // 姓名
    
    // 核心:一对一关联,一个老师对应一个详情
    private TeacherDetail detail; 
    
    // getter/setter
}
TeacherDetail(关联对象)
复制代码
public class TeacherDetail {
    private Integer id;       // 主键
    private String address;   // 地址
    private String email;    // 邮箱
    private Integer tId;      // 关联老师的外键
    
    // getter/setter
}

方式 1:嵌套结果映射(推荐 ✅)

原理 :用 一条 JOIN SQL 把主表和关联表的数据查出来,再通过 resultMap 把字段拆分到两个实体中。

1. 定义 ResultMap(核心)
复制代码
<resultMap id="TeacherDetailMap" type="Teacher">
    <!-- 映射主表字段到 Teacher -->
    <id column="t_id" property="tId"/>
    <result column="t_name" property="tName"/>
    
    <!-- 核心:一对一关联标签 association -->
    <!-- property="detail" 对应 Teacher 里的 detail 属性 -->
    <!-- javaType="TeacherDetail" 指定关联对象的类型 -->
    <association property="detail" javaType="TeacherDetail">
        <!-- column:查询出的列名;property:TeacherDetail 的属性名 -->
        <id column="id" property="id"/>
        <result column="address" property="address"/>
        <result column="email" property="email"/>
        <result column="t_id" property="tId"/>
    </association>
</resultMap>
2. 编写查询 SQL
复制代码
<select id="queryTeacherWithDetail" resultMap="TeacherDetailMap">
    -- 连表查询,把所有字段都查出来
    select t.t_id, t.t_name, d.id, d.address, d.email, d.t_id
    from t_teacher t
    left join t_teacher_detail d on t.t_id = d.t_id
    where t.t_id = #{tId}
</select>
3. 接口
复制代码
Teacher queryTeacherWithDetail(Integer tId);

一对多的查询:

整体上来说和一对一查询差不多,只是标签用的不一样

一对多查询是 MyBatis 最核心的关联查询场景,核心定义:一个主实体(一)对应多个子实体(多),比如:✅ 1 个部门 → 多个员工✅ 1 个用户 → 多个订单✅ 1 个班级 → 多个学生

MyBatis 实现一对多唯一核心标签<collection>(对应一对一用 <association>),且提供两种实现方式

  1. 嵌套结果映射(推荐):连表查询,1 条 SQL,性能最优
  2. 嵌套查询(子查询):多条 SQL,存在 N+1 性能问题(不推荐)

一、前置准备

我们用 部门 (Dept) 员工 (Emp) 经典案例演示:

1. 数据库表

复制代码
-- 部门表(一)
CREATE TABLE dept (
  dept_id INT PRIMARY KEY AUTO_INCREMENT,
  dept_name VARCHAR(20) NOT NULL
);
-- 员工表(多)
CREATE TABLE emp (
  emp_id INT PRIMARY KEY AUTO_INCREMENT,
  emp_name VARCHAR(20) NOT NULL,
  dept_id INT, -- 外键关联部门
  FOREIGN KEY (dept_id) REFERENCES dept(dept_id)
);

2. 实体类

关键 :「一」的实体中,必须包含「多」的集合属性

复制代码
// 部门实体(一)
@Data
public class Dept {
    private Integer deptId;
    private String deptName;
    // 核心:一对多,存储多个员工
    private List<Emp> empList;
}

// 员工实体(多)
@Data
public class Emp {
    private Integer empId;
    private String empName;
    private Integer deptId;
}

二、方式一:嵌套结果映射(✅ 推荐,最优解)

核心原理

  1. 1 条连表 SQL 一次性查询出「部门 + 员工」所有数据
  2. MyBatis 通过 <collection> 自动合并重复的主表数据,将子数据封装为集合
  3. 无 N+1 问题,性能远超子查询

代码实现

1. Mapper 接口
复制代码
public interface DeptMapper {
    // 根据部门ID,查询部门+旗下所有员工
    Dept selectDeptWithEmpByDeptId(Integer deptId);
}
2. Mapper XML 核心配置
复制代码
<?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="com.mapper.DeptMapper">

    <!-- 核心:一对多结果映射 -->
    <resultMap id="DeptWithEmpResultMap" type="com.entity.Dept">
        <!-- 1. 映射部门(主表)字段 -->
        <id column="dept_id" property="deptId"/> <!-- 必须写主键!用于去重 -->
        <result column="dept_name" property="deptName"/>
        
        <!-- 2. 核心:一对多映射 → collection 标签 -->
        <!-- property:实体中集合属性名  ofType:集合泛型类型 -->
        <collection property="empList" ofType="com.entity.Emp">
            <!-- 映射员工(子表)字段 -->
            <id column="emp_id" property="empId"/>
            <result column="emp_name" property="empName"/>
            <result column="dept_id" property="deptId"/>
        </collection>
    </resultMap>

    <!-- 3. 连表查询 SQL -->
    <select id="selectDeptWithEmpByDeptId" resultMap="DeptWithEmpResultMap">
        SELECT d.dept_id, d.dept_name, e.emp_id, e.emp_name
        FROM dept d
        LEFT JOIN emp e ON d.dept_id = e.dept_id
        WHERE d.dept_id = #{deptId}
    </select>

</mapper>

关键必记

  1. <id>**必须写 :主表的主键映射,MyBatis 靠它合并重复的部门数据**(连表查询会返回多条重复部门)
  2. <collection> 两个必填属性:
    • property:实体中集合属性名 (对应 Dept 里的 empList
    • ofType:集合的泛型类型 (对应 Emp
  1. LEFT JOIN:即使部门没有员工,也能查询出部门信息

多表映射优化:

可以去mybatis配置文件里,把自动映射打开

只要把这个的值设置成FULL就会自动映射,不用手写resultMap标签的映射关系了,只是result标签里的不用写,如果是主键(id的标签)的话还是要写的

动态sql语句:

什么是 MyBatis 动态 SQL?

1. 核心定义

动态 SQL = 根据传入的参数条件,自动拼接不同的 SQL 语句

  • 不用手动写繁琐的字符串拼接
  • 不用处理 WHERE 1=1 这种 hack 写法
  • 自动避免 SQL 语法错误、SQL 注入

2. 解决的原生痛点

比如查询员工:前端可能传姓名、部门 ID、性别 ,3 个条件都是可选的。原生 JDBC 拼接 SQL 会非常麻烦,还容易多写 AND/WHERE 导致报错。MyBatis 用 <if>****+ <where> 一行代码解决所有问题。

3. 本次核心标签(90% 场景用这两个)

  • <if>单条件判断,满足就拼 SQL,不满足就不拼
  • <where>智能处理 WHERE 子句 ,自动去掉多余的 AND/OR,无条件时不生成 WHERE

二、核心标签 1:<if> 单条件判断

1. 作用

满足 test 条件 → 拼接标签内的 SQL 片段;不满足 → 直接忽略

2. 语法

复制代码
<!-- test:必填,写判断条件(OGNL表达式) -->
<if test="参数判断条件">
    要拼接的SQL片段
</if>

3. 常用判断规则

|----------|-----------------------------|-----------------|
| 参数类型 | 判断非空写法 | 说明 |
| 字符串 | name != null and name != '' | 既不能为 null,也不能为空 |
| 数字 | deptId != null | 数字只需判断非 null |
| 布尔 | status == true | 布尔值直接判断 |

4. 单独使用 <if> 的坑

复制代码
<!-- 错误示例:单独用if,会出现语法错误 -->
<select id="selectEmp" resultType="com.entity.Emp">
    SELECT * FROM emp
    WHERE
    <if test="empName != null and empName != ''">
        emp_name LIKE CONCAT('%',#{empName},'%')
    </if>
    <if test="deptId != null">
        AND dept_id = #{deptId}
    </if>
</select>

报错场景

  1. 所有条件都不传 → SQL 变成 SELECT * FROM emp WHERE(语法错误)
  2. 只传部门 ID → SQL 变成 SELECT * FROM emp WHERE AND dept_id = ?(多了 AND)

这就是 **<where>**标签诞生的原因


三、核心标签 2:<where> 智能 WHERE 处理器

1. 作用

自动修复 WHERE 子句的所有语法问题,它有 3 个神仙特性:

  1. 内部有条件满足 → 自动生成 WHERE 关键字
  2. 自动剔除开头多余的 AND**/** OR(比如第一个条件就带 AND,直接删掉)
  3. 内部无任何条件 → 直接忽略 WHERE,不生成任何关键字

2. 语法

复制代码
<where>
    <!-- 里面包裹所有 <if> 条件 -->
    <if test="条件1">SQL片段1</if>
    <if test="条件2">SQL片段2</if>
</where>

四、最佳实践:<where> + <if> 组合实战

我们用员工表做完整演示(最贴近实际开发)

1. 实体类 Emp

复制代码
@Data
public class Emp {
    private Integer empId;    // 员工ID
    private String empName;  // 员工姓名
    private Integer deptId;  // 部门ID
    private String gender;   // 性别
}

2. Mapper 接口

复制代码
public interface EmpMapper {
    // 动态条件查询员工:姓名(模糊)、部门ID、性别 都是可选条件
    List<Emp> selectEmpByCondition(Emp emp);
}

3. Mapper XML 核心配置(

复制代码
<?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="com.mapper.EmpMapper">

    <!-- 动态SQL查询:where + if 组合 -->
    <select id="selectEmpByCondition" parameterType="com.entity.Emp" resultType="com.entity.Emp">
        SELECT emp_id, emp_name, dept_id, gender
        FROM emp
        <!-- 核心:where标签包裹所有if条件 -->
        <where>
            <!-- 条件1:姓名非空 → 模糊查询 -->
            <if test="empName != null and empName != ''">
                AND emp_name LIKE CONCAT('%', #{empName}, '%')
            </if>
            <!-- 条件2:部门ID非空 → 精确查询 -->
            <if test="deptId != null">
                AND dept_id = #{deptId}
            </if>
            <!-- 条件3:性别非空 → 精确查询 -->
            <if test="gender != null and gender != ''">
                AND gender = #{gender}
            </if>
        </where>
    </select>

</mapper>

4. 自动拼接效果(MyBatis 智能处理)

|----------------|-----------------------------------------------------------|
| 传入参数 | 最终生成的 SQL |
| 无参数 | SELECT * FROM emp (无 WHERE) |
| 仅传姓名 | SELECT * FROM emp WHERE emp_name LIKE ? |
| 姓名 + 部门 ID | SELECT * FROM emp WHERE emp_name LIKE ? AND dept_id = ? |
| 仅传部门 ID(带 AND) | SELECT * FROM emp WHERE dept_id = ? (自动删 AND) |


五、高频避坑指南(必记)

  1. <if>内的 SQL 片段,开头必须写 AND/ OR交给 <where> 自动处理,不要自己删
  2. 字符串必须判断 **!= null and != ''**只判断 null 会导致空字符串传入,拼接错误条件
  3. 数字 / 包装类(Integer/Long)只需判断 != null基本类型(int)有默认值 0,慎用
  4. <where>只会剔除【开头】的 AND/ OR中间的多余 AND 不会处理,写条件时要规范
  5. 模糊查询推荐用 CONCAT避免手写 % 导致的 SQL 注入风险

六、总结

  1. 动态 SQL :MyBatis 根据参数自动拼接 SQL,解决可选条件查询痛点
  2. <if>:单条件判断,满足就拼 SQL
  3. <where>:智能修复 WHERE 语法,自动删多余 AND/OR、无条件不生成 WHERE
  4. 黄金组合<where> 包裹所有 <if>,90% 的业务查询都用这个写法
  5. 核心优势:代码简洁、无语法错误、防 SQL 注入

为什么sql语句的开头要写and或者or?

先看「不加 AND」的错误场景

假设你这么写(错误写法):

复制代码
<where>
    <if test="empName != null">
        emp_name LIKE #{empName}  <!-- 没加AND -->
    </if>
    <if test="deptId != null">
        dept_id = #{deptId}       <!-- 没加AND -->
    </if>
</where>

如果两个条件都传,拼接的 SQL 是:

复制代码
SELECT * FROM emp WHERE emp_name LIKE ? dept_id = ?

直接报错 !因为两个条件之间没有连接符,SQL 语法不认识。


2. 再看「加了 AND」的正确逻辑

我们给每个条件前面都加 AND正确写法):

xml

复制代码
<where>
    <if test="empName != null">
        AND emp_name LIKE #{empName}  <!-- 开头加AND -->
    </if>
    <if test="deptId != null">
        AND dept_id = #{deptId}       <!-- 开头加AND -->
    </if>
</where>
场景 1:两个条件都传

拼接结果:

复制代码
SELECT * FROM emp WHERE emp_name LIKE ? AND dept_id = ?

第二个 AND 保留,连接两个条件)

场景 2:只传第二个条件

拼接结果:

复制代码
SELECT * FROM emp WHERE dept_id = ?

**<where>**自动删掉了开头多余的 AND

场景 3:都不传

拼接结果:

复制代码
SELECT * FROM emp

无 WHERE,无语法错误

set关键字:

<set> 标签是干嘛的?

核心作用

用于 MyBatis 动态更新 SQL ,智能处理 UPDATE 语句的 SET 子句:

  1. 自动生成 SET 关键字
  2. 自动删除末尾多余的逗号(最核心功能)
  3. 没有要更新的字段时,直接忽略 SET,避免语法报错

先看:不用 <set> 的致命坑(必懂)

假设我们要动态更新员工(只传要改的字段,不传的不更新):

复制代码
<!-- 错误写法:没有 set 标签 -->
<update id="updateEmp">
    UPDATE emp
    SET
    <if test="empName != null and empName != ''">
        emp_name = #{empName},  <!-- 末尾加了逗号 -->
    </if>
    <if test="gender != null and gender != ''">
        gender = #{gender},    <!-- 末尾加了逗号 -->
    </if>
    WHERE emp_id = #{empId}
</update>
报错场景:

只传姓名,最终 SQL 变成:

复制代码
UPDATE emp SET emp_name = ?, WHERE emp_id = ?

SET 最后多了一个逗号,SQL 直接报错。

这就是 <set> 标签要解决的问题!


二、<set> + <if> 正确用法(黄金组合)

核心规则(和 <where> 对应记忆)

|-----------|------------|-----------------------|------------|
| 标签 | 适用 SQL | 拼接规则 | 自动处理功能 |
| <where> | SELECT | 每个 <if> 开头加 AND | 删除开头多余 AND |
| <set> | UPDATE | 每个 <if> 末尾加 逗号 | 删除末尾多余 逗号 |


实战完整代码

1. Mapper 接口

复制代码
// 动态更新员工:只更新传入的非空字段
int updateEmp(Emp emp);

2. Mapper XML(✨ 重点:set + if 写法)

复制代码
<update id="updateEmp" parameterType="com.entity.Emp">
    UPDATE emp
    <!-- 核心:set 标签包裹所有动态更新字段 -->
    <set>
        <!-- 条件1:姓名非空 → 更新,末尾加逗号 -->
        <if test="empName != null and empName != ''">
            emp_name = #{empName},
        </if>
        <!-- 条件2:性别非空 → 更新,末尾加逗号 -->
        <if test="gender != null and gender != ''">
            gender = #{gender},
        </if>
        <!-- 条件3:部门ID非空 → 更新,末尾加逗号 -->
        <if test="deptId != null">
            dept_id = #{deptId},
        </if>
    </set>
    <!-- 必须加 WHERE 条件!否则更新全表 -->
    WHERE emp_id = #{empId}
</update>

自动拼接效果(<set> 智能处理)

|----------|------------------------------------------------------------|
| 传入参数 | 最终生成的 SQL |
| 只传姓名 | UPDATE emp SET emp_name = ? WHERE emp_id = ? |
| 姓名 + 性别 | UPDATE emp SET emp_name = ?, gender = ? WHERE emp_id = ? |
| 无任何更新字段 | UPDATE emp WHERE emp_id = ? (无语法错,不更新数据) |


三、3 个必记避坑点

  1. **<if>**内的字段,末尾必须加 逗号交给 <set> 自动删除最后一个多余逗号
  2. UPDATE 必须加 WHERE 主键条件不加会更新全表数据,生产环境直接事故
  3. 只更新非空字段 这是动态更新的意义:避免把没传的字段覆盖为 null

四、一句话总结(和 where 联动记忆)

  1. 查询用 <where>:包裹 <if>,自动删开头多余 AND
  2. 更新用 <set>:包裹 <if>,自动删末尾多余 逗号
  3. 两个标签都是 MyBatis 的语法保姆,彻底告别动态 SQL 拼接错误

trim标签:

核心结论(先记死)

<trim>万能动态 SQL 标签 👉 <where><set> 都是 <trim>简化专用版 !👉 <trim> 可以完全替代 <where><set>,还能实现更复杂的自定义拼接


一、<trim> 4 个核心属性(必背)

<trim> 就是自定义拼接规则,靠 4 个属性控制:

表格

|-------------------|---------------------------------------------|
| 属性 | 含义(大白话) |
| prefix | 给整个包裹内容添加前缀 (比如加 WHERESET ) |
| prefixOverrides | 去掉内容开头 多余的字符(比如去掉 ANDOR ) |
| suffix | 给整个包裹内容添加后缀(极少用) |
| suffixOverrides | 去掉内容结尾 多余的字符(比如去掉 , 逗号) |


二、实战 1:<trim> 完全替代 <where>

原生 <where> 写法

复制代码
<where>
    <if test="empName != null">
        AND emp_name = #{empName}
    </if>
</where>

等价 <trim> 写法

复制代码
<!-- 前缀加 WHERE,去掉开头多余的 AND/OR -->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
    <if test="empName != null">
        AND emp_name = #{empName}
    </if>
</trim>

<where> 本质就是:

复制代码
<trim prefix="WHERE" prefixOverrides="AND |OR ">

三、实战 2:<trim> 完全替代 <set>

原生 <set> 写法

复制代码
<set>
    <if test="empName != null">
        emp_name = #{empName},
    </if>
</set>

等价 <trim> 写法

复制代码
<!-- 前缀加 SET,去掉结尾多余的 逗号 -->
<trim prefix="SET" suffixOverrides=",">
    <if test="empName != null">
        emp_name = #{empName},
    </if>
</trim>

<set> 本质就是:

复制代码
<trim prefix="SET" suffixOverrides=",">

四、<trim> 真正强大:自定义万能拼接

<where>/<set> 只能固定加前缀、固定去字符<trim> 可以任意自定义,比如:

  1. 同时去掉开头 + 结尾多余字符
  2. 自定义前缀 + 后缀
  3. 适配更复杂的 SQL 场景

示例:自定义复杂拼接

复制代码
<trim 
    prefix="(" 
    suffix=")" 
    prefixOverrides="," 
    suffixOverrides=","
>
    <if test="empName != null">, #{empName}</if>
    <if test="gender != null">, #{gender}</if>
</trim>

👉 自动拼接成 (张三, 男),自动去掉首尾逗号

choose/when/otherwise:

choose/when/otherwise 标签 一看就懂

一句话先定性:✅ <choose>****= Java 的 if...else if...else✅ 作用:互斥多选一,只会执行第一个满足条件的分支<if> 完全相反:

  • <if>:多个条件可以同时生效
  • <choose>:多个条件只能选一个生效

一、完整语法结构

xml

复制代码
<choose>
    <!-- 相当于 else if -->
    <when test="条件A">SQL片段A</when>
    <when test="条件B">SQL片段B</when>
    <!-- 相当于 else 兜底,可选 -->
    <otherwise>默认SQL片段</otherwise>
</choose>

执行规则:

  1. 从上往下判断
  2. 找到第一个成立的 when,执行后直接结束
  3. 全都不成立,走 otherwise

二、实战场景(最常用业务)

需求:查询员工

  1. 如果传了姓名 → 优先按姓名精确查询
  2. 没姓名,但传了部门 ID → 按部门查询
  3. 两个都没传 → 查询所有员工

Mapper XML 完整写法

复制代码
<select id="findEmp" resultType="com.entity.Emp">
    SELECT * FROM emp
    <where>
        <choose>
            <when test="empName != null and empName != ''">
                emp_name = #{empName}
            </when>
            <when test="deptId != null">
                dept_id = #{deptId}
            </when>
            <otherwise>
                1=1
            </otherwise>
        </choose>
    </where>
</select>

三种执行效果

  1. 传姓名 + 部门 ID:只执行姓名那一行(第一个满足条件优先)
  2. 不传姓名,只传部门 ID:执行部门 ID 那一行
  3. 全都不传:走 otherwise 1=1,查询全部

三、和普通 <if> 核心区别(必考)

  1. <if> 组合多个条件同时满足 → 全部拼接生效适合:同时多个筛选条件(姓名 + 部门 + 性别一起查)
  2. <choose> 组合多个条件同时满足 → 只取第一个适合:优先级互斥选择(优先 A,没有再 B)

foreach标签:

<foreach>****= Java 的 for 循环,专门用来遍历 数组 / List 集合,拼接 SQL 开发里最常用三个场景:in 查询、批量删除、批量插入

一、5 个核心属性(必背,超简单)

xml

复制代码
<foreach
    collection="集合参数名"   <!-- 遍历谁,必填 -->
    item="单个元素别名"       <!-- 循环里每一项叫什么 -->
    open="("                 <!-- 循环整体开头拼接符号 -->
    close=")"                <!-- 循环整体结尾拼接符号 -->
    separator=",">           <!-- 每个元素之间用什么分隔 -->
    #{item}
</foreach>

二、最重要:collection 填什么?(新手 90% 踩坑)

  1. 直接传 List 集合collection="list"
  2. 直接传 数组 Arraycollection="array"
  3. 封装到实体类里 → 写属性名(如 userIds)

场景 1:最常用!IN 多条件查询(高频必会)

需求:根据多个员工 ID 批量查询 select * from emp where emp_id in (1,3,5)

1. Mapper 接口

java

运行

复制代码
List<Emp> selectEmpByIds(List<Integer> idList);

2. XML 写法

xml

复制代码
<select id="selectEmpByIds" resultType="Emp">
    SELECT * FROM emp
    <where>
        emp_id IN
        <foreach collection="list" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </where>
</select>

自动拼接效果:emp_id IN (1,2,3) 完美格式无语法错误


场景 2:批量删除

需求:删除多个 ID 的员工

复制代码
int deleteEmpByIds(List<Integer> idList);

<delete id="deleteEmpByIds">
    DELETE FROM emp
    WHERE emp_id IN
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</delete>

场景 3:批量插入(工作常用)

一次性插入多条员工数据

复制代码
int batchInsertEmp(List<Emp> empList);

<insert id="batchInsertEmp">
    INSERT INTO emp(emp_name, dept_id)
    VALUES
    <foreach collection="list" item="emp" separator=",">
        (#{emp.empName}, #{emp.deptId})
    </foreach>
</insert>

自动拼接多条 values 用逗号分隔

sql片段:

SQL 片段 = 把重复写烂的 SQL 代码抽出来封装,以后直接拿来复用,少写代码、少改错!

一、两个核心标签(就记这俩)

  1. <sql id="名字">定义重复代码块(封装)
  2. <include refid="名字"/>引用封装好的片段(调用)

二、最常用场景 1:抽取查询字段(90% 新手最先用)

原来写法(冗余麻烦):多个查询方法,每次都重复写一堆字段:

复制代码
<select id="a">select emp_id,emp_name,gender,dept_id from emp</select>
<select id="b">select emp_id,emp_name,gender,dept_id from emp where ...</select>

用 SQL 片段优化

  1. 先封装公共字段

    <sql id="empCommonColumn"> emp_id, emp_name, gender, dept_id </sql>
  2. 直接引用复用

    <select id="a"> select <include refid="empCommonColumn"/> from emp </select> <select id="b"> select <include refid="empCommonColumn"/> from emp <where>...</where> </select>

三、最常用场景 2:抽取动态条件(配合你学的 if/where)

如果多个接口,查询条件一模一样,直接抽条件片段:

复制代码
<!-- 封装通用动态查询条件 -->
<sql id="empQueryCondition">
    <if test="empName != null and empName != ''">
        and emp_name like concat('%',#{empName},'%')
    </if>
    <if test="deptId != null">
        and dept_id = #{deptId}
    </if>
</sql>

多处直接引用:

复制代码
<select id="list1">
    select * from emp
    <where>
        <include refid="empQueryCondition"/>
    </where>
</select>

<select id="list2">
    select * from emp where status=1
    <where>
        <include refid="empQueryCondition"/>
    </where>
</select>

四、超重要避坑(新手必错)

  1. SQL 片段里不要写 where****关键字 只写纯条件,外面自己套<where>标签
  2. 片段末尾不要乱加逗号和正常 SQL 写法一致就行
  3. 跨 Mapper 文件引用:refid 写全路径 refid="com.mapper.EmpMapper.empCommonColumn"

mapper按包批量扫描:

以前笨办法:一个个配置 Mapper,接口多了累死

复制代码
<!-- 笨办法:一个个写,麻烦死 -->
<mappers>
  <mapper resource="EmpMapper.xml"/>
  <mapper resource="DeptMapper.xml"/>
  <mapper resource="OrderMapper.xml"/>
</mappers>

批量扫描:一行配置,扫整个包

复制代码
<mappers>
  <package name="com.mapper"/>
</mappers>

二、分两种场景(99% 开发用第二种)

场景 1:纯 MyBatis(原生,mybatis-config.xml)

直接在核心配置文件里加一行:

复制代码
<configuration>
  <!-- 其他配置... -->
  <mappers>
    <!-- 核心:批量扫描 → 写你的Mapper接口所在包名 -->
    <package name="com.mapper"/>
  </mappers>
</configuration>

效果:com.mapper 包下所有 Mapper 接口,自动全部生效


场景 2:SpringBoot 整合 MyBatis(工作必用)

最简单:启动类加一个注解,搞定一切

复制代码
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 启动类
@SpringBootApplication
// 👇 这一行就是批量扫描Mapper包
@MapperScan("com.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

三、必须遵守的规则(不然后报错)

你的 Mapper 接口XML 文件 必须对应

  1. Mapper 接口放在:java/com/mapper
  2. Mapper XML 放在:resources/com/mapper

目录结构长这样:

复制代码
src/main
├─ java
│  └─ com
│     └─ mapper
│        ├─ EmpMapper.java   (接口)
│        └─ DeptMapper.java
└─ resources
   └─ com
      └─ mapper
         ├─ EmpMapper.xml    (XML)
         └─ DeptMapper.xml

四、极简总结(记这 2 句就够)

  1. 纯 MyBatis :配置文件写 <package name="包名"/>
  2. SpringBoot :启动类加 @MapperScan("包名")(最常用)

作用:自动扫描包下所有 Mapper 接口,不用手动一个个注册!

分页插件的使用:

一、第一步:导入 Maven 依赖(纯 MyBatis 专用)

不要用 spring-boot-starter,用原生核心依赖

复制代码
<!-- MyBatis 核心 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
</dependency>

<!-- PageHelper 原生版依赖 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.3</version>
</dependency>

二、第二步:核心关键!在 mybatis-config.xml 注册插件

打开全局核心配置文件 mybatis-config.xml,在 <configuration> 里面添加 plugins 标签,手动注册分页插件:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- 1. 全局设置省略 -->

    <!-- ========== 重点:注册 PageHelper 分页插件 ========== -->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 指定数据库方言 mysql/oracle -->
            <property name="helperDialect" value="mysql"/>
            <!-- 开启分页合理化(超推荐) -->
            <property name="reasonable" value="true"/>
            <!-- 自动识别参数 -->
            <property name="supportMethodsArguments" value="true"/>
        </plugin>
    </plugins>

    <!-- 2. 映射器 Mapper扫描省略 -->

</configuration>

配置解释

  1. interceptor:固定写 PageHelper 的拦截器全类名
  2. helperDialect=mysql:告诉插件当前数据库类型,自动拼接 limit
  3. reasonable=true:页码小于 1 自动切第一页,大于总页数自动切最后一页

三、第三步:Mapper 和 XML 完全不变

和普通查询一模一样,不用写 limit

1. Mapper 接口

复制代码
public interface EmpMapper {
    List<Emp> selectAllEmp();
}

2. Mapper XML

复制代码
<select id="selectAllEmp" resultType="Emp">
    select emp_id,emp_name,dept_id from emp
</select>

四、第四步:Java 代码调用(和 Spring 用法完全一样)

获取 SqlSession 之后,按固定模板写:

  1. PageHelper.startPage(页码,每页条数)
  2. 立即执行 Mapper 查询
  3. 封装为 PageInfo

完整测试代码

复制代码
public class TestPage {
    public static void main(String[] args) {
        // 1. 获取SqlSession(纯MyBatis常规写法)
        SqlSession session = MyBatisUtil.getSqlSession();
        EmpMapper mapper = session.getMapper(EmpMapper.class);

        // 2. 开启分页:第1页,每页5条
        PageHelper.startPage(1,5);

        // 3. 立刻执行查询(中间不能写任何代码!)
        List<Emp> empList = mapper.selectAllEmp();

        // 4. 封装分页信息
        PageInfo<Emp> pageInfo = new PageInfo<>(empList);

        // 5. 打印分页结果
        System.out.println("当前页:"+pageInfo.getPageNum());
        System.out.println("总条数:"+pageInfo.getTotal());
        System.out.println("总页数:"+pageInfo.getPages());
        System.out.println("当前页数据:"+pageInfo.getList());

        session.close();
    }
}
相关推荐
421!8 小时前
C 语言学习笔记——11(函数指针与指针函数)
c语言·开发语言·笔记·单片机·学习
承渊政道8 小时前
【优选算法】(实战:栈、队列、优先级队列高频考题通关全解)
数据结构·c++·笔记·学习·算法·leetcode·宽度优先
盐焗西兰花8 小时前
鸿蒙学习实战之路-Share Kit系列(14/17)-手机间碰一碰分享实战
学习·智能手机·harmonyos
arvin_xiaoting8 小时前
OpenClaw学习总结_II_频道系统_6:iMessage集成详解
学习
敲敲了个代码8 小时前
React 那么多状态管理库,到底选哪个?如果非要焊死一个呢?这篇文章解决你的选择困难症
前端·javascript·学习·react.js·前端框架
醇氧8 小时前
【学习】IP地址分类全解析
网络协议·学习·tcp/ip
派大星~课堂8 小时前
【力扣-141. 环形链表 ✨】Python笔记
笔记·leetcode·链表
conlin day8 小时前
Spring AI学习(一)
人工智能·学习·spring