Mybatis3全篇学习笔记

Mybatis简介

Mybatis3简介

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


快速上手Mybatis3

准备工作

  1. 创建数据库
sql 复制代码
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);
  1. 导入依赖
xml 复制代码
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.3.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.11</version>
    </dependency>
</dependencies>

Mybatis做了什么

我们需要了解一下mybatis做了什么,这样才能更清楚的知道为什么要准备其他的一些东西。

我们一共分三步完成持久层的各种操作。

我们首先需要定义一个接口,在其中指定我们需要查询的信息 ,比如在上面的表中,我想要查询员工的姓名。那么我就需要在接口定义一个selectEmpName()的方法。

好的,那么我们现在想要去查询这个信息就显然需要设计一个sql语句需要去查询,在mvc框架中加入mybatis,他会将sql语句从Java代码中抽离出来,放置在专门的xml文件中,我们称它为mapper

现在假设我们查询出来了想要的结果,一个员工姓名,我们显然可以用一个String去接收它。但设想一下,如果我们要查询的是员工的姓名信息和工资信息呢?又或者还有员工的id呢?我们就不能单纯的用一个String去接收了。mybatis提供了一种新的方式,用一个与emp表中列所对应的对象去接收查询到的信息 。 比如下面这个类,我们称他为pojo

kotlin 复制代码
public class Employee {

    private Integer empId;

    private String empName;

    private Double empSalary;

    //此处省略 getter | setter | toString

还有上面所说的一个接口以及它的方法

csharp 复制代码
public interface EmployeeMapper {
    Employee selectEmployee(Integer empId);
}

还有就是存放sql的xml文件 。不过这个xml文件要做的有点多,他需要将接口中的方法与pojo相关联起来。简单来讲就是当接口中的方法被调用,他需要将sql执行的结果映射到pojo中要把它写在classpath下面,因为src只识别java文件,我建议你建个文件夹存放它,因为可以预见,它将来肯定不止一个。

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.Mapper.EmployeeMapper">
    <select id="selectEmployee" resultType="com.atguigu.pojo.Employee">
        select emp_name empName,emp_salary empSalary from t_emp where emp_id=#{id}
    </select>
</mapper>

让我们看一下其中的标签以及属性。

  1. mapper标签,表示一个完整的接口实现类
  2. namespace属性精确定位到它需要实现的接口上
  3. select标签声明了这个sql的类型。
  4. id则指明了接口中的方法,表明这个查询sql是对该方法的重写
  5. 而resultType则指明了返回值得类型(他指向了我们的pojo)

配置mybatis

别忘了配置数据库,当然mybatis也需要一些简单的配置我们在类路径下写一个配置文件。

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

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

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

</configuration>

测试

ini 复制代码
InputStream ism = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(ism);
SqlSession sqlSession = build.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.selectEmployee(1);
System.out.println(employee);
sqlSession。close();

这个测试可以说非常繁琐,

  1. 首先要使用一个工具类把配置文件变成流,
  2. 然后使用 SqlSessionFactoryBuilder()构建一个会话工厂,
  3. 再从工厂中取出会话,
  4. 使用会话工具放入接口,获得接口实现类
  5. 调用方法传入参数执行sql。
  6. 关闭流。

mybatis原理的讲解

mybatis是在ibatis的基础上封装优化而来的。增加了使用mapper接口调用sql。 对参数的传入做了优化。但底层还是调用了ibatis来执行sql

我们使用#{}来拼接参数,不要使用#{}拼接,防止sql漏洞。

我们将更详细的内容打印在控制台,监测sql的拼接与执行(将mybatis的日志实现设置为打印控制台)。

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

mybatis传参

1. 简单数据类型

我们在接口中添加更多方法来演示

scss 复制代码
Integer deleteEmployeeById(Integer empId);
//返回值为复杂数据类型
List<Employee> getEmployeesBySalary(Double salary);
//传入对象时
Integer addOneEmployee(Employee employee);
//传入多个简单参数
Integer updateEmployeeSalaryById(Integer id,Double salary);

处理方法如下

sql 复制代码
    //当返回值是固定的时,可以省略resultType
<delete id="deleteEmployeeById">
    delete * from t_emp where emp_id=#{id}
</delete>


//当返回类型是多个对象时,无需任何操作,resultType传pojo即可
<select id="getEmployeesBySalary" resultType="com.atguigu.pojo.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_salary>#{salary}
</select>

//当传入的是对象时,则参数名必须为pojo的参数名(get方法的名字)
<insert id="addOneEmployee">
    insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
</insert>

传入多个简单数据类型的处理方式有点复杂

less 复制代码
//传入参数为多个简单数据类型时,可以使用arg0 arg1...
// 也可以使用 param1 param2 ...
//但建议使用@param() 直接指定参数!!!
<update id="updateEmployeeSalaryById">
    update t_emp set emp_salary=#{arg1} where emp_id=#{arg0}
</update>

@Param的用法
Integer updateEmployeeSalaryById(@Param("id") Integer id, @Param("salary") Double salary);

Map传参

一般来讲不建议这么用,但是它可以代替pojo直接传参查询 设置key为字段,value为值。


python 复制代码
//传入map做为多个简单数据类型时,设为map中的key即可
<update id="updateEmployeeSalaryById">
    update t_emp set emp_salary=#{map中的key} where emp_id=#{map中的key}
</update>

设置返回值类型

如果返回的是一个基本数据类型 ,比如String,Double,则全部用首字母小写,string,double,map,也可以写全类名。

csharp 复制代码
<select id="selectEmployeeName" resultType="string">
    select emp_name from t_emp where emp_id=#{empId};
</select>

@Alias注解


如果返回对象,而又不想写全限定名可以用下面这个注解

开启驼峰命名映射

如果我们的表的字段全部为XXX_XXX,而我们的pojo全部为驼峰命名。我们就可以配置一下,不写别名。

csharp 复制代码
<select id="getEmployeesBySalary" resultType="com.atguigu.pojo.Employee">
    select emp_id empId,
    emp_name empName,
    emp_salary empSalary from t_emp where emp_salary>#{salary}
</select>

每一个都要写别名去对应pojo 我们在配置文件中写入

ini 复制代码
<setting name="mapUnderscoreToCamelCase" value="true"/>

map接收任意参数

sql 复制代码
<select id="getMap" resultType="map">
    select emp_name 最高工资员工姓名,emp_salary 最高员工工资,(select AVG(emp_salary) from t_emp) 平均工资
    from t_emp where
    emp_salary=(select MAX(emp_salary) from t_emp)
</select>

当然也可以使用list接受上面的代码

如何开启事务自动提交?

ini 复制代码
InputStream ism = Resources.getResourceAsStream("mybatis-config.xml");

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ism);

//传入true表示开启自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);

如何获取插入数据的主键?

sql 复制代码
<insert id="addOneEmployee" useGeneratedKeys="true" keyColumn="emp_id" keyProperty="empId">
    insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
</insert>
  1. useGeneratedKeys="true" ------ 表示我们需要表自动生成的主键

  2. keyColumn="emp_id" ----- 表示指定主键是谁

  3. keyProperty="empId" 表示让mybatis直接将结果返回到我们用于插入数据的对象中。

mybatis维护UUID主键

sql 复制代码
<insert id="addOneEmployee" >
    <selectKey keyColumn="emp_id" keyProperty="empId" resultType="string">
        select replace(UUID(),'-','');
    </selectKey>
    insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary});
</insert>

多表查询

我们如何处理复杂的多表查询。如何设计与其相对应的pojo?多层嵌套关系的对象类型如何精准映射?


多表查询的设计原则

数据库表之间的关系无非分为3类

  1. 一对一的数据表关系

  2. 多对一,一对多的数据表关系

  3. 多对多的数据表关系


我们始终遵从以一张数据表为主的原则来设计。

比如订单表与用户表的关系,从订单角度来看,多张订单对应一个客户,所以是多对1 ,而如果从客户角度看,则是一个客户对应多张表,是1对多的关系

当我们需要查询订单的id,订单的内容与订单对应的客户时客户其实可以设计为订单表的单个对象属性几个。

scss 复制代码
CREATE TABLE t_order(
 order_id INT(11) UNSIGNED,
 order_name VARCHAR(32),
 customer_id INT(11)
 ) CHARSET=utf8 ENGINE=INNODB;
 
 
 CREATE TABLE t_customer(
 customer_id INT(11) UNSIGNED,
 customer_name VARCHAR(32)) CHARSET=utf8 ENGINE=INNODB;
 
 INSERT INTO t_order(order_id,order_name) VALUES(1,'豆橛子订单')
 
INSERT INTO t_customer(customer_id,customer_name) VALUES(1001,'靓仔')

创建pojo

vbnet 复制代码
public class Order {
   private Integer orderId;
   private String orderName;

   private Integer customerId;

   private Customer customer;
   /getter |  setter  |  toString
}
vbnet 复制代码
@Data
public class Customer {
    private Integer customerId;
    private String customerName;
    private List<Order> orderList;
     /getter |  setter  |  toString
}

Mapper相关的配置。

ini 复制代码
<resultMap id="orderAndMap" type="com.atguigu.pojo.Order">

   <id column="order_id" property="orderId"/>
   <result column="order_name" property="orderName"/>
   <result column="customer_id" property="customerId"/>

   <association property="customer" javaType="com.atguigu.pojo.Customer">
       <id column="customer_id" property="customerId"/>
       <result column="customer_name" property="customerName"/>
   </association>

</resultMap>

<select id="selectOrderAndCustomer" resultMap="orderAndMap">
   SELECT o.order_name, o.customer_id, c.customer_name
   FROM t_order o
   JOIN t_customer c
   ON o.customer_id = c.customer_id
   WHERE o.order_id = #{orderId}
</select>

代码解析:

  1. 对于查询语句的映射,我们使用resultMap来解决多表映射
  2. resultMap中type我们依然选择主表Order表的Pojo
  3. 主键我们使用id来映射 column选择表中的列,property选择pojo字段
  4. 对于对象类型我们选择association,指定他的pojo为JavaType

一对多查询的xml写法

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.Mapper.CustomerMapper">

    <resultMap id="orderList" type="com.atguigu.pojo.Customer">

        <id column="customer_id" property="customerId"/>

        <result column="customer_name" property="customerName"/>

        <collection property="orderList" ofType="com.atguigu.pojo.Order">
            <id column="order_id" property="orderId"/>
            <result column="order_name" property="orderName"/>
        </collection>

    </resultMap>

    <select id="getOrderByCustomerId" resultMap="orderList">
        select  ctm.customer_id,ctm.customer_name,order.order_id,order.order_name
        from  t_customer AS ctm JOIN t_order AS `order`
        ON ctm.customer_id=order.customer_id
        where ctm.customer_id=#{id}
    </select>
</mapper>

他们之间的区别在于2个标签

  1. 不同于单个对象的写法,我们使用collection来接收多个对象(List)类型
  2. 不同于单个对象的写法,我们使用ofType指定对象的pojo

多表映射的优化

在上面的代码中我们发现,我们无法对多层级的属性进行自动映射 。比如List集合中的属性。我们需要手写映射关系。其实mybatis可以根据驼峰原则,进行自动映射(浅层和深层都能实现自动完成)。我们需要额外开启相关设置。

xml 复制代码
<settings>
    <!-- SLF4J 选择slf4j输出! -->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <setting name="autoMappingBehavior" value="FULL"/>
</settings>

开启后,我们就不需要写一部分的映射了

xml 复制代码
    <resultMap id="orderList" type="com.atguigu.pojo.Customer">

<!--        <id column="customer_id" property="customerId"/>-->

<!--        <result column="customer_name" property="customerName"/>-->

        <collection property="orderList" ofType="com.atguigu.pojo.Order">
            <id column="order_id" property="orderId"/>
<!--            <result column="order_name" property="orderName"/>-->
        </collection>

但是注意,关于主键我们仍需要定义

自定义多条件查询

在实际开发中,我们常常遇到需要自定义的多条件查询。比如电商平台的购物筛选功能,用户可能添加附加条件查询,就像下面这种情况。如果我们给每条查询都设置对应的sql语句,这显然不现实。

那么如何解决这种问题呢?

mybatis为我们提供了新的标签:<if>标签与<where>标签。 比如下面这个根据姓名和工资查找员工的请求。

less 复制代码
List<Employee> getEmpByNameAndSalary(@Param("name") String name,@Param("salary") Double salary);

如果上面的name和salar可以可能传也可能不传,那我们的sql语句就应该按照下面这样来写。

bash 复制代码
<select id="getEmpByNameAndSalary" resultType="com.atguigu.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>

来讲讲where标签做了什么?他做了2件事

  1. 它代替了传统手写的where
  2. 当条件不成立时,删掉中间的连接词(在上面的代码中是and,也可以是or)

if标签做了什么?

  1. 当test中的条件不成立,则if中的语句不存在
  2. 注意test中的name是我们在mapper中使用@param标签定义的,无法使用arg0代替

自定义多条件设置值

mybatis还为我们提供了set标签,原理与用法与上面的相同,保证传入的参数出现问题时sql正常执行set标签需要特别注意的地方是,如果条件均不成立,语法本身就无法成立。而在上面的查询中,如果条件均不满足,其实会变成无条件查询。

trim标签

trim标签可以用来替代任何上面的标签,他的作用就是在sql语句上面进行直接的裁剪 他提供4个选项,中间可以用|隔开

  1. prefix 表示在if标签的前面动态添加的语句。如where与set
  2. suffix 表示在if标签的后面动态添加的语句。如and连接符
  3. prefixOverrides 表示在if标签的前面动态删除的语句。
  4. suffixOverrides 表示在if标签的后面动态删除的语句。

他的原则就是if条件不满足则开始判定是否有对应的单词,有就删掉;或者条件满足时判定是否有,没有就加上。(不会添加多个where)

设置如switch标签的多选一

使用mybatis提供的标签,我们可以实现多选一,就像java中的switch

mybatis批处理

mybatis并没有提供专业的批处理方法,但提供了一个forEach标签。该标签的作用主要是遍历传入的list集合 ,我们通过遍历循环来拼接出想要的语句

csharp 复制代码
List<Employee> getOneEmpByIds(@Param("ids") List<String> ids);


<select id="getOneEmpByIds" resultType="com.atguigu.pojo.Employee">
    select * from t_emp where
        id in
                <foreach collection="ids" item="id" open="(" separator="," close=")">
                        #{id}
                </foreach>
</select>

代码解析,forEach标签没什么好说的,就是遍历传入的list,主要在于其中的属性。

  1. open 表示要在遍历之前要拼接的东西
  2. close 自然就是结束后要拼接的东西
  3. 至于separator则是每个元素遍历出来后之间的分割符
  4. item无需多说,就是item项

sql片段的抽取与复用

抽取:

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

引用:

xml 复制代码
<!-- 使用include标签引用声明的SQL片段 -->
<include refid="mySelectSql"/>

xml文件的批量映射

我们会有一个很烦人的问题,我们所有的xml文件都必须在配置文件中注册。 我们如何批量的去映射他们,或者说直接指定一个包,让他自动扫描包里面的xml文件呢?

mybatis提供了一个package标签

ini 复制代码
<package name="com.atguigu.Mapper"/>

但是这个标签很坑的一点是,

  1. 他要求mapper.xml文件必须跟mapper接口的名字保持完全一致!
  2. 他要求必须把他们放在同一个包里面。

要知道maven在打包时是默认不会扫描src下其他文件的,只会扫描java文件!,下面演示如何设置maven的打包规则(仅测试用)。

xml 复制代码
<build>
    <resources>
        <resource>
            <directory>
                src/main/java
            </directory>
            <includes>
                <include>
                    **/*.*
                </include>
            </includes>
        </resource>
    </resources>
</build>

这样做还是很不方便,会导致很多问题。


我们在resources文件夹下创建与java包下同样的包结构即可,他会自动打包在一起!,特别注意,创建包时要一层一层创建!!!

分页插件的使用

mybatis并没有提供分页功能,我们可以使用分页插件。这在开发中非常常用

分页插件的原理

他的原理是在sql执行之间添加了一个拦截器,在拦截器中继续封装sql语句,所以他在使用时代码看起来会有点奇怪。


第一步我们导入依赖

xml 复制代码
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.11</version>
</dependency>

第二步我们在配置文件中引入插件,

mybatis配置文件的各项配置有严格的顺序 ,我们可以点击configuration标签查看

xml 复制代码
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>

我们将插件引入到mybatis中的指定位置,代码中的mysql表示需要拦截的竖起了类型。

xml 复制代码
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="helperDialect" value="mysql"/>
    </plugin>
</plugins>

插件使用的注意事项:

我们在写竖起了时正常写就行,但是需要删除掉最后竖起了语句结尾的分号 ---->;否则语句执行会出问题!!!!

xml 复制代码
<!--    List<Employee> PageQueryAll();-->
    <select id="PageQueryAll" resultType="com.atguigu.pojo.Employee">
        select * from t_emp
    </select>

插件的使用看起来非常奇怪,它会提前设置当且页数与每页最大数据量

csharp 复制代码
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

//配置分页信息
PageHelper.startPage(1,2);
//查询代码执行
List<Employee> employees = mapper.PageQueryAll();

//把查询结果放入PageInfo中,可以让我们获得更多有用信息
PageInfo<Employee> pageInfo = new PageInfo<>(employees);

//获取数据的list合集
List<Employee> list = pageInfo.getList();
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);

sqlSession.close();

如果我们出现了在同一套代码中需要2次分页查询,则需要重新写一套startPage与PageInfo()来嵌套查询语句

相关推荐
Moment17 分钟前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
yunteng52136 分钟前
通用架构(同城双活)(单点接入)
架构·同城双活·单点接入
Cobyte1 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
麦聪聊数据1 小时前
Web 原生架构如何重塑企业级数据库协作流?
数据库·sql·低代码·架构
程序员侠客行2 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
Honmaple2 小时前
QMD (Quarto Markdown) 搭建与使用指南
后端
PP东2 小时前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable
invicinble3 小时前
springboot的核心实现机制原理
java·spring boot·后端
全栈老石3 小时前
Python 异步生存手册:给被 JS async/await 宠坏的全栈工程师
后端·python
space62123273 小时前
在SpringBoot项目中集成MongoDB
spring boot·后端·mongodb