当前位置: 首页 > article >正文

MyBatis持久层框架

第1章 Mybatis框架入门

1.1 Mybatis简介

  • MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

  • 官网地址:https://mybatis.org/mybatis-3/zh_CN/index.html

  • 小结

    • Mybatis是半自动持久化层ORM框架
      • 半自动:Mybatis(研发效率较低,程序执行效率较高)
      • 全自动:Hibernate(研发效率高,程序执行效率低)
    • ORM(Object Relational Mapping):对象关系映射
      • 对象中属性表中字段建立映射关系,好处:操作对象会影响表中的数据

1.2 持久化层框架对比

  • JDBC
    • SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
    • 代码冗长,开发效率低
  • Hibernate 和 JPA
    • 操作简便,开发效率高
    • 程序中的长难复杂 SQL 需要绕过框架
    • 内部自动生成的 SQL,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
    • 反射操作太多,导致数据库性能下降
  • MyBatis
    • 轻量级,性能出色
    • SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
    • 开发效率稍逊于 Hibernate,但是完全能够接收

开发效率:Hibernate > Mybatis > JDBC

运行效率:JDBC > Mybatis > Hibernate

1.3 Mybatis之Helloworld(快速入门)

1.3.1 准备环境

  • 准备数据模型

    CREATE DATABASE `db_mybatis`;
    
    USE `db_mybatis`;
    
    CREATE TABLE `t_emp`(
      emp_id INT AUTO_INCREMENT,
      emp_name CHAR(100),
      emp_salary DOUBLE(10,5),
      PRIMARY KEY(emp_id)
    );
    
    INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33);
    INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jerry",666.66);
    INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("andy",777.77);
    
  • 新建工程:springboot环境下

    在这里插入图片描述

1.3.2 导入依赖

  • springboot版

    <dependencies>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
    
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>3.0.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
  • 纯享版(了解)

    <dependencies>
            <!-- mybatis依赖 -->
          <dependency>
              <groupId>org.mybatis</groupId>
              <artifactId>mybatis</artifactId>
              <version>3.5.11</version>
          </dependency>
    
          <!-- MySQL驱动 mybatis底层依赖jdbc驱动实现,本次不需要导入连接池,mybatis自带! -->
          <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>8.0.25</version>
          </dependency>
    
          <!--junit5测试-->
          <dependency>
              <groupId>org.junit.jupiter</groupId>
              <artifactId>junit-jupiter-api</artifactId>
              <version>5.3.1</version>
          </dependency>
    </dependencies>
    

1.3.3 配置文件

spring.application.name=day18_myabtis_s2
#数据库连接池配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db_mybatis
spring.datasource.username=root
spring.datasource.password=root
#mybatis配置
#设置映射文件位置
mybatis.mapper-locations=classpath:mappers/*.xml    
#开启下划线转驼峰(自动映射)
mybatis.configuration.map-underscore-to-camel-case=true
#设置别名包(为当前包所有对象设置别名:类名(忽略大小写))
mybatis.type-aliases-package=com.atguigu.pojo
#添加日志信息
logging.level.com.atguigu.mapper=debug

1.3.4 Mapper接口

Mybatis中不用为Mapper接口设置实现类,框架会自动为Mapper生成代理对象

注意:需要在接口上添加@Mapper注解,才会生成代理对象

package com.atguigu.mapper;

import com.atguigu.pojo.Employee;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;


//@Repository
@Mapper // 表示这是一个mybatis的mapper接口(springboot底层会为EmployeeMapper接口创建代理类)
public interface EmployeeMapper {

    /**
     * 查询所有员工信息
    */
    List<Employee> selectAllEmps();

}

1.3.5 Mapper映射文件

Mapper映射文件中需要注意以下特点:

  • 映射文件名称与接口名称一致
  • 映射文件中标签中namespace与接口的全类名一致
  • 映射文件中id属于与接口中方法名一致
  • 方法返回值和resultType属性值一致
<?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.atguigu.mapper.EmployeeMapper">
    <select id="selectAllEmps" resultType="employee">
        SELECT
            e_id,
            e_name,
            salary,
            address,
            d_id
        FROM
            t_emp
    </select>
</mapper>

1.3.6 测试

package com.atguigu;

import com.atguigu.mapper.EmployeeMapper;
import com.atguigu.pojo.Employee;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class Day18MyabtisS2ApplicationTests {

    @Autowired
    private EmployeeMapper employeeMapper;

    @Test
    void contextLoads() {
        List<Employee> employees = employeeMapper.selectAllEmps();
        for (Employee employee : employees) {
            System.out.println("employee = " + employee);
        }
    }

}

1.4 Mybatis映射文件详解

官网地址:https://mybatis.org/mybatis-3/zh_CN/sqlmap-xml.html

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

SQL 映射文件只有很少的几个顶级元素,常用子标签如下:

  • insert标签:定义添加SQL语句
  • update标签:定义修改SQL语句
  • delete标签:定义删除SQL语句
  • select标签:定义查询SQL语句
  • resultMap:自定义映射(映射表中字段与类中属性关系)

第2章 Mybatis数据输入

2.1 单个普通参数

  • Mybatis中单个普通参数,是可以任意入参(参数名)

2.2 多个普通参数

Mybatis中传递多个普通参数时,底层会封装Map,Map的key是**[参数名列表或param1,param2,param…**](springboot整合mybatis)

Mybatis原生框架,封装底层Map的key是[args0,args1…或param1,param2…]

  • 案例代码

    /**
     * 测试多个普通参数
    */
    Employee selectEmpByProParam(int eId, String eName);
    
    <select id="selectEmpByProParam" resultType="employee">
        SELECT
            e_id,
            e_name,
            salary,
            address,
            d_id
        FROM
            t_emp
        where
            e_id = #{eId}
        and
            e_name = #{param2}
    

2.3 命名参数

Mybatis中使用@Param注解为参数命名,底层会封装Map,Map的key是:[命名参数名列表或param1,param2…]

  • 案例代码

     /**
     * 测试多个普通参数
    */
    Employee selectEmpByParam(@Param("empId") int eId, @Param("empName") String eName);
    
    <select id="selectEmpByParam" resultType="employee">
        SELECT
            e_id,
            e_name,
            salary,
            address,
            d_id
        FROM
            t_emp
        where
            e_id = #{empId}
        and
            e_name = #{param2}
    </select>
    

2.4 POJO入参

Mybatis中使用POJO入参时,参数名就是属性名,且必须getXXX()方法

  • 案例代码

    /**
     * 添加员工信息
    */
    void insertEmployee(Employee employee);
    
    <insert id="insertEmployee">
        insert into
            t_emp(e_name,salary,address,d_id)
        values
            (#{eName},#{salary},#{address},#{dId})
    </insert>
    

2.5 Map入参

Mybatis中Map入参时,Map的key与参数名称一致(如不一致,不会报错,但无法成功入参)

  • 案例代码

    /**
     * 测试Map参数
    */
    List<Employee> selectEmpByMap(Map<String,Object> map);
    
    <select id="selectEmpByMap" resultType="employee">
        SELECT
            e_id,
            e_name,
            salary,
            address,
            d_id
        FROM
            t_emp
        where
            e_id = #{eId}
        and
            e_name = #{eName}
    </select>
    
    package com.atguigu;
    
    import com.atguigu.mapper.EmployeeParamMapper;
    import com.atguigu.pojo.Employee;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     */
    //@SpringBootTest(classes = Day18MyabtisS2Application.class)
    @SpringBootTest
    public class TestEmployeeParamMapper {
    
        @Autowired
        private EmployeeParamMapper employeeParamMapper;
    
        @Test
        public void testSelectEmpByTableName() {
            //测试单个普通参数
    //        Employee employee = employeeParamMapper.selectEmpBySingleParam(6);
    //        System.out.println("employee = " + employee);
    
            //测试多个普通参数
    //        Employee employee = employeeParamMapper.selectEmpByProParam(6, "zs");
    //        System.out.println("employee = " + employee);
    
            //测试@Param()
    //        Employee employee = employeeParamMapper.selectEmpByParam(6, "zs");
    //        System.out.println("employee = " + employee);
    
            //测试Pojo入参
    //        employeeParamMapper.insertEmployee(new Employee(null, "wangwu", 15000, "bj", 2));
    
            //测试Map入参
            Map<String, Object> map = new HashMap<>();
            map.put("eId",6);
            map.put("eName","zs");
            List<Employee> employees = employeeParamMapper.selectEmpByMap(map);
            for (Employee employee : employees) {
                System.out.println("employee = " + employee);
            }
    
    
        }
    
    }
    

2.6 #与$区别(面试题)

2.6.1 #与$底层区别

#{}底层使用PreparedStatement对象,使用?占位符入参,防止SQL注入安全隐患,相对安全

${}底层使用Statement对象,使用字符串拼接方式入参,未防止SQL注入,存在安全隐患,相对不安全

2.6.2 #与$使用场景区别

#{}占位符使用位置:?位置

  • select 列名列表 from 表名列名 where 字段=? group by 分组列名 having 字段=? order by 排序列名 limit ?,?

KaTeX parse error: Expected 'EOF', got '#' at position 8: {}使用位置:#̲{}占位符解决不了的位置,使用{}

  • 案例

    在这里插入图片描述

第3章 Mybatis数据输出

3.1 返回单个字面量

Mybatis返回单个字面量数值时,在映射文件中resultType属性直接使用字面量类型或对应别名即可

  • Mybatis底层默认自带别名如下:

    别名映射的类型
    _bytebyte
    _char (since 3.5.10)char
    _character (since 3.5.10)char
    _longlong
    _shortshort
    _intint
    _integerint
    _doubledouble
    _floatfloat
    _booleanboolean
    stringString
    byteByte
    char (since 3.5.10)Character
    character (since 3.5.10)Character
    longLong
    shortShort
    intInteger
    integerInteger
    doubleDouble
    floatFloat
    booleanBoolean
    dateDate
    decimalBigDecimal
    bigdecimalBigDecimal
    bigintegerBigInteger
    objectObject
    date[]Date[]
    decimal[]BigDecimal[]
    bigdecimal[]BigDecimal[]
    biginteger[]BigInteger[]
    object[]Object[]
    mapMap
    hashmapHashMap
    listList
    arraylistArrayList
    collectionCollection
    iteratorIterator
  • 案例代码

    /**
     * 查询所有员工数量
    */
    Integer selectEmpCount();
    
    <select id="selectEmpCount" resultType="int">
        select
            count(1)
        from
            t_emp
    </select>
    

3.2 返回单个POJO

resultType作用:期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。

  • 案例代码

    /**
     * 测试返回单个POJO
    */
    Employee selectEmpByeId(int eId);
    
    <select id="selectEmpByeId" resultType="employee">
        select
            e_id,
            e_name,
            salary,
            address,
            d_id
        from
            t_emp
        where
            e_id = #{eId}
    </select>
    

3.3 返回List类型

上文提到:如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。

  • 案例代码

    /**
     * 查询所有员工信息(测试返回多个POJO-List)
    */
    List<Employee> selectAllEmp();
    
    <select id="selectAllEmp" resultType="employee">
        select
            e_id,
            e_name,
            salary,
            address,
            d_id
        from
            t_emp
    </select>
    

3.4 返回Map类型

适用于SQL查询返回的各个字段综合起来并不与任何一个现有的实体类对应,所以不能直接封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。

在 MyBatis 中,@MapKey 注解用于指定当查询结果被转换为 Map 类型时,用哪个字段作为键。当你希望将查询的结果集映射到一个 Map 中,并且这个 Map 的键是结果集中某一列的值时,你可以使用 @MapKey 注解。

具体来说,当你有一个方法返回类型为 Map<K,V> 时,MyBatis 默认会创建一个 Map<Integer, V>,其中键是自动生成的整数索引,而值则是你的对象。如果你想要改变这种行为,让 Map 的键成为结果集中某个特定字段的值,你就可以使用 @MapKey 来指明这一点。

  • 案例代码

    /**
     * 查询所有员工信息(测试返回多个POJO-Map)
    */
    @MapKey("eId")
    Map<Integer,Employee> selectAllEmpRetrunMap();
    
    <select id="selectAllEmpRetrunMap" resultType="employee">
        select
            e_id,
            e_name,
            salary,
            address,
            d_id
        from
            t_emp
    </select>
    

3.5 返回注解自增数值

3.5.1 返回自增长类型主键

  • Mapper接口中相应方法

    int insertEmployee(Employee employee);
    
  • 映射文件中相应SQL

    <!-- int insertEmployee(Employee employee); -->
    <!-- useGeneratedKeys属性字面意思就是“使用生成的主键” -->
    <!-- keyProperty属性可以指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性 -->
    <insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">
      insert into t_emp(emp_name,emp_salary)
      values(#{empName},#{empSalary})
    </insert>
    
  • 单元测试代码

    @Test
    public void testSaveEmp() {
      EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
      Employee employee = new Employee();
      employee.setEmpName("john");
      employee.setEmpSalary(666.66);
      employeeMapper.insertEmployee(employee);
      System.out.println("employee.getEmpId() = " + employee.getEmpId());
    }
    

3.5.2 返回非自增长类型主键

对于不支持自增型主键的数据库(例如 Oracle)或者字符串类型主键,则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用!

  • 使用 selectKey 帮助插入UUID作为字符串类型主键示例:

    <insert id="insertUser" parameterType="User">
        <selectKey keyProperty="id" resultType="java.lang.String"
            order="BEFORE">
            SELECT UUID() as id
        </selectKey>
        INSERT INTO user (id, username, password) 
        VALUES (
            #{id},
            #{username},
            #{password}
        )
    </insert>
    

    在上例中,我们定义了一个 insertUser 的插入语句来将 User 对象插入到 user 表中。我们使用 selectKey 来查询 UUID 并设置到 id 字段中。

    通过 keyProperty 属性来指定查询到的 UUID 赋值给对象中的 id 属性,而 resultType 属性指定了 UUID 的类型为 java.lang.String

    需要注意的是,我们将 selectKey 放在了插入语句的前面,这是因为 MySQL 在 insert 语句中只支持一个 select 子句,而 selectKey 中查询 UUID 的语句就是一个 select 子句,因此我们需要将其放在前面。

    最后,在将 User 对象插入到 user 表中时,我们直接使用对象中的 id 属性来插入主键值。

    使用这种方式,我们可以方便地插入 UUID 作为字符串类型主键。当然,还有其他插入方式可以使用,如使用Java代码生成UUID并在类中显式设置值等。需要根据具体应用场景和需求选择合适的插入方式。

3.6 mapperXML映射文件总结

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

select标签:

MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 元素是非常简单:

<select id="selectPerson" 
resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select>

这个语句名为 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。

注意参数符号:#{id}

MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:

// 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

select 元素允许你配置很多属性来配置每条语句的行为细节:

属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
resultType期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。

insert, update 和 delete标签:

数据变更语句 insert,update 和 delete 的实现非常接近:

<insert
  id="insertAuthor"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="updateAuthor"
  statementType="PREPARED"
  timeout="20">

<delete
  id="deleteAuthor"
  statementType="PREPARED"
  timeout="20">
属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn(仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。

3.7 CRUD强化练习

  1. 准备数据库数据

    首先,我们需要准备一张名为 user 的表。该表包含字段 id(主键)、username、password。创建SQL如下:

    CREATE TABLE `user` (
      `id` INT(11) NOT NULL AUTO_INCREMENT,
      `username` VARCHAR(50) NOT NULL,
      `password` VARCHAR(50) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    
  2. 实体类准备

    接下来,我们需要定义一个实体类 User,来对应 user 表的一行数据。

    @Data
    public class User {
      private Integer id;
      private String username;
      private String password;
    }
    
  3. Mapper接口定义

    定义一个 Mapper 接口 UserMapper,并在其中添加 user 表的增、删、改、查方法。

    public interface UserMapper {
      
      int insert(User user);
    
      int update(User user);
    
      int delete(Integer id);
    
      User selectById(Integer id);
    
      List<User> selectAll();
    }
    
  4. MapperXML编写

    在 resources /mappers目录下创建一个名为 UserMapper.xml 的 XML 文件,包含与 Mapper 接口中相同的五个 SQL 语句,并在其中,将查询结果映射到 User 实体中。

    <?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.UserMapper">
      <!-- 定义一个插入语句,并获取主键值 -->
      <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user(username, password)
                    VALUES(#{username}, #{password})
      </insert>
      
      <update id="update">
        UPDATE user SET username=#{username}, password=#{password}
        WHERE id=#{id}
      </update>
      
      <delete id="delete">
        DELETE FROM user WHERE id=#{id}
      </delete>
      <!-- resultType使用user别名,稍后需要配置!-->
      <select id="selectById" resultType="user">
        SELECT id, username, password FROM user WHERE id=#{id}
      </select>
      
      <!-- resultType返回值类型为集合,所以只写范型即可! -->
      <select id="selectAll" resultType="user">
        SELECT id, username, password FROM user
      </select>
      
    </mapper>
    
    
  5. MyBatis配置文件

    位置:resources: mybatis-config.xml

    <?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="mapUnderscoreToCamelCase" value="true"/>
            <!-- 开启logback日志输出-->
            <setting name="logImpl" value="SLF4J"/>
        </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="root"/>
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
            <!-- mapper标签:配置一个具体的Mapper映射文件 -->
            <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
            <!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
            <mapper resource="mappers/UserMapper.xml"/>
        </mappers>
    
    </configuration>
    
  6. 效果测试

    package com.atguigu.test;
    
    import com.atguigu.mapper.UserMapper;
    import com.atguigu.pojo.User;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    
    import java.io.IOException;
    import java.util.List;
    
    /**
     * projectName: com.atguigu.test
     */
    public class MyBatisTest {
    
        private SqlSession session;
        // junit会在每一个@Test方法前执行@BeforeEach方法
    
        @BeforeEach
        public void init() throws IOException {
            session = new SqlSessionFactoryBuilder()
                    .build(
                            Resources.getResourceAsStream("mybatis-config.xml"))
                    .openSession();
        }
    
        @Test
        public void createTest() {
            User user = new User();
            user.setUsername("admin");
            user.setPassword("123456");
            UserMapper userMapper = session.getMapper(UserMapper.class);
            userMapper.insert(user);
            System.out.println(user);
        }
    
        @Test
        public void updateTest() {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            User user = userMapper.selectById(1);
            user.setUsername("root");
            user.setPassword("111111");
            userMapper.update(user);
            user = userMapper.selectById(1);
            System.out.println(user);
        }
    
        @Test
        public void deleteTest() {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            userMapper.delete(1);
            User user = userMapper.selectById(1);
            System.out.println("user = " + user);
        }
    
        @Test
        public void selectByIdTest() {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            User user = userMapper.selectById(1);
            System.out.println("user = " + user);
        }
    
        @Test
        public void selectAllTest() {
            UserMapper userMapper = session.getMapper(UserMapper.class);
            List<User> userList = userMapper.selectAll();
            System.out.println("userList = " + userList);
        }
    
        // junit会在每一个@Test方法后执行@@AfterEach方法
        @AfterEach
        public void clear() {
            session.commit();
            session.close();
        }
    }
    
    

第4章 resultType与resultMap

4.1 多表&实体类映射关系

4.1.1 表与表之间的映射关系

  • 一对一

    夫妻关系,人和身份证号

  • 一对多

    部门对员工,用户和用户的订单,锁和钥匙

  • 多对多

    老师和学生,公交车与路人

    • 案例代码

      在这里插入图片描述

4.1.2 实体类的映射关系

  • 对一

    • 用户订单对用户

      public class Customer {
      
        private Integer customerId;
        private String customerName;
      
      }
      
      public class Order {
      
        private Integer orderId;
        private String orderName;
        private Customer customer;// 体现的是对一的关系
      
      }  
      
    • 员工对部门

      package com.atguigu.pojo;
      
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      
      
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class Employee {
      
          private Integer eId;
          private String eName;
          private Integer salary;
          private String address;
          //private Integer dId;
          private Department dept;
      
      }
      
  • 对多

    • 用户对用户订单

      public class Customer {
      
        private Integer customerId;
        private String customerName;
        private List<Order> orderList;// 体现的是对多的关系
      }
      
      
    • 部门对员工

      package com.atguigu.pojo;
      
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      
      import java.util.List;
      
      
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class Department {
      
          private Integer dId;
          private String dName;
          private String description;
          //对应员工信息
          private List<Employee> employeeList;
      
      }
      

4.2 resultType与resultMap应用场景

在 MyBatis 中,resultTyperesultMap 是两种不同的方式,用于描述如何将 SQL 查询结果映射到 Java 对象。它们有各自的特点和使用场景。具体区别如下:

  • resultType

    • 定义:resultType 用于指定查询结果应映射到的 Java 类型。它可以是基本类型、JavaBean、POJO 或 Map 等。
    • 特点
      • 简单直接,适合于简单的映射关系,即 SQL 查询的结果集列名与 Java 对象的属性名一一对应。
      • 不需要额外的配置,只需要提供目标类型的全限定类名或别名。
    • 适用场景
      • 当查询语句返回的字段可以直接映射到 Java 类的属性时,且不需要处理复杂的映射关系(如嵌套对象、多对一、一对多等)。
      • 所以resultType一般用于单表简单查询
  • resultMap

    • 定义:resultMap 是 MyBatis 中最强大和灵活的映射机制。它允许你详细地定义如何从数据库结果集中映射数据到应用程序中的对象。
    • 特点
      • 提供了更精细的控制,可以处理复杂的数据结构映射,比如关联映射(一对一、一对多)、嵌套结果、鉴别器等。
      • 需要在 XML 映射文件中定义 <resultMap> 元素,并为每个复杂的映射规则指定具体的映射逻辑。
    • 适用场景
      • 当存在复杂的映射需求时,例如表结构和对象模型不一致;或者需要进行联合查询并将结果映射到多个对象;或者是处理继承结构等情况。
      • 所以resultMap一般用于多表复杂查询

4.3 实现对一映射

4.3.1 级联方式实现对一映射

  • 需求说明及POJO准备

    查询员工信息及员工所属部门信息(部门id部门名称)

  • POJO准备

    package com.atguigu.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Employee {
    
        private Integer eId;
        private String eName;
        private Integer salary;
        private String address;
        //private Integer dId;
        private Department dept;
    
    }
    
  • EmployeeMapper接口

    /**
     */
    @Mapper
    public interface EmployeeMapper {
    
        /**
         * 查询员工信息及员工所属部门信息(部门id部门名称)
        */
        List<Employee> selectAllEmpAndDept();
        
    }
    
  • EmployeeMapper.xml映射文件

    <resultMap id="empAndDeptResultMap" type="employee">
        <!--定义员工表中字段与员工实体类属性的映射关系-->
        <id column="e_id" property="eId"/>
        <result column="e_name" property="eName"/>
        <result column="salary" property="salary"/>
        <result column="address" property="address"/>
        <!--定义部门表中字段与部门实体类属性的映射关系-->
        <result column="did" property="dept.dId" />
        <result column="dname" property="dept.dName"/>
        <result column="description" property="dept.description"/>
    </resultMap>
    
    <select id="selectAllEmpAndDept" resultMap="empAndDeptResultMap">
        select
            e.e_id,
            e.e_name,
            e.salary,
            e.address,
            d.did,
            d.dname,
            d.description
        from
            t_emp e left join t_dept d
        on
            e.d_id=d.did
    </select>
    
  • 测试

    @SpringBootTest(classes = Day19Mybatis1Application.class)
    class Day19Mybatis1ApplicationTests {
    
        @Autowired
        private EmployeeMapper employeeMapper;
    
        /**
         * 测试对一映射
        */
        @Test
        void contextLoads() {
            //级联映射
            employeeMapper.selectAllEmpAndDept().forEach(System.out::println);
        }
    }
    

4.3.2 association实现对一映射

  • 需求说明及POJO准备

    查询员工信息及员工所属部门信息(部门id部门名称)association

    POJO同上

  • EmployeeMapper接口

    /**
     * 查询员工信息及员工所属部门信息(部门id部门名称)association
    */
    List<Employee> selectAllEmpAndDeptAssociation();
    
  • EmployeeMapper.xml映射文件

    <resultMap id="empAndDeptAssociation" type="employee">
        <!--定义员工表中字段与员工实体类属性的映射关系-->
        <id column="e_id" property="eId"/>
        <result column="e_name" property="eName"/>
        <result column="salary" property="salary"/>
        <result column="address" property="address"/>
        <!--association(对一)定义部门表中字段与部门实体类属性的映射关系-->
        <association property="dept" javaType="department">
            <id column="did" property="dId"/>
            <result column="dname" property="dName"/>
            <result column="description" property="description"/>
        </association>
    </resultMap>
    <select id="selectAllEmpAndDeptAssociation" resultMap="empAndDeptAssociation">
        select
            e.e_id,
            e.e_name,
            e.salary,
            e.address,
            d.did,
            d.dname,
            d.description
        from
            t_emp e left join t_dept d
        on
            e.d_id=d.did
    </select>
    
  • 测试(略)

4.4 实现对多映射

  • 需求说明POJO准备

    通过部门id获取部门信息及部门对应员工信息

    package com.atguigu.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.List;
    
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Department {
    
        private Integer dId;
        private String dName;
        private String description;
        //对应员工信息
        private List<Employee> employeeList;
    
    }
    
  • DepartmentMapper接口

    /**
     */
    @Mapper
    public interface DepartmentMapper {
    
        /**
         * 通过部门id获取部门信息及部门对应员工信息
        */
        Department selectDeptAndEmpByDeptId(Integer deptId);
        
    }
    
  • DepartmentMapper.xml映射文件

    <resultMap id="deptAndEmpResultMap" type="department">
        <id column="did" property="dId"/>
        <result column="dname" property="dName"/>
        <collection property="employeeList" ofType="employee">
            <id column="e_id" property="eId"/>
            <result column="e_name" property="eName"/>
            <result column="salary" property="salary"/>
            <result column="address" property="address"/>
        </collection>
    </resultMap>
    <select id="selectDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">
       SELECT
            d.did,
            d.dname,
            e.e_id,
            e.e_name,
            e.salary,
            e.address
        FROM
            t_dept d
        LEFT JOIN
            t_emp e
        ON
            d.did = e.d_id
        WHERE
            d.did = #{deptId}
    </select>
    
  • 测试

    /**
     * 测试对多映射
    */
    @Test
    void testToMore() {
        Department department = departmentMapper.selectDeptAndEmpByDeptId(1);
        System.out.println("department = " + department);
    }
    

4.5 resultMap小结

关联关系配置项关键词所在配置文件和具体位置
对一association标签/javaType属性Mapper配置文件中的resultMap标签内
对多collection标签/ofType属性Mapper配置文件中的resultMap标签内
对一分步association标签/select属性/column属性Mapper配置文件中的resultMap标签内
对多分步collection标签/select属性/column属性Mapper配置文件中的resultMap标签内
延迟加载 3.4.1版本前lazyLoadingEnabled设置为true aggressiveLazyLoading设置为falseMybatis全局配置文件中的settings标签内
延迟加载 3.4.1版本后lazyLoadingEnabled设置为trueMybatis全局配置文件中的settings标签内
  • resultMap属性

    • id:定义当前resultMap的唯一标识
    • type:定义[目标方法的返回值]类型(全类名|别名)
  • resultMap子标签

    • :定义主键字段与类中属性的映射关系
    • :定义非主键字段与类中属性的映射关系
      • column:定义表中的字段名称
      • property:定义类中的属性名称
    • :定义(对一)表中的字段与类中的属性映射关系
      • property属性:定义对一属性名称
      • javaType属性:定义对一属性类型全类名|别名)
    • :定义(对多)表中的字段与类中的属性映射关系
      • property属性:定义对多属性名称
      • ofType属性:定义对多属性类型全类名|别名)
  • 设置自动映射

    #设置自动映射full(selectAllEmpAndDeptAssociation)
    mybatis.configuration.auto-mapping-behavior=full
    

4.6 实现分步查询

4.6.1 分步查询概念

在 MyBatis 中,分步查询(Step-by-step Query 或 Stepwise Query)通常与关联对象的懒加载(Lazy Loading)或延迟加载共同使用。这种机制允许你在获取主对象时并不立即加载与之关联的对象,而是当真正需要访问这些关联对象的数据时才进行加载。这可以显著提高性能,尤其是在处理大型数据集或复杂对象图时。

分步查询的意义

  1. 性能优化:并非所有情况下都需要加载所有的关联对象。例如,在一个用户-订单系统中,你可能只需要展示用户的某些基本信息,而不需要立刻加载该用户的所有订单信息。通过分步查询,你可以避免不必要的数据库查询,从而节省资源和时间。
  2. 减少内存占用:对于大规模的数据集合,一次性加载所有相关联的数据可能会导致大量的内存消耗。分步查询可以让应用程序只在需要的时候才加载特定的数据,从而减少了内存的使用。
  3. 按需加载:能够根据业务逻辑的需求来决定何时以及如何加载关联对象,提供更大的灵活性。

分步查询的本质

  • 将复杂的多表连接查询,改为单表的分步查询

分步查询的实现方式

  • 在service层实现:在service中分步调用mapper接口即可
  • 在mapper层实现:在mapper层实现分步查询,可以降低service层代码复杂度

4.6.2 对一分步查询

  • 需求说明

    通过员工eid获取员工信息及员工的部门信息(分步查询实现)

    1. 通过eid 查询员工信息(d_id)
    2. 通过d_id 查询部门信息
  • 相关代码

    • EmployeeMapper

      /**
       * 通过员工id获取员工信息及员工的部门信息(测试分步查询)
           可能效率:select * from t_emp e left join t_dept d on e.dept_id = d.id where e.id = #{eId}
           提高效率
              1. 通过eid 查询员工信息(d_id)
              2. 通过d_id 查询部门信息
      */
      Employee selectEmpAndDeptByeIdStep(Integer eId);
      
    • EmployeeMapper.xml

      <!-- 分步查询
              1. 通过eid 查询员工信息(d_id)
              2. 通过d_id 查询部门信息
      -->
      <resultMap id="empAndDempStepResultMap" type="employee">
          <!--定义员工表中字段与员工实体类属性的映射关系-->
          <id column="e_id" property="eId"/>
          <result column="e_name" property="eName"/>
          <result column="salary" property="salary"/>
          <result column="address" property="address"/>
          <!--分步查询-->
          <association property="dept"
                       javaType="department"
                       select="com.atguigu.mapper.DepartmentMapper.selectDeptByDeptId"
                       column="d_id"/>
      </resultMap>
      <select id="selectEmpAndDeptByeIdStep" resultMap="empAndDempStepResultMap">
          select
              e_id,
              e_name,
              salary,
              address,
              d_id
          from
              t_emp
          where
              e_id = #{eId}
      </select>
      
    • DepartmentMapper

      /**
       * 通过部门id获取部门信息
      */
      Department selectDeptByDeptId(Integer deptId);
      
    • DepartmentMapper.xml

      <select id="selectDeptByDeptId" resultType="department">
          SELECT
              did,
              dname
          FROM
              t_dept
          WHERE
              did = #{deptId}
      </select>
      
    • 关联关系

      在这里插入图片描述

4.6.3 对多分步查询

  • 需求说明

    通过部门id获取部门信息,及部门对应的员工信息(分步实现)

    1. 通过部门id查询部门信息
    2. 通过部门id查询部门对应的员工信息
  • 相关代码

    • DepartmentMapper

      /**
       * 对多分步查询
       * 通过部门id获取部门信息,及部门对应的员工信息
       *  1. 通过部门id查询部门信息
       *  2. 通过部门id查询部门对应的员工信息
      */
      Department selectDeptAndEmpByDeptIdStep(Integer eId);
      
    • DepartmentMapper.xml

      <!--    测试分步查询(对多)
                  1. 通过部门id查询部门信息
                  2. 通过部门id查询部门对应的员工信息
      -->
          <resultMap id="deptAndEmpStepRM" type="department">
              <id column="did" property="dId"/>
              <result column="dname" property="dName"/>
              <!--        定义对多的分步查询-->
              <collection property="employeeList"
                          ofType="employee"
                          select="com.atguigu.mapper.EmployeeMapper.selectEmpByDeptId"
                          column="did"
              ></collection>
          </resultMap>
          <select id="selectDeptAndEmpByDeptIdStep" resultMap="deptAndEmpStepRM">
              SELECT
                  did,
                  dname
              FROM
                  t_dept
              WHERE
                  did = #{deptId}
          </select>
      
    • EmployeeMapper

      /**
       * 通过部门id获取员工信息
      */
      List<Employee> selectEmpByDeptId(int deptId);
      
    • EmployeeMapper.xml

      <select id="selectEmpByDeptId" resultType="employee">
          select
              e_id,
              e_name,
              salary,
              address,
              d_id
          from
              t_emp
          where
              d_id = #{deptId}
      </select>
      

4.7 实现延迟加载(懒加载)

4.7.1 开启延迟加载

在这里插入图片描述

  • 配置文件application.properties或application.yml

    #开启延迟加载(懒加载)
    mybatis.configuration.lazy-loading-enabled=true
    mybatis.configuration.aggressive-lazy-loading=false
    

4.7.2 测试延迟加载

@Test
void testMoreStepSelect() {
    //测试对一分步查询
    Employee employee = employeeMapper.selectEmpAndDeptByeIdStep(2);
    String eName = employee.getEName();
    System.out.println("eName = " + eName);
    System.out.println("==================");
    System.out.println(" dept:"+ employee.getDept());

}

第5章 Mybatis动态SQL

5.1 Mybatis动态SQL概述

MyBatis 的动态 SQL 是指在编写 SQL 语句时,可以根据不同的条件动态地构建 SQL 语句。这使得你可以创建灵活且可重用的查询逻辑,而不需要为每个可能的查询条件都写一个独立的 SQL 语句。MyBatis 提供了多种标签来实现这一功能,包括 <if><choose>(类似于 Java 的 switch)、<when><otherwise><trim><where><set><foreach> 等。

Mybatis动态SQL中应用到OGNL语言

  • OGNL(Object-Graph Navigation Language)是一种强大的表达式语言,最初用于 WebWork 和 XWork 框架中,后来被 Apache Struts2 广泛采用。MyBatis 也使用 OGNL 来处理动态 SQL 中的条件判断和属性访问。OGNL 允许你通过简洁的语法访问和操作 Java 对象图中的数据。

  • 表达式语法

    OGNL 的表达式可以用来访问对象的属性、调用方法、获取数组或列表的元素等。它的基本语法非常直观:

    • 访问属性:person.name
    • 调用方法:person.getName()
    • 访问数组/列表元素:list[0]
    • 访问 Map 键值:map['key']
  • 运算符

    OGNL 支持多种运算符,包括但不限于:

    • 算术运算符:+, -, *, /, %
    • 关系运算符:==, <, >, <=, >=, !=
    • 逻辑运算符:&&, ||, !
    • 三元运算符:condition ? trueValue : falseValue

5.2 Mybatis动态SQL常用标签

5.2.1 <if>标签

  • 作用

    <if> 标签用于根据条件包含或排除 SQL 片段。它有一个 test 属性,该属性是一个 OGNL 表达式,用来判断是否满足某个条件。

  • 案例

    /**
     * 按条件查询员工信息(动态条件)
    */
    List<Employee> selectEmpByDynamicCondition(Employee employee);
    
    <!--    动态SQL-->
    <select id="selectEmpByDynamicCondition" resultType="employee">
        select
            e_id,
            e_name,
            salary,
            address
        from
            t_emp
        where 1= 1
            <if test="eId != null">
               and e_id = #{eId}
            </if>
            <if test="eName != null">
                and e_name like concat('%',#{eName},'%')
            </if>
    </select>
    

5.2.2 <where>标签

  • 作用

    <where> 标签简化了条件语句的处理,它会自动处理第一个 ANDOR,并且当没有条件时不会生成 WHERE 子句。

  • 案例

    /**
     * 按条件查询员工信息(动态条件)
    */
    List<Employee> selectEmpByDynamicConditionWhere(Employee employee);
    
    <!--    动态SQL-->
    <select id="selectEmpByDynamicConditionWhere" resultType="employee">
        select
            e_id,
            e_name,
            salary,
            address
        from
            t_emp
        <where>
            <if test="eId != null">
                e_id = #{eId}
            </if>
            <if test="eName != null">
                and e_name like concat('%',#{eName},'%')
            </if>
        </where>
    </select>
    

5.2.3 <choose>标签

  • 作用

    <choose>, <when>, <otherwise>

    这些标签提供了类似于 Java 中 switch 语句的功能,用于多条件分支选择。

  • 案例

    /**
     * 按条件查询员工信息(动态条件)
    */
    List<Employee> selectEmpByChoose(Employee employee);
    
    <select id="selectEmpByChoose" resultType="employee">
        select
            e_id,
            e_name,
            salary,
            address
        from
            t_emp
        <where>
            <choose>
                <when test="eId != null">
                    e_id = #{eId}
                </when>
                <when test="eName != null">
                    e_name = #{eName}
                </when>
                <when test="salary != null">
                    salary = #{salary}
                </when>
                <when test="address != null">
                    address = #{address}
                </when>
                <otherwise>
                    1=1
                </otherwise>
            </choose>
        </where>
    </select>
    

5.2.4 <trim>标签

  • 作用

    <trim> 标签用于去掉多余的前缀或后缀。它的属性有

    • prefix属性:指定要动态添加的前缀
    • suffix属性:指定要动态添加的后缀
    • prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值
    • suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值
  • 案例

    /**
     * 按条件查询员工信息(动态条件)
    */
    List<Employee> selectEmpByTrim(Employee employee);
    
    <select id="selectEmpByTrim" resultType="employee">
         select
            e_id,
            e_name,
            salary,
            address
        from
            t_emp
        <trim prefix="where" suffixOverrides="and|or">
            <if test="eId != null">
                e_id = #{eId} and
            </if>
            <if test="eName != null">
                e_name like concat('%',#{eName},'%')
            </if>
        </trim>
    </select>
    

5.2.5 <set>标签

  • 作用

    <set> 标签用于更新语句中,它会自动处理逗号分隔符,并且当没有要更新的内容时不会生成 SET 关键字。

  • 案例

    /**
     * 动态条件修改员工信息
    */
    void updateEmpByDynamicCondition(Employee employee);
    
    <update id="updateEmpByDynamicCondition">
        update t_emp
        <set>
            <if test="eName != null">
                e_name = #{eName},
            </if>
            <if test="salary != null">
                salary = #{salary},
            </if>
            <if test="address != null">
                address = #{address}
            </if>
        </set>
        <where>
            <if test="eId != null">
                e_id = #{eId}
            </if>
        </where>
    </update>
    

5.2.6 <foreach>标签

  • 作用

    <foreach> 标签允许你遍历集合,常用于 IN 语句等场景。它有四个重要的属性:collection(集合名称)、item(集合中的元素)、open(开始符号)、close(结束符号)和 separator(分隔符)。

  • 案例

    /**
     * 添加员工信息(测试foreach)
     * List入参注意:map的key[list|collection]
    */
    void insertEmpByBatch(@Param("empList") List<Employee> employees);
    
    <insert id="insertEmpByBatch">
        insert into t_emp(e_name,salary,address,d_id) values
        <foreach collection="empList" item="emp" separator=",">
            (#{emp.eName},#{emp.salary},#{emp.address},#{emp.dept.dId})
        </foreach>
    </insert>
    

5.2.7 <sql>标签

  • 作用

    <sql> 标签允许你定义可重用的 SQL 片段,这些片段可以在多个查询中使用。通过这种方式,你可以避免重复代码,并且使得 SQL 语句更易于管理和维护。

  • 案例

    <sql id="select_emp_all_filed">
        select
            e_id,
            e_name,
            salary,
            address
        from
            t_emp
    </sql>
    <sql id="emp_all_filed">
        e_id,
        e_name,
        salary,
        address
    </sql>
    
    <!--    动态SQL-->
    <select id="selectEmpByDynamicCondition" resultType="employee">
        select
            <include refid="emp_all_filed"/>
        from
            t_emp
        <where>
            <if test="eId != null">
                e_id = #{eId}
            </if>
            <if test="eName != null">
                and e_name like concat('%',#{eName},'%')
            </if>
        </where>
    </select>
    

5.2.8 <bind>标签

  • 作用

    <bind> 标签用于在 SQL 语句中创建变量绑定。它特别适用于需要对某些表达式的结果进行多次引用的情况,或者当你想简化复杂的 OGNL 表达式时。

  • 案例

    <!--    动态SQL-->
    <select id="selectEmpByDynamicCondition" resultType="employee">
        <bind name="patern" value="'%' + eName + '%'"></bind>
        select
            e_id,
            e_name,
            salary,
            address
        from
            t_emp
        <where>
            <if test="eId != null">
                e_id = #{eId}
            </if>
            <if test="eName != null">
                and e_name like #{patern}
            </if>
        </where>
    </select>
    

第6章 Mybatis分页插件

6.1 分页概念

6.1.1 为什么需要分页

分页(Pagination)是处理和展示大量数据时的一种常见技术,它将数据分割成多个页面,每次只显示其中一部分。在软件开发尤其是Web应用中,分页具有多方面的重要性:

  1. 提升用户体验
  • 更快的响应时间:一次性加载所有数据可能会导致页面加载速度变慢,影响用户体验。通过分页,可以减少每次请求的数据量,从而加快页面响应速度。

  • 更好的可读性:过多的信息同时出现在一个页面上会使用户感到困惑,难以快速找到所需信息。分页可以帮助组织内容,让用户更容易浏览和查找。

  1. 优化性能
  • 减轻服务器负担:对于大型数据集,一次性查询并返回所有记录会给数据库服务器带来巨大压力。分页可以让每次查询只获取必要的数据,降低对服务器资源的需求。
  • 减少带宽消耗:传输大量数据会占用较多网络带宽,尤其是在移动网络环境下,这不仅增加了成本还可能导致延迟。分页可以有效减少不必要的数据传输。
  1. 提高系统的可扩展性和稳定性
  • 防止内存溢出:当应用程序需要处理非常大的数据集时,如果不使用分页机制,可能会因为内存不足而导致程序崩溃或异常终止。分页确保了每次只处理有限数量的数据,避免了这种情况的发生。
  • 支持大数据处理:随着业务的增长,数据量也会随之增加。良好的分页设计有助于应对不断增长的数据规模,保证系统能够稳定运行。

6.1.2 如何实现分页

  • 如没有分页插件,需要自定义分页工具类

    自定义Page分页插件

    • 当前页:PageNum
    • 总页数:Pages
    • 总信息数量:Total
    • 每页显示数量:PageSize
    • 当前页显示数据集合:List

    Mybatis中支持分页插件:PageHelper

6.2 分页插件PageHelper应用

6.2.1 添加插件

  • 导入依赖

    <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>6.1.0</version>
    </dependency>
    
  • 装配PageInterceptor

    package com.atguigu.config;
    
    import com.github.pagehelper.PageInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @Author zhangchunsheng
     * @CreateTime: 2024/11/25
     */
    @Configuration
    public class MybatisConfig {
    
        /**
        * @Author: zhangchunsheng
        * @Date: 2024/11/25 9:15
         * 将PageInterceptor注入到springIOC容器中,
         * id: pageInterceptor
        */
        @Bean("pageInterceptor")
        public PageInterceptor getPageInterceptor() {
            PageInterceptor pageInterceptor = new PageInterceptor();
            return pageInterceptor;
        }
    }
    

6.2.2 使用插件

  • 查询之前开启分页:PageHelper.startPage(1,5);

  • 查询之后将查询结果封装到PageInfo中

    • PageInfo pageInfo = new PageInfo<>(empList,5);
  • 案例代码

    @Test
        public void testPageHelper() {
            //开启分页
            PageHelper.startPage(2,3);
            //查询数据
            List<Employee> employees = employee0XMLMapper.selectAllEmps();
            //将查询结果封装PageInfo<T>对象
            //10:导航页
            PageInfo<Employee> pageInfo = new PageInfo<>(employees,10);
    
            System.out.println(pageInfo.getPageNum()+"/"+pageInfo.getPages());
    
            List<Employee> empList = pageInfo.getList();
            empList.forEach(System.out::println);
    
        }
    
    @Test
    public void testTeacherRelationshipToMulti() {
    
        TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class);
    
        PageHelper.startPage(1,2);
        // 查询Customer对象同时将关联的Order集合查询出来
        List<Teacher> allTeachers = teacherMapper.findAllTeachers();
    //
        PageInfo<Teacher> pageInfo = new PageInfo<>(allTeachers);
    
        System.out.println("pageInfo = " + pageInfo);
        long total = pageInfo.getTotal(); // 获取总记录数
        System.out.println("total = " + total);
        int pages = pageInfo.getPages();  // 获取总页数
        System.out.println("pages = " + pages);
        int pageNum = pageInfo.getPageNum(); // 获取当前页码
        System.out.println("pageNum = " + pageNum);
        int pageSize = pageInfo.getPageSize(); // 获取每页显示记录数
        System.out.println("pageSize = " + pageSize);
        List<Teacher> teachers = pageInfo.getList(); //获取查询页的数据集合
        System.out.println("teachers = " + teachers);
        teachers.forEach(System.out::println);
    }
    

第7章 MybatisX之逆向工程

7.1 ORM思维介绍

ORM思维介绍

ORM(Object-Relational Mapping,对象-关系映射)是一种将数据库和面向对象编程语言中的对象之间进行转换的技术。它将对象和关系数据库的概念进行映射,通过一系列的操作将对象关联到数据表中的一行或多行上。

让我们可以使用面向对象思维进行数据库操作!!!

ORM 框架通常有半自动和全自动两种方式。

  • 半自动 ORM 通常需要程序员手动编写 SQL 语句或者配置文件,将实体类和数据表进行映射,还需要手动将查询的结果集转换成实体对象。
  • 全自动 ORM 则是将实体类和数据表进行自动映射,使用 API 进行数据库操作时,ORM 框架会自动执行 SQL 语句并将查询结果转换成实体对象,程序员无需再手动编写 SQL 语句和转换代码。

下面是半自动和全自动 ORM 框架的区别:

  1. 映射方式:半自动 ORM 框架需要程序员手动指定实体类和数据表之间的映射关系,通常使用 XML 文件或注解方式来指定;全自动 ORM 框架则可以自动进行实体类和数据表的映射,无需手动干预。
  2. 查询方式:半自动 ORM 框架通常需要程序员手动编写 SQL 语句并将查询结果集转换成实体对象;全自动 ORM 框架可以自动组装 SQL 语句、执行查询操作,并将查询结果转换成实体对象。
  3. 性能:由于半自动 ORM 框架需要手动编写 SQL 语句,因此程序员必须对 SQL 语句和数据库的底层知识有一定的了解,才能编写高效的 SQL 语句;而全自动 ORM 框架通过自动优化生成的 SQL 语句来提高性能,程序员无需进行优化。
  4. 学习成本:半自动 ORM 框架需要程序员手动编写 SQL 语句和映射配置,要求程序员具备较高的数据库和 SQL 知识;全自动 ORM 框架可以自动生成 SQL 语句和映射配置,程序员无需了解过多的数据库和 SQL 知识。

常见的半自动 ORM 框架包括 MyBatis 等;常见的全自动 ORM 框架包括 Hibernate、Spring Data JPA、MyBatis-Plus 等。

7.2 逆向工程插件-MybatisX

7.2.1 逆向工程基本概念

​ MyBatis 的逆向工程是一种自动化生成持久层代码和映射文件的工具,它可以根据数据库表结构和设置的参数生成对应的实体类、Mapper.xml 文件、Mapper 接口等代码文件,简化了开发者手动生成的过程。逆向工程使开发者可以快速地构建起 DAO 层,并快速上手进行业务开发。
​ MyBatis 的逆向工程有两种方式:通过 MyBatis Generator 插件实现和通过 Maven 插件实现。无论是哪种方式,逆向工程一般需要指定一些配置参数,例如数据库连接 URL、用户名、密码、要生成的表名、生成的文件路径等等。
​ 总的来说,MyBatis 的逆向工程为程序员提供了一种方便快捷的方式,能够快速地生成持久层代码和映射文件,是半自动 ORM 思维像全自动发展的过程,提高程序员的开发效率。

注意:逆向工程只能生成单表crud的操作,多表查询依然需要我们自己编写!


http://www.kler.cn/a/533831.html

相关文章:

  • 如何在Window计算机本地部署DeepSeek-r1模型
  • ubuntu 网络管理--wpa_supplicant、udhcpc
  • ASP.NET Core Filter
  • CompletableFuture
  • 自研有限元软件与ANSYS精度对比-Bar3D2Node三维杆单元模型-央视大裤衩实例
  • Codeforces Round 997 (Div. 2) A~D题解
  • 理解 Maven 的 pom.xml 文件
  • 验证工具:VCS概识
  • Sqli-labs靶场实录(一):Basic Challenges
  • Ubuntu 下 nginx-1.24.0 源码分析 - ngx_sprintf_str 函数
  • 蓝桥杯思维训练(五)
  • 【Day31 LeetCode】动态规划DP Ⅳ
  • 在深度学习中,样本不均衡问题是一个常见的挑战,尤其是在你的老虎机任务中,某些的中奖倍数较高
  • 网络安全-设备安全加固
  • 【前端】【Ts】【知识点总结】TypeScript知识总结
  • 使用DeepSeek R1 + 了解部署
  • 从离散傅里叶变换(DFT)到快速傅里叶变换(FFT)
  • 【蓝桥杯嵌入式】工程创建
  • MapStruct工具类的使用
  • [论文笔记] Deepseek技术报告
  • 【Elasticsearch】`auto_date_histogram`聚合功能详解
  • MLA 架构
  • Ubuntu部署Deepseek-R1模型(8b)
  • 基于微信小程序的医院综合服务平台的设计与实现ssm+论文源码调试
  • 亚博microros小车-原生ubuntu支持系列:22 物体识别追踪
  • AI绘画:解锁商业设计新宇宙(6/10)