3.Mybatis学习

明廷盛 嘻嘻😁

第一章 学习Mybatis的切入点

有几个比较重要的部分

  1. 深入学习核心配置文件[mybatis-config.xml]
  2. 如何将**xxxMapper.xml和xxxMapper.java对应上**(函数和xml对应关系)
    • 函数名称: 通过id, 并且实现不同的函数需要不同的元素
    • 传入参数: Mybatis有自己的 入参规则(单个or多个参数)
    • 回传参数: 同样, 有自己的 回传规则(resultType和resultMap)
  3. Mybatis的特殊技能
    • 动态SQL
    • 缓存机制
    • 逆向工程
    • 分页查询

注意:

  1. 关于属性和元素(子标签)
1
2
3
4
5
6
7
<typeAliases>
<typeAlias type="mybatis.pojo.Employee" alias="employee"/>
<package name="mybatis.pojo"/>
</typeAliases>
5. typeAlias和package是typeAliases的元素
6. type,alias是typeAlias的属性; name还是package的属性
7. 元素也可以称为子标签, 尤其当叠的层过多的时候

第二章 HelloMybatis

image-20240617230343317

第一节 STEP1: pom.xml配置

1
2
3
4
5
6
7
<!--pom.xml-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>

第二节 STEP2:核心配置文件[mybatis-config.xml]

  1. 配置基本数据库信息(可以写死, 也可以properties)
  2. 设置映射文件的路径
1
2
3
4
5
#database.properties的内容
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test_mybatis?serverTimezone=UTC
username=root
password=root
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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<!--1.配置基本数据库信息(可以写死, 也可以通过properties(相对该文件的路径))-->
<properties resource="database.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

<!--2.设置映射文件的路径(相对与该文件的路径)-->
<mappers>
<mapper resource="mapper/EmployeeMapper.xml"/>
</mappers>
</configuration>

第三节 STEP3: 相关接口[xxxMapper.java]及映射文件[xxxMapper.xml]

  1. 接口(就是原来的DAO)
1
2
3
4
5
6
7
package mybatis.mapper;

import mybatis.pojo.Employee;

public interface EmployeeMapper {
public Employee selectById(int id); // 示例
}
  1. 映射文件(主要作用为Mapper接口书写Sql语句, 就是原来DAOimplement的作用)
1
2
3
4
5
6
7
8
9
10
11
<?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="mybatis.mapper.EmployeeMapper">
<!--示例-->
<select id="selectById" resultType="mybatis.pojo.Employee">
select id, last_name, email, salary
from tbl_employee
where id = #{id};
</select>
</mapper>
  1. 相互关系

    • 映射文件名与接口名一致

    • 映射文件namespace与接口全类名一致

    • 映射文件SQL的Id与接口的方法名一致

第四节 STEP4:测试

  1. 从 Resources中 获取 SqlSessionFactory对象
  2. 从SqlSessionFactory中获取SqlSession对象
  3. 获取映射XXXMapper代理对象
  4. 测试开始
1
2
3
4
5
6
7
8
9
10
11
// 1. 从 Resources中 获取 SqlSessionFactory对象
String resources= "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 从SqlSessionFactory中获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 获取映射XXXMapper代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 4. 测试开始
Employee res = employeeMapper.selectById(1);
System.out.println(res);

第三章 深入学习核心配置文件中的元素[mybatis-config.xml]

注意这里只展示部分常用元素, 同时这些元素有指定顺序,编写时必须按照此顺序 见官网

第一节 properties

  • 作用:定义或引入外部属性文件
  • 属性:
    • url: 填写绝对路径, 从文件中去找
    • resource: 写相对与resources(Maven)的路径
  • 元素:
    • property: 内联式的设置K,V
  • 如何引用resource中的K,V: 在需要引用的地方${键}
  • 代码示例:
1
2
3
4
5
# 通过设置properties属性, 实现外链式properties文件
<properties resource="database.properties">
# 通过设置properties元素property, 实现内联式的设置K,V
<property name="username" value="Tom"/>
</properties>

第二节 settings

  • 作用:这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

  • 属性: 🈚️

  • 元素: 很多, 去官网查看

    • mapUnderscoreToCamelCase

      • 取值: true | false (default:false)
      • 作用: 驼峰式命名last_name=>lastName
      • 注意: 开启后原名不再可用
    • lazyLoadingEnabled

      • 取值: true | false (default:false)

      • 作用: 开启懒加载后, 只有用到才加载, 否则不加载; 未开启的话,无论是否用到, 都会加载

      • 示例: 在下方的**分步查询**中可以看到效果

    • aggressiveLazyLoading

      • 取值: true | false (default: false [在 3.4.1 及之前的版本中默认为 true])
    • 作用: 只要这个开启上面的懒加载就不起作用

1
2
3
<settings> 
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

第三节 typeAliases

  • 作用:类型别名可为 Java 类型设置一个缩写名字。一般是为pojo定义别名
  • 属性: 🈚️
  • 元素:
    • typeAlias: 为指定的类定义别名
    • package: 为指定包下的所有类名自动定义别名(不区分大小写, 但单词得写对)
  • 代码示例
1
2
3
4
<typeAliases>
<typeAlias type="mybatis.pojo.Employee" alias="employee"/> <!--为指定的类定义别名-->
<package name="mybatis.pojo"/> <!--为指定包下的所有类名自动定义别名(不区分大小写, 但单词得写对)-->
</typeAliases>

第四节 environments

1
2
3
4
5
6
7
8
9
10
11
12
<!--设置数据库连接环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

第五节 mappers

  • 作用: 设置映射文件(xml)路径

  • 属性: 🈚️

  • 元素:

    • 使用相对于类路径的资源引用(推荐)

      1
      2
      3
      <mappers>
      <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
      </mappers>
    • 使用完全限定资源定位符(URL)

      1
      2
      3
      <mappers>
      <mapper url="file:///var/mappers/AuthorMapper.xml"/>
      </mappers>
    • 使用映射器接口实现类的完全限定类名

      1
      2
      3
      <mappers>
      <mapper class="org.mybatis.builder.AuthorMapper"/>
      </mappers>
    • 将包内的映射器接口全部注册为映射器

      1
      2
      3
      4
      <!--要求:接口的包名与映射文件的包名需要一致, 全路径完全一致, 不只是上级文件夹名字一致-->
      <mappers>
      <package name="org.mybatis.builder"/>
      </mappers>

第四章 如何将xxxMapper.xml和xxxMapper.java对应上(函数和xml对应关系)

第一节 映射文件部分元素

4.1.1 insert, delete, update

  • 作用: 增删改

  • 属性:

    • id: 与xxxMapper.java接口中的方法名称对应
    • useGeneratedKeys: true/false; 是否启用主键生成策略
    • keyProperty: 设置属性值(e.g. 比如我要pojo中Employee的id属性来获取到自增的主键值
  • 元素:🈚️

  • 任务:

    1. 在插入后, 得到插入的id(主键)

      1
      2
      3
      4
      5
      6
      <!--EmployeeMapper.xml-->
      <!--开启自增, 同时指定主键, 后续测试时可以直接得主键值-->
      <insert id="insertEmployee" keyProperty="id" useGeneratedKeys="true">
      insert into tbl_employee (last_name, email, salary)
      values (#{last_name}, #{email}, #{salary});
      </insert>
      1
      2
      3
      4
      5
      6
      // EmployeeTest.java
      EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class); //3.获取映射xxxMapper代理对象

      Employee employee = new Employee(null, "Tom", "23@32", 23.3);
      employeeMapper.insertEmployee(employee);
      System.out.println(employee.getId()); // 加上后结果为自增的id值, 否则为null
    2. 注意, 这里如果是insert/delete/update的话, 要手动提交事务

      什么?你说你不用手动提交也可以?数据库创建时引擎不是innoDB

      对于Mysql数据库, 只有InnoDB引擎是支持事务的其他都不支持

      可以通过SHOW TABLE STATUS FROM 'your_table_name';查看当前数据库的引擎;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      /*调用处 test.java*/
      // 2. 从SqlSessionFactory中获取SqlSession对象
      SqlSession sqlSession = sqlSessionFactory.openSession();

      // 3. 获取映射xxxMapper代理对象
      EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);

      Employee employee = new Employee(null, "Yohoal","[email protected]",666.6 );
      employeeMapper.insertEmployee(employee);
      sqlSession.commit();

4.1.2 select

4.1.3 sql

  • 作用: 定义可重用的SQL语句块

4.1.4 cache:

  • 作用: 设置**当前**namespace的缓存配置

4.1.5 cache-ref:

  • 作用: 设置**其他**namespace的缓存配置

4.1.6 resultMap(回传规则):

  • 作用: 描述如何从数据库结果集中加载对象, resultType解决不了的问题,交给resultMap

第二节 函数名称

4.2.1 xml与java文件的三对应

  1. ① 映射文件名与接口名一致

  1. ② 映射文件namespace与接口全类名一致

  1. ③ 映射文件SQL的Id与接口的方法名一致

第三节 入参规则

4.3.1 单个参数

  • 基本类型, 可以任意命名, 但推荐见名思义
1
2
/*EmployeeMapper.java*/
public Employee selectById(int id);
1
2
3
4
5
6
<!--EmployeeMapper.xml-->
<select id="selectById" resultType="mybatis.pojo.Employee">
select id, last_name, email, salary
from tbl_employee
where id = #{fadsfasdf};
</select>
  • 集合类型: 是有可能传入集合类型的, 比如需求—-询员工id在集合employees中的所有员工信息; 但这一般用到动态化SQL-foreach循环标签

4.3.2 多个参数–匿名参数

  • param1、param2 或 arg0、arg1;
1
2
/*EmployeeMapper.java*/
public ArrayList<Employee> selectByMany(double salary, String last_name);
1
2
3
4
5
6
7
<!--EmployeeMapper.xml-->
<select id="selectByMany" resultType="mybatis.pojo.Employee">
select id, last_name, email, salary
from tbl_employee
where salary=#{arg0}
and last_name=#{arg1};
</select>

4.3.3 多个参数–POJO参数

  • 函数: 传入单个pojo变量; xml: 写pojo中的成员变量(属性)
1
2
3
4
/*EmployeeMapper.java*/
// 同样还是查找salary和last_name,但不是入这两个变量, 而是这两个变量所属的pojo
// 3. [pojo参数] 通过salary和last_name; 查找对象
public ArrayList<Employee> selectByPojo(Employee employee);
1
2
3
4
5
6
7
8
<!--EmployeeMapper.xml-->
<!--下面可以直接写salary和last_name是因为他们都是Employee的成员变量(属性), 如果不是,不能用POJO参数, 要用命名参数-->
<select id="selectByPojo" resultType="mybatis.pojo.Employee">
select id, last_name, email, salary
from tbl_employee
where salary=#{salary}
and last_name=#{last_name};
</select>

4.3.4 多个参数–命名参数

  • 函数: @Param注解命名; xml: 写注解命的名
  • @Param(value=”xml中入参的参数名”) 或 @Param(“xml中入参的参数名”)
  • 注意: 命名参数的入参方法同时支持匿名参数的(param1, param2)[不支持arg, 只有命名参数, pojo不行, 下面的也不行]
1
2
/*EmployeeMapper.java*/
public ArrayList<Employee> selectByName(@Param(value = "S") double salary, @Param("LN") String last_name);
1
2
3
4
5
6
7
<!--EmployeeMapper.xml-->
<select id="selectByName" resultType="mybatis.pojo.Employee">
select id, last_name, email, salary
from tbl_employee
where salary=#{S} <!--这里支持param1, param2-->
and last_name=#{LN};
</select>

4.3.5 多个参数–Map参数

  • 函数: Map; xml: 参数名为Map中的键(key)
1
2
3
4
5
/*调用时*/
HashMap<String, Object> map = new HashMap<>();
map.put("S", 8888);
map.put("LName", "Tom");
ArrayList<Employee> res = employeeMapper.selectByMap(map);
1
2
/*EmployeeMapper.java*/
public ArrayList<Employee> selectByMap(Map<String, Object> map);
1
2
3
4
5
6
7
<!--EmployeeMapper.xml-->
<select id="selectByMap" resultType="mybatis.pojo.Employee">
select id, last_name, email, salary
from tbl_employee
where salary=#{S}
and last_name=#{LName};
</select>

4.3.6 占位符–#{}

  • 底层: 【#】底层执行SQL语句的对象,使用PreparedStatementd(?),预编译SQL,防止SQL注入安全隐患,相对比较安全。
  • 使用场景: #使用场景,sql占位符位置均可以使用#

4.3.7 占位符–${}

  • 底层:【$】底层执行SQL语句的对象使用**Statement(字符串拼接)**对象,未解决SQL注入安全隐患,相对不安全。
  • 使用场景: $使用场景,比如表名, 得字符串拼接把, 我函数想传入不同的名字, 查不同的表,;

第四节 回传规则

4.4.1 单表查询[resultType=”pojo”]

  • resultType: POJO对象 (单个对象或对象的集合都是只写这个对象)

  • 示例:

    1
    2
    /*EmployeeMapper.java*/
    public ArrayList<Employee> selectByMap(Map<String, Object> map);
    1
    2
    3
    4
    5
    6
    7
    <!--EmployeeMapper.xml-->
    <select id="selectByMap" resultType="mybatis.pojo.Employee">
    select id, last_name, email, salary
    from tbl_employee
    where salary=#{S}
    and last_name=#{LName};
    </select>

4.4.2 多表查询[resultMap]

①数据库中表的**连接查询** 在 java中用**对象之间的嵌套**体现:
比如数据库中的两张表 tbl_employee(id, salary, last_name, dept_id); tbl_dept(dept_id, dept_name)
在java中就是体现就是 Employee的成员变量(属性)中添加一个Dept dept即可

②在回传规则中直接写resultType =”mybatis.pojo.Employee”不行:
Mybatis不能自动的去映射Employee, 所以得自定义映射规则, 用到resultMap

resultMap的属性和元素

  • 属性:

    • id: 指定这个resultMap的名称, 方便后续引用指定的resultMap
    • type: 函数的返回类型(Pojo)
  • 元素:

    1
    <id column="数据库中的字段名" property="Pojo中的成员变量(属性)名"> <!--主键字段与属性关联关系-->
    1
    <result column="数据库中的字段名" property="Pojo中的成员变量(属性)名"> <!--定义非主键字段与属性关联关系-->
    1
    2
    3
    4
    <association property="Pojo中的成员变量(属性)名" javaType="嵌套的Pojo全路径(开启别名可以用别名)">   
    <result column="dept_id" property="deptId"/> <!--嵌套的pojo中的字段, 遵循id和result元素规则-->
    <result column="dept_name" property="deptName"/>
    </association>
    1
    2
    3
    4
    5
    6
    7
    <collection property="Pojo中的成员变量(属性)名" ofType="嵌套的Pojo全路径(开启别名可以用别名)">
    <id column="id" property="id"/> <!--嵌套的pojo中的字段, 遵循id和result元素规则-->
    <result column="last_name" property="lastName"/>
    <result column="email" property="email"/>
    <result column="salary" property="salary"/>
    <result column="dept_id" property="deptId"/>
    </collection>
resultMap-级联映射(一对一映射关系)

比如一个员工只能属于一个部门; 那在员工中Dept deptId这个字段, 就是一对一的映射关系

1
2
3
/*EmployeeMapper.java*/
// 1. 通过员工的id, 获取员工的信息和部门信息
List<Employee> selectEmpAndDeptByEmpId(int empId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--EmployeeMapper.xml-->
<resultMap id="EmpAndDept" type="Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="salary" property="salary"/>
<result column="dept_id" property="deptId"/>
<!--为嵌套中的对象,自定义关联关系-->
<result column="dept_id" property="dept.deptId"/>
<result column="dept_name" property="dept.deptName"/>
</resultMap>

<select id="selectEmpAndDeptByEmpId" resultMap="EmpAndDept">
select e.id,e.last_name,e.salary, e.email, e.dept_id, d.dept_name
from tbl_employee e, tbl_dept d
where e.dept_id=d.dept_id
and e.id=#{id};
</select>
resultMap-Association映射(一对一映射关系)
1
2
3
/*EmployeeMapper.java*/
// 2. [Association映射] 通过员工的id, 获取员工的信息和部门信息
Employee selectEmpAndDeptByEmpIdAssociation(int empId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--EmployeeMapper.xml-->
<resultMap id="EmpAndDeptAssociation" type="Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="salary" property="salary"/>
<result column="dept_id" property="deptId"/>
<association property="dept" javaType="Dept"> <!-- assocaiation 解决一对一-->
<result column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>

<select id="selectEmpAndDeptByEmpIdAssociation" resultMap="EmpAndDeptAssociation">
select e.id,e.last_name,e.salary, e.email, e.dept_id, d.dept_name
from tbl_employee e, tbl_dept d
where e.dept_id=d.dept_id
and e.id=#{id};
</select>
resultMap-Collection(一对多映射关系)

一个部门可以有多个员工; 那在一个部门中加入List<Employee> employees这个字段就是一对多关系

1
2
3
/*Dept.java*/
// 4. 多表查询(一对多的关系); 依据deptId, 获取这个部门中所有员工的信息
List<Employee> selectEmployeesByDeptId(int deptId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--DeptMapper.xml-->
<resultMap id="selectEmployees" type="Dept">
<result column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<collection property="employees"
ofType="com.mts.mybatis.pojo.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="salary" property="salary"/>
<result column="dept_id" property="deptId"/>
</collection>
</resultMap>

<!--4. 多表查询(一对多的关系)-->
<select id="selectEmployeesByDeptId" resultMap="selectEmployees">
select e.id,e.last_name, e.email, e.salary,d.dept_id, d.dept_name
from tbl_employee e, tbl_dept d
where e.dept_id=d.dept_id
and d.dept_id=#{deptId};
</select>

4.4.3 分步查询[resultMap]

  • [association/collection]中的属性(分步查询需要的)

    1
    2
    3
    4
    5
    <association property="Pojo中的成员变量(属性)名"
    select="函数的全路径名(.java那个)"
    column="在嵌套的那边查询需要的成员变量"
    fetchType="eager|lazy 对于当前sql语句是否启用懒加载[优先级最高,不受全局setting的设置影响]">
    </association>
  • column:

    • 当另外一边只需要一个传入参数时 column="deptId"
    • 但当另一边需要多个参数传入时: column="{dId=deptId, eId=empId}"
一对一映射association

示例: 通过员工id获取员工信息及员工所属的部门信息【分步查询】

  1. 先通过员工id获取员工信息【id、last_name、email、salary、dept_id】
  2. 再通过部门id获取部门信息【dept_id、dept_name】
1
2
3
/*EmployeeMapper.java*/
// 3. [Association分步查询]
Employee selectEmpAndDeptByEmpIdAssociationStep(int empId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--EmployeeMapper.xml-->
<!--Association分步查询-->
<resultMap id="AssociationStep" type="employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="salary" property="salary"/>
<result column="dept_id" property="deptId"/>
<association property="dept"
select="com.mts.mybatis.mapper.DeptMapper.selectDeptByDeptId"
column="dept_id"
fetchType="eager">
</association>
</resultMap>

<select id="selectEmpAndDeptByEmpIdAssociationStep" resultMap="AssociationStep">
select id, last_name, salary, email, dept_id
from tbl_employee
where id = #{empId};
</select>
1
2
3
/*DeptMapper.java*/
// 3.分步查询
Dept selectDeptByDeptId(int deptId);
1
2
3
4
5
6
<!--DeptMapper.xml-->
<select id="selectDeptByDeptId" resultType="Dept">
select dept_id, dept_name
from tbl_dept
where dept_id=#{deptId};
</select>
一对多映射collection

示例: 通过dept_id获取部门信息及改部门的所有员工信息【分步查询】

  1. 通过deptId获取部门信息(deptId, deptName)
  2. 通过deptId获取员工信息(id, lastName, email, salary, deptId)
1
2
3
/*DeptMapper.java*/
// 5. 分步查询(一对多的关系)[collection]
Dept selectDeptByDeptIdStep(int deptId);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--DeptMapper.xml-->
<!--5.分步查询(一对多的关系)[collection]-->
<resultMap id="selectDeptStep" type="Dept">
<result column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<collection property="employees"
select="com.mts.mybatis.mapper.EmployeeMapper.selectEmpAndDeptByDeptIdCollectionStep"
column="dept_id">
</collection>
</resultMap>

<select id="selectDeptByDeptIdStep" resultMap="selectDeptStep">
select dept_id, dept_name
from tbl_dept
where dept_id=#{deptId};
</select>
1
2
3
/*EmployeeMapper.java*/
// 5.分步查询(一对多的关系)[collection]
List<Employee> selectEmpAndDeptByDeptIdCollectionStep(int deptId);
1
2
3
4
5
6
7
<!--EmployeeMapper.xml-->
<!--5.分步查询(一对多的关系)[collection]-->
<select id="selectEmpAndDeptByDeptIdCollectionStep" resultType="Employee">
select id, last_name, salary, email, dept_id
from tbl_employee
where id = #{dept_id};
</select>

调用过程与顺序

4.4.4 万能[resultType=”map”]

查询单行数据返回Map集合
  • resultType: map(小写)

  • 示例

    1
    2
    3
    /*EmployeeMapper.java*/
    // 3. [查询单行; 返回Map集合]: 依据id查询Employee(假设没有封装pojo)
    public Map<String, Object> selectOneReturnMap(int id);
    1
    2
    3
    4
    5
    6
    7
    <!--EmployeeMapper.xml-->
    <!--3. [查询单行; 返回Map集合]-->
    <select id="selectOneReturnMap" resultType="map">
    select id, last_name, email, salary
    from tbl_employee
    where id=#{id};
    </select>
    1
    2
    3
    // 3.[查询单行; 返回Map集合]
    Map<String, Object> map = employeeMapper.selectOneReturnMap(1);
    System.out.println(map);

    结果: image-20240620164034624

查询多行数据返回Map集合
  • resultType: map(小写)

  • 注意: 需要在函数那里利用@MapKey指定Key是谁

  • 示例:

    1
    2
    3
    4
    /*EmployeeMapper.java*/
    // 4. [查询多行; 返回Map集合]: 查询所有Employee(假设咩有封装pojo)
    @MapKey("id")
    public Map<Integer, Object> selectManyReturnMap();
    1
    2
    3
    4
    5
    <!--[查询多行; 返回Map集合]-->
    <select id="selectManyReturnMap" resultType="map">
    select id, last_name, email, salary
    from tbl_employee;
    </select>
    1
    2
    3
    4
    /*调用时*/
    // 4.[查询多行; 返回Map集合]
    Map<Integer, Object> map = employeeMapper.selectManyReturnMap();
    for(Map.Entry<Integer, Object> e: map.entrySet()) System.out.println(e);

    结果: image-20240620164936709

第五章 动态SQL

第一节 if where

  • where元素的作用:

    1. 子元素有内容(有进任何一个if/choose)才会插入”WHERE”

    2. 若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

  • 注意: ==会去除开头的AND或OR, 所以每个if中and都在最前面==

  • 使用场景: 查询员工信息, 条件不确定(传入一个Employee emp 其中emp不为空的字段都是需要查询的条件)

    1
    2
    3
    /*EmployeeMapper.java*/
    // [where if] 查询员工信息, 条件不确定(只要不为null就作为条件)
    List<Employee> selectEmployeeByOpt(Employee emp);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!--EmployeeMapper.xml-->
    <!--[where if] 查询员工信息, 条件不确定(只要不为null就作为条件)-->
    <select id="selectEmployeeByOpt" resultType="Employee">
    select id, last_name, email, salary, dept_id
    from tbl_employee
    <where>
    <!--where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。-->
    <!--where关键字, 每个if中and都在最前面-->
    <if test="id!=null">
    and id=#{id}
    </if>
    <if test="lastName!=null"> <!--成员变量(属性)-->
    and last_name=#{lastName}
    </if>
    <if test="email!=null">
    and email=#{email}
    </if>
    </where>
    </select>

第二节 trim

  • trim的元素的作用: 去除/添加一些字符

    • prefix:添加前缀;

    • prefixOverrides:去掉前缀;

    • suffix:添加后缀;

    • suffixOverride: 去掉后缀

  • where和trim的联系: <trim prefix="WHERE" prefixOverrides="AND |OR ">

    prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。如果写成and| or后面留空格, 则OR不起作用; and|or这样紧贴着写没问题

  • 使用场景: 使用场景: 查询员工信息, 条件不确定(使用trim而不是where)

    1
    2
    3
    /*EmployeeMapper.java*/
    // [trim] 查询员工信息, 条件不确定
    List<Employee> selectEmployeeUseTrim(Employee emp);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--EmployeeMapper.xml-->
    <!--[trim prefix:添加前缀; prefixOverrides:去掉前缀; suffix:添加后缀; suffixOverride: 去掉后缀]
    [trim] 查询员工信息, 条件不确定-->
    <select id="selectEmployeeUseTrim" resultType="Employee">
    select id, last_name, email, salary, dept_id
    from tbl_employee
    <trim prefix="where" prefixOverrides="and |or"> <!--和where元素等价的自定义trim元素为-->
    <if test="id!=null">and id=#{id}</if>
    <if test="lastName!=null">and last_name=#{lastName}</if>
    <if test="email!=null">and email=#{email}</if>
    </trim>
    </select>

第三节 set

  • set: set元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)

  • 注意:==会去除结尾的逗号, 所以所有的if中在结尾后面加逗号==

  • set和trim的联系:<trim prefix="SET" suffixOverrides=",">

  • 使用场景:更新员工信息, 需要更新的员工信息不确定(传入一个Employee emp 其中emp不为空的字段都是需要修改的字段)

    1
    2
    3
    /*EmployeeMapper.java*/
    // [set] 更新员工信息, 需要更新的员工信息不确定(只要不为null, 就更新)
    int updateEmployeeByOpt(Employee emp);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!--EmployeeMapper.xml-->
    <!--[set] 更新员工信息, 需要更新的员工信息不确定(只要不为null, 就更新)-->
    <update id="updateEmployeeByOpt">
    update tbl_employee
    <set> <!--会去除结尾的逗号, 所以所有的if后面加,-->
    <if test="deptId!=null">
    dept_id=#{deptId},
    </if>
    <if test="lastName!=null">
    last_name=#{lastName},
    </if>
    <if test="salary!=null">
    salary=#{salary},
    </if>
    </set>
    where id=#{id}
    </update>

第四节 choose, when, otherwise

  • choose, when, otherwise: 类似于JSTL

  • 注意:

  • 使用场景: 查询员工信息, 只查询Employee字段中第一个不为空的字段, 如果全为null, 查询所有

    1
    2
    3
    /*EmployeeMapper.java*/
    // [choose, when, otherwise] 查询员工信息, 只查询一个条件, 如果都为null, 返回全部
    List<Employee> selectEmployeeByOneOpt(Employee emp);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!--EmployeeMapper.xml-->
    <!--[choose, when, otherwise] 查询员工信息, 只查询一个条件, 如果都为null, 返回全部-->
    <select id="selectEmployeeByOneOpt" resultType="Employee">
    select id, last_name, email, salary, dept_id
    from tbl_employee
    <where>
    <choose>
    <when test="deptId!=null">
    dept_id=#{deptId}
    </when>
    <when test="salary!=null">
    salary=#{salary}
    </when>
    <otherwise>
    1=1
    </otherwise>
    </choose>
    </where>
    </select>

第五节 foreach

  • foreach: 在需要循环的地方加入比如, 插入多个, 查询在一个集合中的等

  • foreach的属性:

    • collection: for(Auto item: collection)
    • item: for(Auto item: collection)
    • separator: where id in (1,2,3,4,5) 就是这个逗号
    • index: 当循环是map对象时, index为key, item为value<index: item>
    • open: 加在整个循环最前面where id in (1,2,3,4,5)1前面的(, 并不是每个都加
    • close: 加在整个循环最后面, same as above
  • 注意: 关于List, Set, Map入参时collection应该写什么,List入参可以写[list/collection], Set入参可以写collection, Map入参只能用@Param命名参数; 注意当你忘记要写什么时------@Param或懒一点arg0

  • 使用场景:

    • 场景1: 查询员工id在集合ids中的所有员工信息

      1
      2
      3
      select id, last_name, email, salary, dept_id
      from tbl_employee
      where id in(1,2,3)
      1
      2
      3
      /*EmployeeMapper.java*/
      // [foreach] 查询员工id在集合ids中的所有员工信息
      List<Employee> batchSelectEmployee(@Param(value = "ids") List<Integer> ids);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <!--EmployeeMapper.xml-->
      <!--[foreach] 查询员工id在集合ids中的所有员工信息-->
      <select id="batchSelectEmployee" resultType="Employee">
      select id, last_name, email, salary, dept_id
      from tbl_employee
      where id in
      <foreach open="(" collection="ids" item="id" separator="," close=")">
      #{id}
      </foreach>
      </select>
    • 场景2: 传入map查询满足<id, dept_id>的员工信息

      1
      2
      3
      SELECT * FROM tbl_employee
      WHERE id IN (1, 3, 4)
      AND dept_id IN (1, 2)
      1
      2
      3
      /*EmployeeMapper.java*/
      // [foreach] map查询满足<id, dept_id>的员工信息
      List<Employee> mapSelectEmployee(@Param(value = "info")Map<Integer, Integer> info);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <!--EmployeeMapper.xml-->
      <!--[foreach] map查询满足<id, dept_id>的员工信息-->
      <select id="mapSelectEmployee" resultType="Employee">
      SELECT * FROM tbl_employee
      WHERE id IN
      <!--这里的循环, 看起来是分开的, 但其实是一一对应的, 不是单查;sql语法-->
      <foreach collection="info" item="value" index="key" open="(" separator="," close=")">
      #{key}
      </foreach>
      AND dept_id IN
      <foreach collection="info" item="value" open="(" separator="," close=")">
      #{value}
      </foreach>
      </select>
    • 场景3: 批量插入员工信息 (注意item是每个员工, 所以取里面的成员变量(属性)要emp.lastName)

      1
      2
      3
      4
      insert into tbl_employee(last_name, email, salary, dept_id)
      value ("Tom", "[email protected]",88.5,1),
      ("Jerry", "[email protected]",63.2,3),
      ("Kate", "[email protected]",43.3,2),
      1
      2
      3
      /*EmployeeMapper.java*/
      // [foreach] 批量插入员工信息
      int batchInsertEmployee(@Param(value = "employees") List<Employee> employees);
      1
      2
      3
      4
      5
      6
      7
      8
      9
      <!--EmployeeMapper.xml-->
      <!--[foreach] 批量插入员工信息-->
      <insert id="batchInsertEmployee" >
      insert into tbl_employee(last_name, email, salary, dept_id)
      value
      <foreach collection="employees" item="emp" separator=",">
      (#{emp.lastName}, #{emp.email}, #{emp.salary}, #{emp.deptId})
      </foreach>
      </insert>

第六节 重用SQL片段

  • 重用SQL片段: 映射文件中的子标签之一

  • 使用场景: 我们不提倡select *, 但有时候字段又很多, 此时可以用sql片段提取, 后期只需要include即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <sql id="emp_col"> <!--可重用SQL片段-->
    id,
    last_name,
    email,
    salary
    </sql>

    <select id="queryAll" resutlType="Emloyee">
    select <include refid="emp_col"></include>
    from tbl_employee
    </select>

第六章 缓存机制

第一节 一级缓存

  • 别名:【本地缓存(Local Cache)或 SqlSession级别缓存】

  • 特点

    • 一级缓存默认开启
    • 不能关闭
    • 可以清空
  • 缓存原理

    • 第一次获取数据时,先从数据库中加载数据,将数据缓存至Mybatis一级缓存中【缓存底层实现原理Map,key:hashCode+查询的SqlId+编写的sql查询语句+参数】
    • 以后再次获取数据时,先从一级缓存中获取,如未获取到数据,再从数据库中获取数据。
  • 一级缓存五种失效情况

    1. 不同的SqlSession对应不同的一级缓存

    2. 同一个SqlSession但是查询条件不同

    3. 同一个SqlSession两次查询期间执行了任何一次增删改操作都会==清空全部一级缓存的内容==

    4. 同一个SqlSession两次查询期间手动清空了缓存sqlSession.clearCache()

    5. 同一个SqlSession两次查询期间提交了事务sqlSession.commit()

  • 注意事项:

    • 注意这里不管table的engine是InnoDB还是MyISAM等, 不管是否有事务系统, 只要执行了sqlSession.commit就是清除一级缓存;
    • 同理期间只要进行了增删改的操作, 无论是否提交到了数据库, 都会清空一级缓存

第二节 二级缓存

  • 别名: 【全局作用域缓存 或 SqlSessionFactory级别缓存】

  • 特点

    • 二级缓存默认关闭,需要开启才能使用
    • 二级缓存需要sqlSession.commit()sqlSession.close()时,才会将一级缓存的内容转移到二级缓存
  • 缓存原理

    • 第一次获取数据时,先从数据库中获取数据,将数据缓存至一级缓存;当提交或关闭SqlSession时,将数据缓存至二级缓存
    • 以后再次获取数据时,先从一级缓存中获取数据,如一级缓存没有指定数据,再去二级缓存中获取数据。如二级缓存也没有指定数据时,需要去数据库中获取数据。
  • 二级缓存的失效情况

    • 在两次查询之间,执行增删改操作,会同时==清空一级缓存和二级缓存==
  • 注意事项:

    • sqlSession.clearCache():只是用来清除一级缓存。二级缓存只能被刷新(通过设置flushInterval属性), 不能被清空(但是你关闭cacheEnabled相当于清空了)
  • 如何开启二级缓存:

    1. ① 全局配置文件中开启二级缓存<setting name="cacheEnabled" value="true"/>(默认开启)

    2. ② 需要使用二级缓存的映射文件处使用cache元素(子标签)配置缓存<cache/>如下是其属性

      • eviction=“FIFO”:缓存清除【回收】策略。

        • LRU – 最近最少使用的:移除最长时间不被使用的对象。
        • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
      • flushInterval:刷新间隔,单位ms

      • size:引用数目,正整数(建议512的倍数)

      • readOnly:只读,true/false

      <cache eviction="FIFO" flushInterval="60000" size="1024"/>

    3. ③ 注意:POJO需要实现Serializable接口

[^如何开启序列号alter+enter开启fix自动生成UID]: 开启后在类继承了Serializable接口后, 在类名后面alter+enter自动生成UID

第三节 第三方缓存

  • 作用: 可以本地持久化存储

  • 常用的第三方缓存: EhCache (EhCache 是一个纯Java的进程内缓存框架)

  • 开启步骤

    1. ①开启二级缓存, 只有开启二级缓存才能使用第三方缓存

    2. ② EhCache的maven导包

      • 导入jar包

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        <!-- mybatis-ehcache -->
        <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.0.3</version>
        </dependency>

        <!-- slf4j-log4j12 -->
        <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.6.2</version>
        <scope>test</scope>
        </dependency>
      • 编写配置文件【ehcache.xml】

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        <?xml version="1.0" encoding="UTF-8"?>
        <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
        <!-- 磁盘保存路径 -->
        <diskStore path="D:\桌面文件夹\专业学习\Mybatis学习\mybatis\ehcache" />

        <defaultCache
        maxElementsInMemory="512"
        maxElementsOnDisk="10000000"
        eternal="false"
        overflowToDisk="true"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
        </defaultCache>
        </ehcache>
    3. ③ mybatis映射文件中

      1
      2
      <!--开始第三方缓存(ehcache) 不需要其它属性了-->
      <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  • 注意事项

    • 第三方缓存,需要建立在二级缓存基础上【需要开启二级缓存,第三方缓存才能生效】
    • 如何让第三方缓存失效【将二级缓存设置失效即可】

第七章 逆向工程【插件】【了解】

正向工程: java中的pojo=>数据库中的(表)
逆向工程: Table=>Pojo, XXXMapper.java, XXXMapper.xml

第一节 MyBatis Generator插件

  • 官网地址: https://mybatis.org/generator/
  • 简介: 是一个专门为MyBatis框架使用者定制的代码生成器, 可以快速的更具表生成对应的映射文件, 接口, pojo。但只可以生成单表的CRUD, 表链接, 存储过程等复杂sql的定义还是需要我们手动编写

第二节 具体实现步骤

7.2.1 STEP1: 配置pom.xml

1
2
3
4
5
6
<!-- mybatis-generator-core -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.6</version>
</dependency>

7.2.2 STEP2: 编写配置文件【mbg.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
28
29
30
31
<!DOCTYPE generatorConfiguration PUBLIC
"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
id属性:设置一个唯一标识
targetRuntime属性值说明:
MyBatis3Simple:基本的增删改查
MyBatis3:带条件查询的增删改查[QBC风格(Query By Criteria]
-->
<context id="simple" targetRuntime="MyBatis3Simple">
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/test_mybatis"
userId="root"
password="root">
<!--数据库8.0的要加上这句(因为驱动升级的缘故)-->
<property name="nullCatalogMeansCurrent" value="true"/>
</jdbcConnection>
<!--javaBean[POJO]的生成策略 targetPackage="生成到哪个包下" targetProject="在哪个具体的项目中"
最终生成地址: src/main/java/mybatis/pojo/Employee.java-->
<javaModelGenerator targetPackage="mybatis.pojo" targetProject="src/main/java"/>
<!--映射文件[XXXMapper.xml ]的生成策略 最终生成地址:src/main/resources/mapper/XXXMapper.xml-->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"></sqlMapGenerator>
<!--接口文件[XXXMapper.java]的生成策略 最终生成地址:src/main/java/mybatis/mapper/XXXMapper.java-->
<javaClientGenerator type="XMLMAPPER" targetPackage="mybatis.mapper" targetProject="src/main/java"/>

<!--tableName="需要生成的表名" domainObjectName="pojo名"-->
<table tableName="tbl_dept" domainObjectName="Dept"/>
<table tableName="tbl_employee" domainObjectName="Employee"/>
</context>
</generatorConfiguration>

7.2.3 STEP3: 运行

1
2
3
4
5
6
7
8
9
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("src/main/resources/mbg.xml"); // 指定MBG配置文件的文件路径
// 注意JavaSE的File是相对于整个项目的路径, copy时要copy"path from Content Root"
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);

第八章 分页查询【插件】【重点】

在Mybatis中分页是用插件来实现的; 分页的好处 ①提高用户体验 ②降低服务器压力

第一节 设计Page类

46/60 46: 当前页码 60: 总页码
select * from tbl_employee where 1=1 limit x, y – x: 开启的下标[0开始], y: 每页显示数量

  • pageNum:当前页码
  • pages:总页数【计算:总页数=⌈总数据数量/每页显示数据数量⌉】
  • total:总数据数量
  • pageSize:每页显示数据数量
  • List:当前页显示数据集合

第二节 PageHelper插件

第三节 具体实现步骤

8.3.1 STEP1: 配置pom.xml

1
2
3
4
5
6
<!-- 分页: PageHelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>

8.3.2 STEP2: 在mybatis-config.xml中配置分页插件

1
2
3
4
5
6
7
8
<typeAliases></typeAliases>
....
<plugins>
<!--配置PageHelper分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
....
<environments></environments>

8.3.3 STEP3: 查询之前, 使用PageHelper开启分页

  • 这里接收page后, 可以通过page获取上面”设计Page类”中的一些分页数据
1
Page<Object> page = PageHelper.startPage(6, 3);// 注意这里的第一个参数是第几页(不是Limit的第一个参数)

8.3.4 STEP4: 查询之后, 最后将结果封装Pagelnfal中,使用Pagelnfo实现后续分页效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
List<Employee> employees = employeeMapper.selectAll(); //执行查询

//查询之后,封装PageInfo
PageInfo<Employee> pageInfo = new PageInfo<>(employees, 5); //第二个参数用于指定导航栏显示页码个数(default:8)
//分页需要数据如下
System.out.println(pageInfo.getPageNum() + "/" + pageInfo.getPages());
System.out.println("总数据数量:" + pageInfo.getTotal());
System.out.println("每页显示数据数量:" + pageInfo.getPageSize());
System.out.println("当前页显示数据集合:");
pageInfo.getList().forEach(System.out::println);
System.out.println("是否有上一页: "+pageInfo.isHasPreviousPage());
System.out.println("上一页是:"+pageInfo.getPrePage());
System.out.println("是否有下一页:"+pageInfo.isHasNextPage());
System.out.println("下一页是:"+pageInfo.getNextPage());
System.out.println("是否是第一页: "+pageInfo.isIsFirstPage());
System.out.println("是否是最后一页: "+pageInfo.isIsLastPage());
System.out.println("导航页的第一个页码是: "+pageInfo.getNavigateFirstPage());
System.out.println("导航页的最后一个页码是:"+pageInfo.getNavigateLastPage());
System.out.println("导航页的总页码是 :"+pageInfo.getNavigatePages());
  • Title: 3.Mybatis学习
  • Author: 明廷盛
  • Created at : 2025-02-15 21:19:10
  • Updated at : 2025-02-15 21:34:00
  • Link: https://blog.20040424.xyz/2025/02/15/😼Java全栈工程师/第三部分 SSM/3.Mybatis学习/
  • License: All Rights Reserved © 明廷盛