MyBatis

一、框架概述

什么是框架

框架对通用的代码的封装,通过使用框架,提高开发效率,而不需要关心一些繁琐的、复杂的底层代码实现,把更多的经历用于所在需求的实现上。

框架可以理解为一个半成品,我们选用这个半成品,然后加上业务需求来最终实现整个功能。

软件开发的分层

在我们进行程序设计以及程序开发时,尽可能让每一个接口、类、方法的职责更单一(单一原则)

单一原则:一个类或者一个方法,就只做一件事情,只管一个功能。这样就可以让类、接口、方法的复杂度更低,可读性更强、扩展性更好,也便于后期的维护。

以前我们写代码,从组成可以分成三个部分:

  • 数据访问:负责业务数据的维护操作
  • 逻辑处理:负责业务逻辑处理的代码
  • 请求处理:接受请求,给页面响应数据

在我们项目开发中,将代码分为三层:

  1. 前端发起的请求,由controller层接收,控制器响应数据给前端
  2. controller层调用service层进行逻辑处理,service层处理后,把处理结果返回给controller层
  3. dao层操作底层的数据,负责拿到数据返回给service层

分层就是分工,划分环节,通过分层架构的设计,使代码的职责分明,容易理解和维护,还能实现代码的重用,提高系统的整体性能和扩展性,同时各层之前的结构也很方便,提高代码的质量和稳定性。

通过分层更好的实现各个部分的职责,在每一层将再细化出不同的矿界,分别解决各层关注的问题。

分层开发下的常见框架

  • 表现层:指的是用户直接与应用进行交互的部分,负责接收用户请求、处理业务逻辑、返回响应结果。表现层框架的主要任务是将业务逻辑与用户进行交互,提供良好的用户体验。
  • 业务逻辑层:也称为服务层,负责处理应用的业务逻辑。它负责协调不同组件之间的工作,执行复杂的业务规则和计算。业务逻辑层框架的主要任务是封装业务逻辑,提供可重复使用的业务功能。
  • 数据访问层:也称为持久层,负责与数据库或其他数据存储进行交互。它负责执行数据库操作,如查询、插入、更新和删除数据。数据访问层框架的主要任务是提供统一的接口,隐藏底层数据库的细节,使业务逻辑层能够独立于具体的数据库实现。

二、Mybatis框架

什么是mybatis

MyBatis 是一款优秀的持久层框架。

采用ORM思想解决实体和数据库映射的问题,对JDBC 进行了封装,屏蔽了JDBC API底层访问细节,使我们不用与JDBC API打交道,就可以完成对数据库的持久化操作。

Mybatis本来是apache的一个开源项目IBatis,2010年的这个项目由apache改名MyBatis。

  • 持久层:指的是数据访问层dao,是用来操作数据库的
  • 框架:是一个半成品软件,再框架的基础上进行软件开发,更加高效,规范,可扩展。

ORM:对象关系映射,实现了面向对象编程语言中对象与关系型数据库中的表之间的映射,ORM框架允许开发者使用面向对象的方式来操作数据库,而无需关心底层的SQL语句。

具体来说:

  1. ORM框架将数据库中的表(table)映射为编程语言中的类(class)
  2. 表中的记录映射为类的实例
  3. 字段映射为对象的属性

Mybatis框架自定义sql语句

  1. 什么情况下需要自定义sql语句

    情况一:需要实现的功能超出了BaseMapper父接口的能力范围

    框架提供的BaseMapper父接口中只实现了【单表】和【CRDU】操作,支持条件构造器和分页器功能,但是要是想实现复杂功能比如
  • 多表关联查询
  • 复杂统计
  • 特殊SQL
    等等

    情况二:项目使用的是基础版本的Mybatis,而不是MP

    基础版本的Mybatis没有提供BaseMapper,MP版本的在基础版本的功能基础上提供了BaseMapper,所以MP版本的Mybatis更方便。
    如果是基础版本的mybatis,单表操作都需要自己写sql

    一个自动化高,一个半自动灵活性高

JDBC编程的分析

  1. 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
  2. 因为SQL语句的where条件不一定,可能多可能少,修改SQL还要i需改代码,系统不容易维护。
  3. 对结果集解析步骤相对繁琐。

自定义sql写在哪里呢?

SQL语句不写在java代码中,而是写在XML映射文件中。
xml放在resources资源目录中
其xml文件名需要和对应的接口同名,目录与接口的包同名

Mybatis入门案例

1). pom.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
<dependency>
<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>3.8.1</version>

</dependency>

<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.46</version>

</dependency>

<!--MyBatis核心包-->
<dependency>
<groupId>org.mybatis</groupId>

<artifactId>mybatis</artifactId>

<version>3.5.5</version>

</dependency>

2). 编写实体类

1
2
3
4
5
6
public class Dept {
private int deptNo;
private String dname;
private String loc;
// 省略get/set及toString方法
}

3). 编写持久层接口

1
2
3
4
public interface IDeptDao {
// 查询所有部门信息
List<Dept> findAll();
}

XML配置文件实现

【1】创建XML映射文件

要求:

  • 创建位置必须与持久层接口在相同的包中
  • 名称:必须以持久层接口名称命名文件名

【2】编写XML映射文件

xml映射文件中的DTD约束,直接从mybatis官网复制即可,或者直接AI生成

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

【3】配置

在mybatis-config.xml配置文件中,需要配置mapper映射文件的位置,告诉mybatis去哪里找到对应的SQL语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- XML映射文件的namespace属性为dao接口全限名 包名.类名 -->
<mapper namespace="com.iweb.dao.IDeptDao">

<!-- 配置查询所有操作
id属性为方法名
resultType属性为返回值类型 报名.类名 区分大小
-->
<select id="findAll" resultType="com.iweb.bean.Dept">
select * from dept
</select>
</mapper>

【4】配置mybatis-config.xml配置文件

核心配置文件主要用于配置数据库的环境以及mybatis的全局配置信息,存放的位置src/main/resources目录下

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
    <?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>

<!-- 配置mybatis的环境 -->
<environments default="mysql">
<!-- 配置mysql的环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>

<!-- 配置数据库信息,用的是连接池的数据源 -->
<dataSource type="POOLED">
<!--配置连接池需要的参数-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>

</environment>

</environments>


<!-- 告诉mybatis映射文件的位置 -->
<mappers>
<mapper resource="com/iweb/dao/DeptDao.xml"></mapper>

</mappers>

</configuration>

以后,这个配置文件可以省略

【测试】

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
    package com.iweb.test;

import com.iweb.bean.Dept;
import com.iweb.dao.IDeptDao;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.InputStream;


public class TestMyBatis {

@Test
public void testFindAll() throws Exception{
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sqlSessionFactory构建者对象
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(in);
//3.使用SqlSessionFactory生成sqlSession对象
SqlSession session = build.openSession();
//4.使用session创建dao接口的代理对象
IDeptDao deptDao = session.getMapper(IDeptDao.class);
//5.使用代理对象执行查询所有的方法
for (Dept dept : deptDao.findAll()) {
System.out.println(dept.toString());
}
//6.释放资源
session.close();
in.close();
}
}

XML映射配置

mybatis的开发有两种方式:

  1. 注解
  2. XML

XML配置文件规范

使用mybatis的注解方式,主要是完成一些简单的增删改查功能,如果需要实现复杂的SQL功能,建议使用XML配置来配置映射语句,也就是将SQL语句写在XML配置文件中。

在mybatis中使用XML映射文件方式开发,需要符合一定的规范:

  1. XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)
  2. XML映射文件的namespace属性为Mapper接口全限定名一致
  3. XML映射文件中SQL语句的id属性值与Mapper接口中的方法名一致,并保持返回类型一致

总结
通过上述案例,我们发现使用mybatis是非常容易的事情,只需要编写Dao接口并且按照mybatis的要求编写两个配置文件,就可以实现功能。

接口和xml文件是如何绑定的呢

接口与XML文件绑定是通过xml文件的根标签的namespace属性一致。
接口方法与xml文件中的SQL语句的id属性值一致。

基于注解的mybatis使用

1). 在持久层接口中添加注解

1
2
3
4
5
    public interface IDeptDao {
// 查询所有部门信息
@Select("select * from dept")
List<Dept> findAll();
}

2). 修改mybatis-config.xml
1
2
3
4
5
 <!-- 告诉mybatis映射文件的位置 -->
<mappers>
<mapper class="com.iweb.dao.IDeptDao"></mapper>

</mappers>

注意事项

在使用基于注解的mybatis配置时,需要移除xml的映射配置

mybatisX的使用

mybatis是一款基于DIEA的快速开发mybatis的插件,为效率而生。

xml与接口互跳
我们点击dao接口方法左侧的图标可以直接跳转到dao.xml对应的SQL实现,在dao.xml点击左侧图标也可以直接跳转到dao接口中对应的方法。

日志技术

日志就用来记录程序运行信息,状态信息,错误信息的。

mybatis日志框架

mybatis没有直接依赖具体的日志实现,而是通过内置的日志抽象层来桥接不同的日志框架。

mybatis支持的日志框架(优先级别):

  • SLF4J
  • LOG4J 2
  • LOG4J
  • JDK
  • LOG4J

LOG4J是一个流行的日志框架,提供了灵活的配置选项,支持多种输出目标。

【1】引入log4j的依赖

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

【2】在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择日志实现工具
1
2
3
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>

【3】添加日志控制文件
1
2
3
4
5
6
7
8
# 配置全局的日志输出级别debug->info->warn->error
# 设置日志输出源 stdout 输出到控制台
log4j.rootLogger=debug, stdout
# 配置日志相关的信息
log4j.logger.org.mybatis.example.BlogMapper=TRACE
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

代理Dao实现CRUD操作

查询

需求:依据部门编号查询部门信息

1)在持久层添加findByDeptId方法

1
2
// 根据部门编号查询部门信息
Dept findByDeptId(int deptNo);

2)在deptDao.xml的映射配置见配置查询方法
1
2
3
<select id="findByDeptId" resultType="com.iweb.bean.Dept" parameterType="int">
select * from dept where deptno = #{deptNo}
</select>

属性说明

  • resultType属性:用于指定返回结果集的类型
  • parameterType属性:用于指定传入参数的类型
  • sql语句种使用#{}:在mybatis中我们可以通过占位符#{..}来占位,在调用findByDeptId方法时,传递参数只,最终会替换占位符。

3)测试

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
    package com.iweb.test;
public class TestMyBatis {
private InputStream in;
private SqlSessionFactory build;
private SqlSession session;
private IDeptDao deptDao;

@After // 在测试方法执行完成之后执行
public void destroy() throws Exception{
session.commit();
session.close();
in.close();
}

@Before // 在测试方法执行之前完成执行
public void init() throws Exception{
//1.读取配置文件
in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sqlSessionFactory构建者对象
build = new SqlSessionFactoryBuilder().build(in);
//3.使用SqlSessionFactory生成sqlSession对象
session = build.openSession();
//4.使用session创建dao接口的代理对象
deptDao = session.getMapper(IDeptDao.class);
}


@Test
public void testFindByDeptId(){
Dept dept = deptDao.findByDeptId(3);
System.out.println(dept);
}

@Test
public void testFindAll() throws Exception{
//5.使用代理对象执行查询所有的方法
for (Dept dept : deptDao.findAll()) {
System.out.println(dept.toString());
}
}
}

添加

1). 在持久层中添加新增方法

1
2
// 新增部门
int saveDept(Dept dept);

2). 在映射配置文件中配置新增方法
1
2
3
4
5
 <!-- 添加 -->
<insert id="saveDept" parameterType="com.iweb.bean.Dept">
insert into dept(dname,loc) values(#{dname},#{loc})
</insert>


parameterType属性:代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写全类名

#{}中内容的写法:由于我们保存方法的参数是一个Dept对象,此处需要写Dept对象中的属性名称。它用的是OGNL表达式:

OGNL表达式

它是apache提供的一种表达式语言,全程是对象图导航语言

#{dept.dname}它会先去找dept对象,然后在找到dept对象中的dname属性,并调用getDname()方法把值取出来。但是我们在parameterType属性上指定了实体类名称,所以可以省略dpet.,而直接写dname即可。

1
2
3
4
5
6
7
8
9
  @Test
public void testSaveDept(){
Dept dept=new Dept();
dept.setDname("市场部");
dept.setLoc("长沙");
// 执行保存方法
int i = deptDao.saveDept(dept);
System.out.println(i+"条受影响");
}

我们在实现增删该时一定要去控制事务的提交,那么mybatis是如何控制事务提交的?可以使用session.commit()来提交事务。

#{}和${}的区别

  • #{}表示一个占位符通过#{}可以实现PreparedStatement向占位符中设置值,自动进行Java类型和Jdbc类型转换,#{}可以有效防止SQL注入。#{}可以接受简单类型值和POJO属性值,如果parameterType传输单个简单类型值,#{}扩种可以是value或者其他名字。
  • ${}表示拼接SQL通过${}可以将parameterType传入的内容拼接在SQL中且不进行Jdbc类型转换,${}可以接受简单类型值或者POJO属性值,如果parameterType传输单个简单类型值,${}括号只能是value。

说人话就是#{}是mybatis提供的占位符,可以有效防止SQL注入,而${}是java提供的占位符,使用的是拼接方法,不能有效防止SQL注入。

如何获取新增id的返回值?

新增部门后,同时还有返回当前新增部门的id值,因为id属性值是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长的值返回

1
2
3
<insert id="saveDept" useGeneratedKeys="true" keyProperty="deptNo" parameterType="com.iweb.bean.Dept">
insert into dept(dname,loc) values(#{dname},#{loc})
</insert>

属性说明

  • useGeneratedKeys=true: 在执行添加记录之后可以获取到数据库自动生成的主键ID,在自动获取到主键后,需要设置返回的主键对象。
  • keyProperty:设置的值为java对象主键的属性,指定主键id值存放的属性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
      @Test
    public void testSaveDept(){
    Dept dept=new Dept();
    dept.setDname("市场部");
    dept.setLoc("长沙");
    // 执行保存方法
    deptDao.saveDept(dept);
    System.out.println("插入的主键--->"+dept.getDeptNo());
    }

    修改

    1
    2
    // 修改部门信息
    int updateDept(Dept dept);
    1
    2
    3
    4
    5
     <!-- 修改 -->
    <update id="updateDept" parameterType="com.iweb.bean.Dept">
    update dept set dName=#{dname},loc=#{loc} where detpNo=#{deptNo}
    </update>

    测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void testUpdateDept(){
    Dept dept=new Dept();
    dept.setDeptNo(5);
    dept.setDname("销售部");
    dept.setLoc("广州");
    int result = deptDao.updateDept(dept);
    System.out.println(result+"行受影响");
    }

    删除

    1
    2
     // 删除
    int delDeptByDeptNo(int deptNo);
    1
    2
    3
    4
    <!-- 删除 -->
    <delete id="delDeptByDeptNo" parameterType="int">
    delete from dept where deptno=#{deptNo}
    </delete>
    测试
    1
    2
    3
    4
    5
    @Test
    public void testDelDeptByDeptNo(){
    int i = deptDao.delDeptByDeptNo(5);
    System.out.println(i+"条受影响");
    }

    模糊查询

【方式一】

在dao接口中直接使用#{}

1
2
// 模糊查询方式一
List<Dept> findByDeptName(String dname);

映射文件配置
1
2
3
4
<!-- 模糊查询一 -->
<select id="findByDeptName" resultType="com.iweb.bean.Dept" parameterType="string">
select * from dept where dname like #{dname}
</select>

测试方法
1
2
3
4
5
6
7
  @Test
public void testLikeByDeptName(){
List<Dept> depts = deptDao.findByDeptName("%部%");
for (Dept dept : depts) {
System.out.println(dept);
}
}

我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识符%。

【方式二】

在xml中使用concat函数

如果你不想在java代码中拼接字符串,可以在xml映射文件中的SQL中使用concat函数来拼接百分比符号和参数。

1
2
3
4
5
<!-- 模糊查询二 -->
<select id="findByDeptName" parameterType="string" resultType="com.iweb.bean.Dept">
select * from dept where dname like concat('%',#{deptName},'%')
</select>


测试
1
2
3
4
5
6
7
@Test
public void testLikeByDname(){
List<Dept> depts = deptDao.findByDeptName("运营");
for (Dept dept : depts) {
System.out.println(dept);
}
}

【方式三】

使用${}进行拼接(不推荐)

虽然可以使用${}进行字符串拼接以实现like查询,但是这种方式容易导致SQL注入攻击,因此不推荐使用。

1
2
3
4
   <!-- 模糊查询三 -->
<select id="findByDeptName" parameterType="string" resultType="com.iweb.bean.Dept">
select * from dept where dname like '%${value}%'
</select>

我们在上面将原来的#{}占位符改成了${value},如果用这种方式的模糊查询,那么${value}的写法是固定的,不能改成其他的名字。

测试

1
2
3
4
5
6
7
 @Test
public void testLikeByDname(){
List<Dept> depts = deptDao.findByDeptName("部");
for (Dept dept : depts) {
System.out.println(dept);
}
}

面试题 #{} vs ${}区别?

  • #{}表示一个占位符通过#{}可以实现PreparedStatement向占位符中设置值,自动进行Java类型和Jdbc类型转换,#{}可以有效防止SQL注入。#{}可以接受简单类型值和POJO属性值,如果parameterType传输单个简单类型值,#{}扩种可以是value或者其他名字。
  • ${}表示拼接SQL通过${}可以将parameterType传入的内容拼接在SQL中且不进行Jdbc类型转换,${}可以接受简单类型值或者POJO属性值,如果parameterType传输单个简单类型值,${}括号只能是value。

聚合查询

1
2
// 聚合函数
int findTotal();

映射配置文件

1
2
3
4
<!-- 聚合函数 -->
<select id="findTotal" resultType="int">
select count(*) from dept
</select>

测试
1
2
3
4
5
@Test
public void testFindTotal(){
int total = deptDao.findTotal();
System.out.println(total);
}

四、mybatis的参数深入

parameterType配置参数

SQL语句传参,使用标签的parameterType属性设置。属性的取值可以是基本类型,引用类型(String),还可以是实体类类型(POJO),同时也可以是实体类的包装类。

传递POJO包装对象

开发中通过POJO传递查询条件,查询条件是综合的查询条件,不仅包括用户查询条件还可以包括其他的查询条件(比如用户购买的商品信息也作为查询条件),这时可以使用包装类对象传递输入参数,POJO类中包含POJO

需求:根据部门名称查询部门查询,查询条件放到QueryVo的Dept属性中。

1). 编写paramVO

1
2
3
4
5
6
7
8
9
10
11
12
public class ParamVO {

private Dept dept;

public Dept getDept() {//这里是类中嵌入一个类
return dept;
}

public void setDept(Dept dept) {
this.dept = dept;
}
}

2). 在持久层接口中添加方法
1
2
// 包装类作为参数
List<Dept> findByVo(ParamVO vo);

3). 映射配置
1
2
3
<select id="findByVo" resultType="com.iweb.bean.Dept" parameterType="com.iweb.bean.ParamVO">
select * from dept where dname like #{dept.dname}
</select>

4). 测试包装类作为参数
1
2
3
4
5
6
7
8
9
10
11
@Test
public void testFindParamVo(){
ParamVO paramVO=new ParamVO();
Dept d=new Dept();
d.setDname("%部%");
paramVO.setDept(d);
List<Dept> depts = deptDao.findByVo(paramVO);
for (Dept dept: depts) {
System.out.println(dept);
}
}

五、输出结果封装

resultType配置结果

resultType的主要作用是告诉mybatis如何将数据库查询的结果集(resultSet)转换为java对象,它告诉mybatis每一行结果应该映射到哪个类型的java对象,还有一个要求,实体类属性名和数据库表中的返回的字段名一致,mybatis才会自动封装。

如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装。

特殊情况示例

实体类属性和数据库表的列名已经不一致了

resultMap结果类型

resultMap标签可以建立查询的列名和实体类的属性名不一致时建立对应关系,从而实现封装。

在select标签中使用resultMap属性指定引用即可。同时resultMap可以实现将查询结果映射为复杂的POJO,比如在查询结果映射对象中包括POJO和List实现一对一查询或者一对多查询。

1). 定义resultMap

1
2
3
4
<resultMap id="唯一标识" type="指定查询结果映射的java类型">
<id property="实体类中主键属性名称" column="数据库表中主键字段的名称"/>
<result property="实体类中非主键属性名称" column="数据库表中非主键字段的名称"/>
</resultMap>
1
2
3
4
5
6
7
8
9
10
<!--  定义Dept实体类和数据库表中字段的对应关系
-->
<resultMap id="deptMap" type="com.iweb.bean.Dept">
<id property="deptNo" column="deptid"></id>

<result property="dname" column="deptname"></result>

<result property="loc" column="address"></result>

</resultMap>

属性说明

  • id属性:唯一的标识,将来是给查询select标签去引用的
  • type属性:指定查询结果映射的java类型
  • id标签:用于指定主键字段
    • property属性:指定实体类中属性名称
    • column属性:指定数据库表中字段的名称
  • result标签:用于指定非主键字段

2). 映射配置

1
2
3
<select id="findAll"  resultMap="deptMap">
select * from dept
</select>

3)测试
1
2
3
4
5
6
7
8
@Test
public void testFindAll() throws Exception{
//5.使用代理对象执行查询所有的方法
List<Dept> depts = deptDao.findAll();
for(Dept dept : depts){
System.out.println(dept);
}
}

resultMap和resultType的区别

resultType是自动映射查询结果

  • resultType是select标签的属性,用于指定映射查询结果的类型
  • resultType的取值是自定义的java类型
  • 自动映射:框架自动将查询结果映射到java对象,无需编写映射规则
  • 适用于单表查询,列名和属性名一致
  • 不适用可与一对多连表查询,命名不一致
    resultMap是手动映射查询结果
  • resultMap是select标签的另一个属性,用于指定映射查询结果的规则
  • resultMap需要额外编写映射规则,resultType不需要
  • resultMap的取值是映射规则的ID名字,必须是唯一的
  • 支持手动执行负责映射规则
    • 适用于列名和属性名不一致的情况
    • 可以实现一对一查询和一对多查询

自动映射级别

NONE(最低级别)

- 完全关闭自动映射,只映射指定了映射规则的字段

PARTIAL(默认级别)

- 自动映射简单属性
- 不自动映射嵌套对象或者集合

FULL(最高级别)

- 完全自动映射,包含嵌套的对象和集合也自动映射
- 容易产生误映射

总结:

  • 单表查询或者统计结果可以用resultType自动映射(前提是要符合自动映射的命名规范)
  • 连表查询(一对一,一对多)使用resultMap手动映射

在配置文件中我们可以对其进行修改级别

一对一,一对多的映射标签是什么

  • 一对一指的是主表中的一行数据,在对应的中只有一行匹配连接的数据
    • 实体类中嵌套一个对象
    • 在xml文件中用标签映射
  • 一对多指的是主表中的一行数据,在对应的中有多行匹配连接的数据
    • 实体类中嵌套一个集合
    • xml文件中用标签映射

六、配置内容

在使用mybatis框架时,自定义别名可以简化XML配置文件和代码中的映射操作。

  • 告诉框架entity包的路径,mybatis就可以自动扫描包下的类,为每个类生成一个别名
  • 可以在dao.xml文件中直接使用别名来引用java类
  • 多个包路径之间使用逗号分隔
  • 配置后在xml文件中映射查询结果,直接写类名即可
1
2
3
4
5
6
<!-- 单个别名定义 -->
<typeAliases>
<typeAlias type="com.iweb.bean.Dept" alias="Dept"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大小写都可以) -->
<package name="com.iweb.bean"/>
</typeAliases>

在dao.xml文件使用别名

定义了别名,就可以在dao.xml文件中直接使用别名来引用java类

1
2
3
4
<select id="findAll"  resultType="dept">
select * from dept
</select>

七、mybatis连接池与事务

连接池技术

在我们前面Web课程中也学习过类似的连接池技术,而mybatis中也有连接池技术,在之前的案例中mybatis通过mybatis-config.xml配置文件中的<dataSource type="POOLED">来实现mybatis连接池的配置。

连接分类

MyBatis内置了连接池技术,dataSource标签的type属性有3个取值:

  • POOLED 使用连接池
  • UNPOOLED 不使用连接池
  • JNDI 使用JNDI实现连接池

在这三种数据源中,我们一般都是采用POOLED 数据源,数据库连接只有在我们用到的时候,才会获取并打开连接,当我们用完了就立即将数据库连接归还给连接池。

八、mybatis的动态SQL

通常情况下,静态SQL是预先定义好的SQL语句,动态SQL允许根据程序运行时的条件和需求动态的生成SQL语句,从而提供更高的灵活性和可重用性。

在mybatis框架中,提供了一系列的标签可以让我们实现动态SQL配置。

if标签

<if>标签用于进行条件判断,类似于java中的if语句,通过标签可以有选择的加入SQL语句的片段。

需求:

根据实体类的不同取值,使用不同的SQL语句进行查询,比如deptName不为空时可以根据deptName查询,如果address不为空时还要加入部门位置作为条件,这种情况在我们的多条件组合查询中经常会碰到。

1
2
// 多条件查询
List<Dept> findByDept(Dept dept);

1
2
3
4
5
6
7
8
9
10
11
<select id="findByDept" resultMap="deptMap" parameterType="dept">
select * from dept where 1=1
<if test="dname!=null and dname!=''">
and deptname like #{dname}
</if>

<if test="loc!=null and loc!=''">
and address like #{loc}
</if>

</select>

在构建动态SQL时,我们可能会根据不同的条件来拼接查询语句,如果没有任何条件,直接拼接一个where子句会导致SQL语句格式错误,使用where 1=1以确保无论是否添加其他条件,都不会出现语法错误。
1
2
3
4
5
6
7
8
9
10
@Test
public void testFindByDept(){
Dept d=new Dept();
// d.setDname("%部%");
// d.setLoc("%州%");
List<Dept> depts = deptDao.findByDept(d);
for (Dept dept : depts) {
System.out.println(dept);
}
}

where标签

<where>标签替换where,能够自动处理查询条件,智能的处理多余的where、and、or关键字。

需求:为了简化上面where1=1的条件拼接,可以采用<where>标签来简化开发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--  sql标签封装SQL语句  id给SQL去定义 -->
<sql id="queryAll">select * from dept</sql>


<select id="findByDept" resultMap="deptMap" parameterType="dept">
<include refid="queryAll"/>
<where>
<if test="dname!=null and dname!=''">
and deptname like #{dname}
</if>

<if test="loc!=null and loc!=''">
and address like #{loc}
</if>

</where>

</select>

foreach标签

<foreach>标签可以在SQL中配置迭代集合类型参数,用于in语句查询某一范围内的数据。

需求:

传入多个deptId查询部门信息,这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来

1). 在Vo中加入一个List集合用于封装参数

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.iweb.bean;

public class QueryVo {

private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}

public void setIds(List<Integer> ids) {
this.ids = ids;
}
}

映射配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="findInIds" parameterType="queryVo" resultMap="deptMap">
<include refid="queryAll"/>
<where>
<if test="ids!=null and ids.size()>0">
<foreach collection="ids" open="deptid in(" item="deptId" separator="," close=")">
#{deptId}
</foreach>

</if>

</where>

</select>


SQL说明:

标签用于遍历集合,它的属性:

  • ollection:代表要遍历的集合
  • open:前缀,表示该语句以什么开头
  • item:代表遍历集合的每一个元素别名
  • separator:表示每次迭代元素之间以什么作为分隔符
  • close:是后缀,表示以什么结束

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testFindByIds(){
QueryVo vo=new QueryVo();
List<Integer> ids=new ArrayList<>();
ids.add(1);
ids.add(3);
ids.add(9);
vo.setIds(ids);
List<Dept> depts = deptDao.findInIds(vo);
for (Dept dept : depts) {
System.out.println(dept);
}
}

九、多表查询

我们之间学习都是基于单表操作的,而实际开发中,随着业务难度的加深,肯定需要多表操作。
多表分类

  • 一对一:在任意一方建立外键,关联对方的主表
  • 一对多:在多的一方建立外键,关联一的一方的主键
  • 多对多:借助中间表,中间表至少两个字段,分别关联两张表的主键
    【一对一】
    需求:查询用户,同时还要获取当前账户的所属用户信息

    因为一个账户信息只能给一个用户使用,所以从查询账户信息触发关联查询用户信息是一对一查询。如果从用户信息出发查询用户下的账户信息为一对多查询,因为一个用户可以有多个账户

1).数据库设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 用户表
create table user(
id int auto_increment primary key,
username varchar(10) not null,
sex varchar(4),
birthday date,
address varchar(200)
)
insert into user values(1,'jack','男','2005-1-1','南京'),
(2,'lucy','女','2000-11-12','扬州');
-- 账户表
create table account(
id int auto_increment primary key,
money decimal(10,2),
uid INT, -- 外键
foreign key(uid) references user(id)
);
insert into account values(10,2000,1);
insert into account values(11,1500,2);
insert into account values(12,3000,1);

实现查询账户信息时对应用户的信息SQL如下:
1
2
select s.*,a.`id` as aid,a.`money` from 
account a,user s where a.`uid`=s.`id`

2).账户实体类
1
2
3
4
5
6
7
8
9
10
package com.iweb.bean;

public class Account {
private Integer id;
private Integer uid;
private Double money;
// 查询账户信息以及对应的用户信息,需要建立一对一或者一对多映射关系,将查询结果封装到user属性中
private User user;
// 省略get/set..
}

3).用户信息实体类
1
2
3
4
5
6
7
8
public class User {
private Integer id;
private String username;
private String sex;
private Date birthday;
private String address;
// 省略get/set..
}

4).创建mapper
1
2
3
public interface AccountDao {
List<Account> findAll();
}

5).创建mapper映射文件
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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.iweb.mapper.AccountDao">

<!-- 建立对应关系 -->
<resultMap id="accountMap" type="account">
<!-- 封装account对象 -->
<id column="aid" property="id"></id>

<result column="uid" property="uid"></result>

<result column="money" property="money"></result>

<!-- 用于映射关联查询用户的信息
property 实体类对应的属性名,查询完之后将结果封装到property属性所指定的实体bean熟悉中
javaType 实体类对应的全类名,用于指定关联查询的结果封装到哪个实体类中
-->
<association property="user" javaType="user">
<id column="id" property="id"></id>

<result column="username" property="username"></result>

<result column="sex" property="sex"></result>

<result column="birthday" property="birthday"></result>

<result column="address" property="address"></result>

</association>

</resultMap>


<!-- 配置查询所有操作,同时关联用户信息 -->
<select id="findAll" resultMap="accountMap">
select s.*,a.`id` as aid,a.`money` from account a,user s where a.`uid`=s.`id`
</select>

</mapper>


6).将mapper映射文件,添加到mybatis-config.xml
1
2
3
4
5
6
<!-- 告诉mybatis映射文件的位置 -->
<mappers>
<mapper resource="com/iweb/mapper/AccountDao.xml"></mapper>

</mappers>


7).测试
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
package com.iweb.test;
public class AccountTest {

private InputStream in;
private SqlSessionFactory build;
private SqlSession session;
private AccountDao accountDao;

@After // 在测试方法执行完成之后执行
public void destroy() throws Exception{
session.commit();
session.close();
in.close();
}

@Before // 在测试方法执行之前完成执行
public void init() throws Exception{
//1.读取配置文件
in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sqlSessionFactory构建者对象
build = new SqlSessionFactoryBuilder().build(in);
//3.使用SqlSessionFactory生成sqlSession对象
session = build.openSession();
//4.使用session创建dao接口的代理对象
accountDao = session.getMapper(AccountDao.class);
}

@Test
public void testFindAll(){
List<Account> accounts = accountDao.findAll();
for (Account account : accounts) {
System.out.println("------------账户信息-------------");
System.out.println(account.getId()+"\t"+account.getMoney());
System.out.println("------------所属用户-------------");
System.out.println(account.getUser().getId()+"\t"+account.getUser().getUsername());
}
}
}

【一对多】
需求:查询用户信息及用户关联的账户信息
1).修改用户表
1
2
3
4
5
6
7
8
9
10
11
12
13
public class User {

private Integer id;
private String username;
private String sex;
private Date birthday;
private String address;

// 实现查询用户的同时关联账户信息 一对多关联
// 查询用户的信息对应的账户信息之间建立一对多关系,主表为用户表,实体类中需要包含从表实体类的集合引用
private List<Account> accounts;
// 省略get/set方法
}

2).用户持久层添加查询方法
1
2
3
public interface UserDao {
List<User> findAll();
}

3).创建对应mapper文件
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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.iweb.mapper.UserDao">

<resultMap id="userMap" type="user">
<id column="id" property="id"></id>

<result column="username" property="username"></result>

<result column="sex" property="sex"></result>

<result column="birthday" property="birthday"></result>

<result column="address" property="address"></result>

<!-- collection 是用于建立一对多集合属性的对应关系
property="accounts" 关联查询的结果集存储在Users对象上的哪个属性中
ofType="account" 指定关联查询的结果集中的对象类型,即List中的对象类型
-->
<collection property="accounts" ofType="account">
<id column="aid" property="id"></id>

<result column="uid" property="uid"></result>

<result column="money" property="money"></result>

</collection>

</resultMap>

<!-- 配置查询所有操作,同时关联账户信息 -->
<select id="findAll" resultMap="userMap">
select u.*,a.`id` as aid,a.`uid`,a.`money` from user u left join account a on u.`id`=a.`uid`
</select>

</mapper>


4).将mapper映射文件,添加到mybatis-config.xml
1
<mapper resource="com/iweb/mapper/UserDao.xml"></mapper>

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.iweb.test;

public class UserTest {

private InputStream in;
private SqlSessionFactory build;
private SqlSession session;
private UserDao userDao;

@After // 在测试方法执行完成之后执行
public void destroy() throws Exception{
session.commit();
session.close();
in.close();
}

@Before // 在测试方法执行之前完成执行
public void init() throws Exception{
//1.读取配置文件
in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sqlSessionFactory构建者对象
build = new SqlSessionFactoryBuilder().build(in);
//3.使用SqlSessionFactory生成sqlSession对象
session = build.openSession();
//4.使用session创建dao接口的代理对象
userDao = session.getMapper(UserDao.class);
}

@Test
public void testFindAll(){
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println("-----------用户信息------------");
System.out.println(user.getId()+"\t"+user.getUsername());
System.out.println("-----------账户信息-----------");
List<Account> accounts = user.getAccounts();
for (Account account : accounts) {
System.out.println(account.getId()+"\t"+account.getMoney());
}
}
}
}

十、Mybatis缓存

缓存(cache)是一种临时存储数据的机制,用于提供数据访问速度,在计算机系统中,缓存通常存储频繁访问的数据副本,避免每次访问都从原始数据源(数据库)获取,从而减轻IO操作和数据库的压力。

大多数的持久层框架都提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提供性能。

一级缓存

默认开启,一级缓存是SqlSession级别的缓存,当Session flush或者close后,该session中的一级缓存将被清空。

1
2
3
4
5
6
@Test
public void testFindByDeptId(){
Dept dept = deptDao.findByDeptId(3);
System.out.println("第一次查询:"+dept);
System.out.println("第二次查询:"+dept);
}

我们可以发现虽然上面的代码中我们查询了两次,但是最后只执行了一次数据库操作,这就是mybatis提供给我们的一级缓存起了作用,因为一级缓存的存在,导致第二次查询id为3的记录时,并没有发起SQL语句从数据库查询数据,而是从一级缓存中查询。
一级缓存分析
1.一级缓存是SqlSession范围的缓存。
2.第一次发起部门id为3的部分信息,先去缓存中找是否有id为3的部门信息,如果没有从数据库查询部门信息,得到部门信息后,将部门信息存储到一级缓存中。
3.如果sqlSession去执行commit操作(执行插入,更新,删除),会清空SqlSession中的一级缓存,是直接从数据库查询的数据。这样做的目的是为了让缓存中存储的是最新的数据,避免脏读。即在一个会话中,对数据库的增删改操作,均会使一级缓存失效。
4.第二次发起查询部门id为3的部门信息,先去缓存中找是否有id为3的部门信息,缓存中有,直接从缓存中获取信息。
测试清空一级缓存
1
2
3
4
5
6
7
8
9
@Test
public void testFindByDeptId(){
Dept dept = deptDao.findByDeptId(3);
System.out.println("第一次查询:"+dept);
session.clearCache();// 此方法清空缓存
Dept dept1 = deptDao.findByDeptId(3);
System.out.println("第二次查询:"+dept1);
System.out.println(dept==dept1);
}

mybatis的二级缓存

默认关闭,需要手动开启。
作用域是sqlSessionFactory级别。
开启二级缓存
1).在mybatis-config.xml中开启二级缓存

1
2
<!-- 因为cacheEnabled的取值默认是true,false代表不开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>

2).在对应的mapper配置文件中声明使用二级缓存
1
2
3
4
5
 <mapper namespace="com.iweb.dao.IDeptDao">
<!-- 开启二级缓存的支持 -->
<cache/>
....省略其他配置信息...
</mapper>

3).实体类必须实现Serializable接口
1
public class Dept implements Serializable {}

4).配置statement上面的
1
2
3
4
<!--   useCache="true" 要使用二级缓存,针对每次要查询的数据是最新的,设置为false,禁用二级缓存 -->
<select id="findByDeptId" useCache="true" parameterType="int" resultMap="deptMap">
<include refid="queryAll"/> where deptid=#{deptNo}
</select>

5).测试二级缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testFindByDeptId(){
SqlSession sqlSession1 = build.openSession();
IDeptDao deptDao1 = sqlSession1.getMapper(IDeptDao.class);
Dept dept1 = deptDao1.findByDeptId(3);
System.out.println(dept1);
sqlSession1.close();// 清除一级缓存
SqlSession sqlSession2 = build.openSession();
IDeptDao deptDao2 = sqlSession2.getMapper(IDeptDao.class);
Dept dept2 = deptDao2.findByDeptId(3);
System.out.println(dept2);
sqlSession2.close();// 清除一级缓存
System.out.println(dept1==dept2);
}

经过上面的测试,我们发现执行的两次查询,并且在第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发起SQL语句,所以此时的数据就是来源我们所说的二级缓存。
==注意==
当我们使用二级缓存时,所缓存的类一定要实现Serializable接口这种就可以使用序列化来保存对象。

一级缓存和二级缓存的使用和区别
1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

<foreach>标签用于遍历

MyBatisPlus

这玩意神的一批啊,有这个和小辣椒搭配,我上面写的这上千行就和没写一样

SpringBoot整合MyBatisPlus

创建SpringBoot项目

1).使用Spring Initializr创建SpringBoot项目,选择Spring Web和MyBatis Plus依赖
2).在项目中添加MyBatis Plus的依赖
3).在项目中添加MyBatis Plus的配置

当然上述过程最快的部署方式,如果我们出现了一些小问题,那我们可以自行在pom.xml中添加依赖

依赖要补充这些(示例,请自行参考自己的环境选择合适的版本)

1
2
3
4
5
6
<!-- MyBatis Plus依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>

配置文件说明(yml写法)
1
2
3
4
5
6
# MyBatis Plus配置
mybatis-plus:
# 配置mapper.xml文件的位置
mapper-locations: classpath:/mapper/*.xml
# 配置实体类所在的包
type-aliases-package: com.iweb.bean

记得匹配数据库配置,不然mybatis-plus会报错(自行匹配链接)
1
2
3
4
5
6
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

创建实体类

创建一个entity包,用于存放实体类,这里以User为例为例
这里的参数和字段得和数据库表相对应,以便可以正常映射

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 用户实体类
*/
@TableName("user")
@Data //这个是除了全参构造的全部写好
@AllArgsConstructor //这个写了全参构造
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
public String email;
}

这个上面使用了小辣椒lombok注解,来简化代码的,如果你也想省了get,set,toString方法,你可以使用
小辣椒lombok依赖引入
1
2
3
4
5
6
<!-- Lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>

我们编写实体类有一些东西需要介绍下:
框架会通过反射实体类的字节码,阅读字节码中的属性名和注解的信息,动态拼接增删改查的sql语句

- 有注解的属性,会自动拼接sql语句
- 没有注解的属性,不会拼接sql语句

编写Mapper接口

先创建mappar包或者写为DAO,创建每张表的Mapper接口(这里以UserMapper为例)

1
2
3
4
5
6
7
8
9
/**
* UserMapper接口用于代替UserDAO
* 用于操作user表的增删改查
* 需要继承mybits框架的BaseMapper接口
* 指定实体范形
*/
public interface UserMapper extends BaseMapper<User> {

}

很简单吧,你只要继承官方提供的基础父接口就生命都不用动了,说白了这就是给你项目中加一个目录锚点,方法早就写好了
家产继承这一块,少走了多少弯路啊,增删改查的功能全给你写好了

不信吗?你编译一下看看就知道了,全弄好了都

在启动类上加上扫描注解

在启动类上我们得添加扫描注解,来扫描我们的Mapper接口包

  • @MapperScan注解用于扫描Mapper接口包,后面括号中的参数是Mapper接口包的路径,告诉框架这个包在哪里
  • @SpringBootApplication注解用于开启Spring Boot应用,自动配置Spring Boot的环境

配好之后框架会根据我们指定的Mapper包路径来扫描该包中的所有接口,这个矿建会基于cglib动态代理模式,动态的实现接口中的每一个方法

也就是说,我们缩写的Mapper接口继承BaseMapper接口,里面所有的抽象方法,框架已经帮助我们实现,但是你如果想要半自动写法,请看上面的在xml中配置sql的写法

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@MapperScan("com.xhayane.springbootmybits.mapper")
public class SpringBootMybitsApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBootMybitsApplication.class, args);
}

}

组合使用Mapper接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
UserMapper userMapper;

@GetMapping
public Object getAllUsers(){
List<User> userList = userMapper.selectList(null);
return userList;
}

@PostMapping
public Object createUser(@RequestBody User user){
return userMapper.insert(user);
}
}

上述代码是在控制层包中编写的一个简单的实际应用,他主要有这几个部分构成:

  • @Autowired注解用于自动注入UserMapper接口的实现类
  • @GetMapping注解用于映射GET请求,返回所有用户
  • @PostMapping注解用于映射POST请求,创建用户
  • @RequestBody注解用于将请求体中的JSON数据转换为User实体类

关注BaseMapper有哪些核心功能

官方提供的BaseMapper接口将成为每个Mapper接口的基础,它包含了增删改查的基本方法,以及一些高级方法,如批量操作、分页查询等
控制器层中,我们可以直接调用BaseMapper接口的方法,来实现对数据库的增删改查操作

mybatis条件构造器和分页器的使用

条件构造器的分类

  • 按照功能分
    • 查询条件构造器
      • 生成where子句
    • 更新条件构造器
      • 生成where子句
      • 生成set子句
    • 删除条件构造器
    • 新增条件构造器
  • 按照语法分
    • 普通条件构造器
      • 列名需要用字符串硬编码写死,写错不容易被发现,写错编译也可以通过
    • Lambda条件构造器
      • 支持方法引用【::方法名】语法,写错立马出红线,不能编译通过

常见的条件构造器名单就不在这里描述了,见站外文档

分页器部署和安装

在Mybatisatis中,分页器是用于分页查询的工具类,它可以帮助我们快速实现分页查询的功能,该分页器需要单独配置依赖才可以使用
分页器的依赖引入

1
2
3
4
5
6
<!-- 分页器依赖 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.1</version>
</dependency>

配置分页器插件
在项目中的config(自定义设置)中添加相关配置类,注意导包和路径命名问题
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class MybatisPlusConfig {
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
// 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
return interceptor;
}
}

如果你是在配置文件中设置,在yml中如下配置
1
2
3
4
5
6
7
8
9
10
11
spring:
application:
name: springboot-mybits
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
pagehelper:
helper-dialect: mysql
reasonable: true
params: countSql
support-methods-arguments: true

使用分页器
在Mapper接口中,我们可以直接调用PageHelper类的方法,来实现分页查询,我们需要创建传参对象
高效的方式是创建一个BaseDTO类,来封装分页查询的参数,有分页页码、每页数量、查询条件等属性
1
2
3
4
5
6
@Data
public class BaseDTO {
//这两个是分页参数名称
private Integer current;
private Integer size;
}

其他类继承BaseDTO类,来封装分页查询的参数,比如UserDTO类
分页器的成员结构分析
| 成员 | 作用 | 说明 |
| —- | —- | —- |
| records | 分页查询的结果集 | 分页查询的结果集 ,是一个List集合,每个元素都是一个实体类对象,分页器会允许select from user limit ?,?,其集合中的内容就是查询结果 |
| total | 总记录数 | 总记录数,用于分页查询的分页信息,如果没有查询条件,是所有数据的总行数,如果有查询条件,是符合条件的总行数,分页器会先运行select count(
) from user,再根据查询条件进行分页查询 |
| size | 每页数量 | 用客户端传回来的size作为返回的size |
| current | 当前页码 | 用客户端传回来的current作为返回的current |
| pages | 总页数 | pages是total/size的向上取整的值 |

分页器的底层运行流程

1.用select count() from user查询总记录数,根据总行数算出总页数
2.用select
from user limit ?,?查询当前页数据,并将结果集解析为list,用名为records的list集合存储
3.将total、records、size、current、pages这5个属性封装到一个PageResult对象中,返回给客户端

分页参数current和size的作用分别是什么?
分页器会使用current和size参数,计算limit后面的两个参数
第一个参数是查询的行号偏移量 = (current - 1) * size
第二个参数是查询的行号 = size

如果客户端没有传current和size参数,他们不会有什么影响,会使用默认值完成,默认值分别是1和10

1
2
3
4
5
6
Page<User> page;
if (userDTO.getCurrent()!=null && userDTO.getSize()!=null) {
page = new Page<>(userDTO.getCurrent(), userDTO.getSize());
}else {
page = new Page<>();
}