快速了解(入门案例)(Mybatis3)

  1. 准备数据模型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    CREATE DATABASE `mybatis-example`;

    USE `mybatis-example`;

    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);
  2. 搭建项目和准备
    a.搭建项目
    mybatis-base-quickstart-01
    b.依赖导入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <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>
    c.实体类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package cn.xnj.pojo;

    public class Employee {

    private Integer empId;

    private String empName;

    private Double empSalary;

    //生成 getter | setter | toString 方法
    }
  3. 准备Mapper接口和MapperXML文件
    MyBatis 框架下,SQL语句编写位置发生改变,从原来的Java类,改成XML或者注解定义!
    a.定义mapper接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package cn.xnj.mapper;

    import cn.xnj.pojo.Employee;

    /**
    * t_emp表对应数据库SQL语句映射接口!
    * 接口只规定方法,参数和返回值!
    * mapper.xml中编写具体SQL语句!
    */
    public interface EmployeeMapper {

    /**
    * 根据员工id查询员工数据方法
    * @param empId 员工id
    * @return 员工实体对象
    */
    Employee selectEmployeeById(Integer empId);

    }
    b.定义xml映射文件
    注意此处规范的文件目录格式应该是:src/main/resource/对应mapper接口包路径/xxxMapper.xml
    对应mapper接口包路径在idea创建的时候也应该用/分隔开,这里不能用.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?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 namespace="cn.xnj.mapper.EmployeeMapper">
    <!-- 查询使用 select标签
    id = 方法名
    resultType = 返回值类型
    标签内编写SQL语句
    -->
    <select id="selectEmployeeById" resultType="cn.xnj.pojo.Employee">
    <!-- #{empId}代表动态传入的参数,并且进行赋值! -->
    select emp_id empId,emp_name empName, emp_salary empSalary from
    t_emp where emp_id = #{empId}
    </select>

    </mapper>
    注意:
    • 方法名和SQL的id一致
    • 方法返回值和resultType一致
    • 方法的参数和SQL的参数一致
    • 接口的全类名和映射配置文件的名称空间一致
  4. 准备MyBatis配置文件
    mybatis框架配置文件: 数据库连接信息,性能配置,mapper.xml配置等!
    建议命名为mybatis-config.xml,之后用spring会整合,就不需要该配置文件了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <?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="123456"/>
    </dataSource>
    </environment>
    </environments>

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

    </configuration>
  5. 运行和测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class MyBatisTest {
    @Test
    public void testSelectEmployee() throws IOException {

    // 1.读取外部配置文件 (mybatis-config.xml)
    // ①声明Mybatis全局配置文件的路径
    String mybatisConfigFilePath = "mybatis-config.xml";
    // ②以输入流的形式加载Mybatis配置文件
    InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);

    // 2. 创建SqlSessionFactory
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build (inputStream);

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

    // 4.根据接口的代理对象(代理技术)调用代理对象的方法,就会查找 mapper接口的方法
    EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    Employee employee = employeeMapper.selectEmployeeById(1);
    System.out.println("employee = " + employee);

    // 4.关闭SqlSession
    //session.commit(); //提交事务 [DQL不需要,其他需要]
    session.close(); //关闭会话

    }
    }
    说明:
    • SqlSession:代表Java程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的会话)
    • SqlSessionFactory:是“生产”SqlSession的“工厂”。
    • 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。

MyBatis基本使用

向SQL语句传参

mybatis日志输出配置

mybatis-config.xml配置文件中的标签和结构如下:
configuration(配置,都写在它下面)

  • properties(属性)
  • settings(设置)
  • typeAliases(类型别名)
  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境配置)
  • environment(环境变量)
    • transactionManager(事务管理器)
    • dataSource(数据源)
  • databaseIdProvider(数据库厂商标识)
  • mappers(映射器)

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

1
2
3
4
5
6
7
8
9
<settings>
<!-- 开启 mybatis的日志输出,
STDOUT_LOGGING -选择使用sysytem进行控制台输出
SLF4J -选择使用选择slf4j输出
JDK_LOGGING -选择使用JDK自带的日志输出
NOP_LOGGING -选择不输出日志
-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

#{}形式

Mybatis会将SQL语句中的#{}转换为?问号占位符。

  • #{key}:emp_id = ? ? = 赋值
  • 可以防止sql注入的问题
  • ?只能代替值的位置,不能代替 容器名(标签,列名,sql关键字)
  • select * from 表 where 列名=#{传参}
1
2
3
4
<select id="selectEmployeeById" resultType="cn.xnj.pojo.Employee">
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_id = #{id}
</select>

${}形式

${}形式传参,底层Mybatis做的是字符串拼接操作。

  • ${key}:字符串拼接 "emp_id=" + id
1
2
3
4
<select id="selectEmployeeById" resultType="cn.xnj.pojo.Employee">
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_id = ${id}
</select>

数据输入

这里数据输入具体是指上层方法(例如Service方法)调用Mapper接口时,数据传入的形式。

  • 简单类型:只包含一个值的数据类型
    • 基本数据类型:int、byte、short、double、……
    • 基本数据类型的包装类型:Integer、Character、Double、……
    • 字符串类型:String
  • 复杂类型:包含多个值的数据类型
    • 实体类类型:Employee、User、……
    • 集合类型:List、Set、Map、……
    • 数组类型:int[]、String[]、……
    • 复合类型:List、实体类中包含集合……

单个简单类型参数

传入单个简单类型,列名=#{key},key可以随便取值,但是为了规范,使用参数名!

Mapper接口中抽象方法声明

1
2
3
public interface EmployeeMapper {
Employee selectEmployeeById(Integer empId);
}

Mapper.xml文件中sql语句
1
2
3
4
<select id="selectEmployeeById" resultType="cn.xnj.pojo.Employee">
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_id = #{empId}
</select>

实体类型参数

传入实体类型 (#{key1},#{key2}),key填对应实体类的属性名即可

实体类

1
2
3
4
5
6
7
8
public class Employee {

private Integer empId;

private String empName;

private Double empSalary;
}

Mapper接口中抽象方法声明
1
2
3
4
5
6
7
8
9
10
public interface EmployeeMapper {
/* 插入入员工数据【实体对象】
Employee employee = new Employee();
employee.setEmpName("张三");
employee.setEmpSalary(10000.0);
int i = employeeMapper.insertEmployee(employee);
System.out.println("i = " + i);
*/
int insertEmployee(Employee employee);
}

Mapper.xml文件中sql语句
1
2
3
4
<insert id="insertEmployee" >
insert into t_emp(emp_name,emp_salary)
values (#{empName},#{empSalary})
</insert>

零散(多个)的简单类型数据

传入多个简单类型 列名1=#{key1} and 列名2=#{key2}

  • key的值不能随便写,也不是按参入的形参名写
  • 方案一:注解指定,使用@Param注解 指定多个简单参数的key [推荐]
    • @Param("value值")
    • sql中:key=value值
  • 方案二:mybatis默认机制
    • arg0 arg1...形参从左到右依次对应arg0,arg1…
    • (name,salary) name-> key=arg0 salary->key=arg1
    • param1 param2...形参从左到右依次对应param1 param2…
    • (name,salary) name-> key=param1 salary->key=param2

Mapper接口中抽象方法声明

1
2
3
4
public interface EmployeeMapper {
//多个参数查找员工数据
Employee selectByEmpNameAndEmpSalary(@Param("name") String empName,@Param("salary") Double empSalary);
}

Mapper.xml文件中sql语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<select id="selectByEmpNameAndEmpSalary" resultType="cn.xnj.pojo.Employee">
<!--方案一 @Param("value值")-->
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_name = #{name} and emp_salary = #{salary}

<!--方案二 arg0 arg1...-->
<!--
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_name = #{arg0} and emp_salary = #{arg1}
-->

<!--方案三 param1 param2...-->
<!--
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_name = #{param1} and emp_salary = #{param2}
-->
</select>

Map类型参数

传入map只需 key=map的key即可

Mapper接口中抽象方法声明

1
2
3
4
5
6
7
8
9
public interface EmployeeMapper {
/* 传入Map数据来插入员工数据 map(name="员工名",salary=员工薪资)
Map data = new HashMap();
data.put("name","李四");
data.put("salary",9000.0);
int i = employeeMapper.insertEmployeeByMap(data);
*/
int insertEmployeeByMap(Map data);
}

Mapper.xml文件中sql语句
1
2
3
4
<insert id="insertEmployeeByMap">
insert into t_emp(emp_name,emp_salary)
values (#{name},#{salary})
</insert>

数据输出

数据输出总体上有两种形式:

  • 增删改操作返回的受影响行数:直接使用 int 或 long 类型接收即可
    • int insert(User user);//添加数据,返回添加的行数
    • int deleteById(Integer id);//通过id删除,返回删除的行数
    • int updateById(User user);//根据id修改数据,返回修改的行数
  • 查询操作的查询结果
    • String queryNameById(Integer id);//通过id查询名字
    • Double querySalaryByName(String name);//通过姓名查询薪水

我们需要做的是,指定查询的输出数据类型即可!
并且插入场景下,实现主键数据回显示

单个简单类型

细节:

  • select标签,通过resultType指定查询返回值类型!
  • resultType = “全限定符 | 别名 | 如果是返回集合类型,写范型类型即可”
  • 基本数据类型 int double -> _int _double
  • 包装数据类型 Integer Double -> int integer double
  • 集合容器类型 Map List HsahMap -> 小写即可 map list hashmap

别名:

Mapper接口中抽象方法声明

1
2
3
4
5
6
7
public interface EmployeeMapper {
//通过id查询名字
String queryNameById(Integer id);

//通过姓名查询薪水
Double querySalaryByName(String name);
}

Mapper.xml文件中sql语句
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--resultType="java.lang.String" 全限定符写法 -->
<!--<select id="queryNameById" resultType="java.lang.String">-->
<!--resultType="String" 别名写法 -->
<select id="queryNameById" resultType="String">
select emp_name empName
from t_emp where emp_id = #{id}
</select>


<select id="querySalaryByName" resultType="_double">
select emp_salary empSalary
from t_emp where emp_name = #{name}
</select>

返回实体类对象

Mapper接口的抽象方法

1
Employee selectEmployee(Integer empId);

Mapper.xml配置SQl语句
1
2
3
4
5
6
7
8
9
<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
<select id="selectEmployee" resultType="cn.xnj.pojo.Employee">

<!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 -->
<!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
select emp_id empId,emp_name empName,emp_salary empSalary
from t_emp where emp_id=#{maomi}
</select>

因为数据库表中的列的命名和实体类属性的命名规范不同,导致我们需要通过给数据库表字段加别名,让查询结果的每一列都和Java实体类中属性对应起来,才能正确的接收返回结果。
我们可以在mybatis-config.xml全局配置文件中,做如下配置,这样就不用再给字段设置别名了
1
2
3
4
5
6
7
8
9
<!-- 在全局范围内对Mybatis进行配置 -->
<settings>
<!-- 具体配置 -->
<!-- 从org.apache.ibatis.session.Configuration类中可以查看能使用的配置项 -->
<!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则 -->
<!-- 规则要求数据库表字段命名方式:单词_单词 -->
<!-- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

驼峰命名规范

Java实体类属性名命名方式:首字母小写的驼峰式命名
实体类:Book

  • 属性:id,bookId,bookName,createTime,updateTime

数据库表字段命名方式:单词_单词
数据库表:tb_book

  • 字段:id,boo_id,book_name,create_time,update_time

返回Map类型

适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。
Mapper接口的抽象方法:

1
2
//查询薪水最高的员工的姓名和薪水
Map<String,Object> selectEmpNameAndMaxSalary();

Mapper.xml的SQL语句:
1
2
3
4
<select id="selectEmpNameAndMaxSalary" resultType="java.util.Map">
select emp_name 员工姓名, emp_salary 工资
from t_emp where emp_salary = (select max(emp_salary) from t_emp)
</select>

返回List类型

查询结果返回多个实体类对象,希望把多个实体类对象放在List集合中返回。此时不需要任何特殊处理,在resultType属性中还是设置实体类类型即可

Mapper接口中抽象方法

1
List<Employee> selectAll();

SQL语句
1
2
3
4
5
6
7
8
9
<!-- List<Employee> selectAll(); -->
<!-- 集合类型的返回值
返回值是集合,resultType不需要指定集合类型
只需指定泛型即可,如string,比如下面返回集合的泛型就是Employee
-->
<select id="selectAll" resultType="cn.xnj.pojo.Employee">
select emp_id ,emp_name ,emp_salary
from t_emp
</select>

返回主键值

  1. 自增长类型主键
    当sql表的id字段为自增长(auto_increment)时,执行插入操作时返回生成的主键id的值

    • 在sql标签设置useGeneratedKeys=true keyColumn="主键列的值" keyProperty="接收主键列值的属性"

      Mapper接口中抽象方法

      1
      2
      // 传入员工数据【实体对象】
      int insertEmployee(Employee employee);

      SQL语句

      1
      2
      3
      4
      <insert id="insertEmployee" useGeneratedKeys="true" keyColumn="emp_id" keyProperty="empId">
      insert into t_emp(emp_name,emp_salary)
      values (#{empName},#{empSalary})
      </insert>

      测试

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
          @Test
      public void testInsertEmployee() throws IOException {
      String mybatisConfigFilePath = "mybatis-config.xml";
      InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);
      SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

      //传入参数true 开启自动提交事务
      SqlSession session = sessionFactory.openSession(true);

      EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
      //创建员工对象
      Employee employee = new Employee();
      employee.setEmpName("李白");
      employee.setEmpSalary(8888.88);
      System.out.println("插入操作前empID = " + employee.getEmpId());//插入操作前empID = null
      int i = employeeMapper.insertEmployee(employee);
      System.out.println("插入操作后empID = " + employee.getEmpId());//插入操作后empID = 8
      System.out.println("i = " + i);//i = 1


      //session.commit(); //提交事务
      session.close(); //关闭会话

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

    • order="before | after" sql语句是在插入语句之前还是之后执行
    • resultType = "返回值类型"
    • keyProperty = "查询结果给哪个属性赋值"

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

    1
    2
    3
    4
    5
    6
    7
    <insert id="insertEmployee" >
    <selectKey order="BEFORE" resultType="string" keyProperty="tId">
    select replace(UUID(),'-','');
    </selectKey>
    insert into teacher(t_id,t_name)
    values (#{tId},#{tName})
    </insert>

实体类属性和数据库字段对应关系

  1. 别名对应
    将字段的别名设置成和实体类属性一致
    实体类属性约定:即getXxx()方法、setXxx()方法把方法名中的get或set去掉,首字母小写。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
    <!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
    <select id="selectEmployee" resultType="cn.xnj.pojo.Employee">

    <!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 -->
    <!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=# {maomi}

    </select>
  2. 全局配置自动识别驼峰式命名规则
    在Mybatis全局配置文件(mybatis-config.xml)加入如下配置:

    1
    2
    3
    4
    5
    6
    7
    <!-- 使用settings对Mybatis全局进行设置 -->
    <settings>

    <!-- 将xxx_xxx这样的列名自动映射到xxXxx这样驼峰式命名的属性名 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>

    </settings>

    之后xxxMapper.xml文件中的SQL语句可以不设置别名

  3. 使用ResultMap
    使用resultMap标签定义对应关系,再在后面的SQL语句中引用这个对应关系

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- 专门声明一个resultMap设定column到property之间的对应关系 -->
    <resultMap id="EmployeeResultMap" type="cn.xnj.pojo.Employee">

    <!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
    <!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
    <id column="emp_id" property="empId"/>

    <!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
    <result column="emp_name" property="empName"/>

    <result column="emp_salary" property="empSalary"/>

    </resultMap>

    <!-- Employee selectEmployeeByRM(Integer empId); -->
    <select id="selectEmployeeByRM" resultMap="EmployeeResultMap">

    select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}

    </select>

Lombok

lombok能帮我们自动生成无参和有参构造方法以及get,set方法等,只需在实体类上方加上对应注解即可

常用注解 说明
@Data 包含get,set,toString等
@AllArgsConstructor 全参构造函数
@NoArgsConstructor 无参构造函数
@ToString 重写toString方法
  1. 导入依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.32</version>
    </dependency>
  2. 安装插件lombok(图标封面是一个红辣椒🌶️)

MyBatis多表映射

大致分为3步

  1. 使用多表查询语句
  2. 多表结果承接实体类设计
    • 一对一:属性中包含对方对象
    • 一对多:属性中包含对方对象集合
  3. 使用ResultMap完成多表结果映射

多表映射案例

经典关系:
客户和订单,一对多关系,一个客户对应多个订单
订单和客户,一对一关系,一个订单对应一个客户

  1. 数据准备

    1
    2
    3
    4
    5
    6
    7
    8
    9
    CREATE TABLE `t_customer` (`customer_id` INT NOT NULL AUTO_INCREMENT, `customer_name` CHAR  (100), PRIMARY KEY (`customer_id`) );

    CREATE TABLE `t_order` ( `order_id` INT NOT NULL AUTO_INCREMENT, `order_name` CHAR(100), `customer_id` INT, PRIMARY KEY (`order_id`) );

    INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');

    INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1');
    INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1');
    INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '1');
  2. 实体类准备

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Data
    public class Customer {//客户实体类

    private Integer customerId;
    private String customerName;
    private List<Order> orderList;// 体现的是对多的关系

    }

    @Data
    public class Order {//订单实体类
    private Integer orderId;
    private String orderName;
    private Integer customerId;
    private Customer customer;// 体现的是对一的关系

    }

对一映射

  1. 需求说明
    根据订单id查询订单,订单里关联客户的信息

  2. OrderMapper接口

    1
    2
    3
    4
    5
    public interface OrderMapper {

    //根据id查询订单,
    Order selectOrderById(Integer id);
    }
  3. OrderMapper.xml配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <?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="cn.xnj.mapper.OrderMapper">

    <resultMap id="OrderResultMap" type="cn.xnj.pojo.Order">
    <!-- 主键列 -->
    <id column="order_id" property="orderId"/>
    <!-- 普通列 -->
    <result column="order_name" property="orderName"/>
    <result column="customer_id" property="customerId"/>
    <!-- 一对一关联 -->
    <association property="customer" javaType="cn.xnj.pojo.Customer">
    <id column="customer_id" property="customerId"/>
    <result column="customer_name" property="customerName"/>
    </association>
    </resultMap>

    <select id="selectOrderById" resultMap="OrderResultMap">
    select * from t_order join t_customer
    on t_order.customer_id = t_customer.customer_id
    where order_id = #{id};
    </select>

    </mapper>
  4. junit测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class MyBatisTest {

    private SqlSession sqlSession;
    @BeforeEach
    public void before() throws IOException {
    String mybatisConfigFilePath = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build (inputStream);
    sqlSession = sessionFactory.openSession(true);
    }
    @AfterEach
    public void after(){
    sqlSession.close();
    }

    @Test
    public void test_01() throws IOException {
    OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
    Order order = mapper.selectOrderById(1);
    System.out.println(order);
    //控制台输出
    //Order(orderId=1, orderName=o1, customerId=1, customer=Customer(customerId=1, customerName=c01, orderList=null))
    }
    }

对多映射

  1. 需求说明
    根据客户id查询客户信息,客户信息包含其所有订单信息

  2. CustomerMapper接口

    1
    2
    3
    4
    5
    public interface CustomerMapper {

    //根据客户id查询客户信息,客户信息包含用户信息
    Customer selectCustomerById(Integer id);
    }
  3. CustomerMapper.xml配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <?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="cn.xnj.mapper.CustomerMapper">

    <resultMap id="CustomerResultMap" type="cn.xnj.pojo.Customer">
    <!--主键列-->
    <id column="customer_id" property="customerId"/>
    <!--普通列-->
    <result column="customer_name" property="customerName"/>
    <!--一对多关联-->
    <collection property="orderList" ofType="cn.xnj.pojo.Order">
    <id column="order_id" property="orderId"/>
    <result column="order_name" property="orderName"/>
    <result column="customer_id" property="customerId"/>
    </collection>
    </resultMap>

    <select id="selectCustomerById" resultMap="CustomerResultMap">
    select * from t_customer join t_order
    on t_order.customer_id = t_customer.customer_id
    where t_customer.customer_id = #{id};
    </select>

    </mapper>
  4. junit测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void test_02() throws IOException {
    CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
    Customer customer = mapper.selectCustomerById(1);
    System.out.println(customer);

    //控制台输出
    //Customer(customerId=1, customerName=c01, orderList=[Order(orderId=1, orderName=o1, customerId=1, customer=null), Order(orderId=2, orderName=o2, customerId=1, customer=null), Order(orderId=3, orderName=o3, customerId=1, customer=null)])
    }

MyBatis动态语句

if和where标签

我们现在可以看到很多软件里查询时,筛选条件是可选可不选的,即sql语句是动态的,能根据你传递的值来动态的拼接执行sql

  • if标签 让我们可以有选择的加入SQL语句的片段。
    • 这个SQL语句片段是否要加入整个SQL语句,就看if标签判断的结果是否为true
    • test属性 内部做比较运行,为true将标签内的sql语句进行拼接
      • 判断连接语句:and | or
      • 大小比较: < &lt; > &gt; >= &ge; <= &le;
      • <if test="salary!= null and salary &gt; 10000.0"></test>
  • where标签会自动去掉“标签体内前面多余的and/or,自适应添加where关键字
    • 防止,当有多个条件时,随机的拼接导致sql语句错误
    • 如第一个条件没拼接:select * from t_emp where and salary=10000.0的语法错误
    • 没有一个条件拼接:select * from t_emp where
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--
List<Employee> query(@Param("name") String name , @Param("salary") Double salary);
-->

<mapper namespace="cn.xnj.mapper.EmployeeMapper">
<select id="query" resultType="cn.xnj.pojo.Employee">
select * from t_emp
<where>
<if test="name!= null ">
emp_name=#{name}
</if>
<if test="salary!= null">
and emp_salary=#{salary}
</if>
</where>
</select>
</mapper>

junit测试

1
2
3
4
5
6
7
8
@Test
public void test_01() throws IOException {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employeeList = mapper.query(null, null);
System.out.println(employeeList);
List<Employee> employeeList1 = mapper.query(null, 10000.0);
System.out.println(employeeList1);
}

set标签

set标签和where标签类似,set标签能动态管理set语句,并切动态去掉两端多余的逗号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 
void updateEmployeeDynamic(Employee employee)
-->
<update id="updateEmployeeDynamic">
update t_emp
<!-- set emp_name=#{empName},emp_salary=#{empSalary} -->
<!-- 使用set标签动态管理set子句,并且动态去掉两端多余的逗号 -->
<set>
<if test="empName != null">
emp_name=#{empName},
</if>
<if test="empSalary">
emp_salary=#{empSalary},
</if>
</set>
where emp_id=#{empId}
<!--
第一种情况:所有条件都满足 SET emp_name=?, emp_salary=?
第二种情况:部分条件满足 SET emp_salary=?
第三种情况:所有条件都不满足 update t_emp where emp_id=?
没有set子句的update语句会导致SQL语法错误
-->
</update>

junit测试

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test_02() throws IOException {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//使用builder模式创建对象,引入lombok依赖,实体类要加上@Builder注解
Employee employee = Employee.builder()
.empId(4)
.empName("xnj")
.empSalary(15000.0)
.build();
mapper.updateEmployeeDynamic(employee);
}

trim标签

使用trim标签控制条件部分两端是否包含某些字符

  • prefix属性:指定要动态添加的前缀
  • suffix属性:指定要动态添加的后缀
  • prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值
  • suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!--  trim 代替 where-->
<select id="selectEmployeeByConditionByTrim" resultType="cn.xnj.pojo.Employee">
select emp_id,emp_name,emp_age,emp_salary,emp_gender
from t_emp
<trim prefix="where" suffixOverrides="and|or">
<if test="empName != null">
emp_name=#{empName} and
</if>
<if test="empSalary &gt; 3000">
emp_salary>#{empSalary} and
</if>
<if test="empAge &lt;= 20">
emp_age=#{empAge} or
</if>
<if test="empGender=='male'">
emp_gender=#{empGender}
</if>
</trim>
</select>

<!-- trim 代替 set-->
<update id="updateEmployeeDynamic">
update t_emp

<trim prefix="set" suffixOverrides=",">
<if test="empName != null">
emp_name=#{empName},
</if>
<if test="empSalary ">
emp_salary=#{empSalary},
</if>
</trim>
where emp_id=#{empId}
</update>

choose/when/otherwise标签

在多个分支条件中,仅执行一个。

  • 从上到下依次执行条件判断
  • 遇到的第一个满足条件的分支会被采纳
  • 被采纳分支后面的分支都将不被考虑
  • 如果所有的when分支都不满足,那么就执行otherwise分支
  • 即:必会执行一个条件,也只会执行一个条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- List<Employee> selectEmployeeByConditionByChoose(Employee employee) -->
<select id="selectEmployeeByConditionByChoose" resultType="com.atguigu.mybatis.entity.Employee">
select emp_id,emp_name,emp_salary from t_emp
where
<choose>
<when test="empName != null">emp_name=#{empName}</when>
<when test="empSalary != null">emp_salary=#{empSalary}</when>
<otherwise>1=1</otherwise>
</choose>

<!--
第一种情况:第一个when满足条件 where emp_name=?
第二种情况:第二个when满足条件 where emp_salary=?
第三种情况:两个when都不满足 where 1=1 执行了otherwise,即查询全部
-->
</select>

foreach标签

collection属性:要遍历的集合

  • item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象
  • separator属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符
  • open属性:指定整个循环把字符串拼好后,字符串整体的前面要添加的字符串
  • close属性:指定整个循环把字符串拼好后,字符串整体的后面要添加的字符串
  • index属性:这里起一个名字,便于后面引用
    • 遍历List集合,这里能够得到List集合的索引值
    • 遍历Map集合,这里能够得到Map集合的key
  1. EmployeeMapper接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public interface EmployeeMapper {

    // 批量查询
    List<Employee> queryBatch(@Param("ids") List<Integer> ids);
    // 批量插入
    int insertBatch(@Param("employees") List<Employee> employees);
    // 批量删除
    int deleteBatch(@Param("ids") List<Integer> ids);
    // 批量更新
    int updateBatch(@Param("employees") List<Employee> employees);

    }
  2. EmployeeMapper.xml配置文件SQL语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <?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 namespace="cn.xnj.mapper.EmployeeMapper">


    <!-- 批量查询 List<Employee> queryBatch(List<Integer> ids) -->
    <select id="queryBatch" resultType="cn.xnj.pojo.Employee">
    select * from t_emp where emp_id in
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
    #{id}
    </foreach>
    </select>

    <!-- 批量插入 void insertBatch(List<Employee> employees) -->
    <insert id="insertBatch">
    insert into t_emp(emp_name,emp_salary)values
    <foreach collection="employees" item="employee" separator=",">
    (#{employee.empName},#{employee.empSalary})
    </foreach>
    </insert>

    <!-- 批量更新 void updateBatch(List<Employee> employees) -->
    <update id="updateBatch">
    <foreach collection="employees" item="emp" separator=";">
    update t_emp set emp_name=#{emp.empName},emp_salary=#{emp.empSalary} where emp_id=#{emp.empId}
    </foreach>
    </update>

    <!-- 批量删除 void deleteBatch(List<Integer> ids) -->
    <delete id="deleteBatch">
    delete from t_emp where emp_id in
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
    #{id}
    </foreach>
    </delete>
    </mapper>
  3. junit功能测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    public class MyBatisTest {

    private SqlSession sqlSession;
    @BeforeEach
    public void before() throws IOException {
    String mybatisConfigFilePath = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build (inputStream);
    sqlSession = sessionFactory.openSession(true);
    }

    @AfterEach
    public void after(){
    sqlSession.close();
    }

    @Test //批量查询
    public void test_03() throws IOException {
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    //List<Employee> employeeList = mapper.queryBatch(List.of(1, 2, 3, 4, 5));
    List<Integer> ids = new ArrayList<>();
    ids.add(1);
    ids.add(2);
    ids.add(3);
    ids.add(4);
    ids.add(5);
    System.out.println(ids);
    List<Employee> employeeList = mapper.queryBatch(ids);
    System.out.println(employeeList);
    }

    @Test // 批量添加
    public void test_04() throws IOException {
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    List<Employee> employees = new ArrayList<>();
    employees.add(new Employee("xx1",1000.0));
    employees.add(new Employee("xx2",1000.0));
    employees.add(new Employee("xx3",1000.0));
    employees.add(new Employee("xx4",1000.0));
    employees.add(new Employee("xx5",1000.0));
    mapper.insertBatch(employees);
    }

    @Test // 批量修改
    public void test_05() throws IOException {
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    List<Employee> employees = new ArrayList<>();
    employees.add(new Employee(10,"xx1",1500.0));
    employees.add(new Employee(11,"xx2",1500.0));
    employees.add(new Employee(12,"xx3",1500.0));
    employees.add(new Employee(13,"xx4",1500.0));
    employees.add(new Employee(14,"xx5",1500.0));
    mapper.updateBatch(employees);
    }

    @Test // 批量删除
    public void test_06() throws IOException {
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    List<Integer> ids = new ArrayList<>(List.of(10,11,12,13,14));
    mapper.deleteBatch(ids);
    }
    }
  4. 批量更新时需要注意

    • 上面批量插入的例子本质上是一条SQL语句,而实现批量更新则需要多条SQL语句拼起来,用分号分开。也就是一次性发送多条SQL语句让数据库执行。此时需要在数据库连接信息的URL地址中设置:
    • jdbc.url=jdbc:mysql:///mybatis-example?allowMultiQueries=true
  5. mapper接口方法定义参数时注意

    • 如果没有给接口中List类型的参数使用@Param注解指定一个具体的名字,那么在collection属性中默认可以使用collectionlist来引用这个list集合。这一点可以通过异常信息看出来:
    • Parameter 'empList' not found. Available parameters are [arg0, collection, list]

Sql片段

抽取重复的SQL片段

1
2
3
4
<!-- 使用sql标签抽取重复出现的SQL片段 -->
<sql id="mySelectSql">
select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp
</sql>

引用已抽取的SQL片段
1
2
<!-- 使用include标签引用声明的SQL片段 -->
<include refid="mySelectSql"/>

Mapper批量映射优化

前面提过一嘴,当你创建XxxMapper.xml配置文件时,建议目录应该和XxxMapper一样,如:

  • src/main/java/cn.xnj.mapper.XxxMapper.java
    • 在此处创建时,用.,idea能自动识别并创建包对应的文件目录结构
  • src/main/resourcecn/xnj/mapper/XxxMapper.xml
    • 在此处创建时,用.idea不能识别为多层目录结构,要用/
  • 这样在打包后能看到,XxxMapper.javaXxxMapper.xml存放在一个同一个位置

这么做不仅仅是口头规范,在配置批量映射时也是要求目录的格式
mybatis-config.xml配置文件中,Mapper 配置文件很多时,需要在全局配置文件中一个一个注册,这样太麻烦:

1
2
3
4
5
<mappers>
<mapper resource="cn/xnj/mapper/EmployeeMapper.xml"/>
<mapper resource="cn/xnj/mapper/UserMapper.xml"/>
...
</mappers>

现在我们可以直接在mybatis-config.xml配置文件指定 Mapper 映射文件时,只指定其所在的包:

1
2
3
<mappers>
<package name="cn/xnj/mapper"/>
</mappers>

分页插件PageHelper

  1. pom.xml引入依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.11</version>
    </dependency>
  2. 在mybatis-config.xml全局配置文件中添加分页插件

    1
    2
    3
    4
    5
    <plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
    <property name="helperDialect" value="mysql"/>
    </plugin>
    </plugins>
  3. 页插件使用
    sql语句中不需要写limit,结尾不要写;

    1
    2
    3
    4
     <!--分页查询  List<Employee> queryByPage();-->
    <select id="queryByPage" resultType="cn.xnj.pojo.Employee">
    select * from t_emp
    </select>

    在查询方法中使用分页

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @Test // 分页查询
    public void test_07() throws IOException {
    EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
    //调用之前,先设置分页数据(当前页码,每页显示多少数据)
    PageHelper.startPage(1,2);
    // 查询Customer对象同时将关联的Order集合查询出来
    List<Employee> list = employeeMapper.queryByPage();
    //将查询到的数据封装到一个pageInfo的分页实体类当中
    PageInfo<Employee> pageInfo = new PageInfo<>(list);
    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<Employee> employees = pageInfo.getList(); //获取查询页的数据集合
    System.out.println("查询页的数据集合:employees = " + employees);
    // 遍历集合
    employees.forEach(System.out::println);
    }

MyBatisX插件

一个实用的插件,当你书写正确时,能自动定位你的mapper接口方法到mapper.xml配置文件中的sql语句
搜索 MyBatisX 并安装(小鸟图标🐦)。