九、Mybatis

262 阅读13分钟

一,Mybatis介绍

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

  • MyBatis本是Apache的一个开源项目iBatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis。2013 年11月迁移到Github。
  • 官网: mybatis – MyBatis 3 | 简介

二,Mybatis基础操作

2.1 Mybatis快速入门

首先,需要在Maven中导入Mybatis的坐标

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.2</version>
</dependency>

以Mybatis操作MySQL为例,导入Java操作Mysql的驱动:

<!--mysql8.0之前的版本-->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>5.1.45</version>
</dependency>
<!--MySQL8.0及之后的版本-->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

配置MySQL的连接信息,在项目application.properties或其他配置文件中配置:

spring:
  #mysql数据库配置
  datasource:
  	#连接MYSQL的账号
    username: root
    #连接MYSQL的密码
    password: 123456
    #连接MYSQL的地址
    url: jdbc:mysql://172.31.154.122:3306/demo
    #mysql驱动的全类名,这里是8.0以上的
    #8.0之前的是:com.mysql.jdbc.Driver
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  #开启类别命名自动扫描
  typeAliasesPackage: com.normaling.**.domain
  #开启mapper扫描路径
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  configuration:
    #指定mybatis日志输出的位置,这里是输出到控制台
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    #开启小驼峰命名转换。将column=a_column--->aColumn
    map-underscore-to-camel-case: true

创建对应数据库表的实体类,用于接受从数据库中查询的数据。

image-20231012142324880

在Mapper层的类中使用注解的方式来编写SQL语句

image-20231012142400548

@Mapper是Mybatis提供的注解,用于将我们的java类较给IOC容器管理。

@Select就是查询语句了,对应的还有@Insert,@Delete,@Update

Mybatis编写SQL语句分为:

  • 静态SQL(用注解直接写SQL)
  • 动态SQL(编写xml文件)(主要通过xml编写SQL)

2.2 数据库连接池

数据库连接池:

  • 数据库连接池是个容器,负责分配、管理数据库连接(Connection)
  • 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
  • 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏

数据库连接池标准接口:DataSource

  • 官方(sun)提供的数据库连接池接口,由第三方组织实现此接口
  • 功能:获取连接:image-20231012142904680

常见的数据库连接池:

image-20231012142945576

Druid (德鲁伊)

  • Druid连接池是阿里巴巴开源的数据库连接池项目
  • 功能强大,性能优秀,是ava语言最好的数据库连接池之一

切换Druid数据库连接池的方法:

官方地址: github.com/alibaba/dru…

  1. 引入Druid

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.16</version>
    </dependency>
    
  2. 修改数据源配置

    spring:
      datasource:
        # 基本数据库连接信息
        url: jdbc:mysql://localhost:3306/your_db?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource  # 指定使用 Druid 连接池
    
        # Druid 连接池专属配置
        druid:
          # --- 连接池核心参数 ---
          initial-size: 5            # 初始化时建立的物理连接数(建议根据并发量调整)
          min-idle: 5                # 最小空闲连接数(长期维持的闲置连接数)
          max-active: 20             # 最大活跃连接数(高并发时可适当调高,避免等待)
          max-wait: 60000            # 获取连接时的最大等待时间(毫秒),超时则抛异常
          time-between-eviction-runs-millis: 60000  # 检查空闲连接的间隔时间(毫秒)
          min-evictable-idle-time-millis: 300000     # 连接保持空闲的最小时间,超时则被关闭
          validation-query: SELECT 1  # 用于检测连接有效性的 SQL(如 MySQL 用 SELECT 1)
          test-while-idle: true       # 空闲时是否检测连接有效性(建议开启)
          test-on-borrow: false       # 获取连接时是否检测有效性(影响性能,不建议开启)
          test-on-return: false       # 归还连接时是否检测有效性(影响性能,不建议开启)
          
          # --- 监控统计配置 ---
          stat-view-servlet:
            enabled: true             # 启用内置的监控页面
            url-pattern: /druid/*     # 监控页面的访问路径
            login-username: admin     # 监控页面登录用户名(生产环境建议修改)
            login-password: admin     # 监控页面登录密码(生产环境必须修改)
            reset-enable: false       # 禁用监控页面的 "Reset All" 功能(安全考虑)
            
          web-stat-filter:
            enabled: true             # 启用 Web 应用监控
            url-pattern: /*            # 监控所有 URL 请求
            exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"  # 排除静态资源
            session-stat-enable: true  # 统计 Session 使用情况
            principal-session-name: user  # 用户信息的 Session Key(用于监控用户行为)
            
          # --- 高级功能(可选)---
          filters: stat,wall,slf4j     # 启用统计、防火墙、日志过滤器(需添加依赖)
          filter:
            stat:
              log-slow-sql: true      # 记录慢查询
              slow-sql-millis: 2000   # 慢查询阈值(单位:毫秒)
            wall:
              config:
                multi-statement-allow: true  # 允许多条 SQL 语句(根据需求开启)
    

三,Mybatis静态SQL

3.1 参数占位符

image-20231017082547744

参数顺序和注解里面的参数顺序需要保持一致。

3.2 删除

默认返回操作影响的记录数,用int存储。

举例:根据主键id删除数据。

image-20231017080936982

#{id}所包含的内容在mybatis中会先变成?占位符,这个叫预编译SQL

image-20231017081655426

预编译SQL和普通SQL语句的执行对比:

image-20231017082157978

注:SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法,Mybatis能防止SQL注入是因为这个预编译SQL,因为不管传入怎么样的字符串,他都会将其当作一个参数传入!

image-20231017082451410

3.3 新增

举例:新增人员信息

image-20231017082845601

其中参数占位符里面的是Emp类里面的属性名,一般采用小驼峰命名方法,而在数据库表中一般采用小写使用下划线分割的方式来命名。

举例:新增(主键返回)

image-20231017084152867

即insert插入数据后,将对应记录里面的主键封装到你传入的参数Emp emp里面。这个就叫新增主键返回。

3.4 更新

默认返回操作影响的记录数,用int存储。

image-20231017084355554

3.5 查询

根据主键id查询:

image-20231017084456287

但是在查询回显数据的时候需要注意数据封装问题:

  • 实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装
  • 如果实体类属性名 和 数据库表查询返回的字段名不一致,不能自动封装
  • 假设你查询返回的是一张表,里面每一条记录数代表的就是一个Emp信息,那么返回值可以用List<Emp>来存储整张表。

解决方法:

  1. 给数据库表中字段名和对应java属性名不一致的地方在select语句中起别名。

    image-20231017084758749

  2. 通过**@Results,@Result**注解手动映射数据库字段和java类字段

    image-20231017084915481

    column里面是数据库表字段名,property里面是java类字段名。

  3. 在mybatis中开启小驼峰转换,让他自动映射。

    a_column ==》 aColumn这种

    mybatis:
      configuration:
        #指定mybatis日志输出的位置,这里是输出到控制台
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        #开启小驼峰命名转换。将column=a_column--->aColumn
        map-underscore-to-camel-case: true
      #开启mapper扫描路径
      mapper-locations: classpath:mapper/*.xml
    

条件查询

image-20231017085436039

注意:

image-20231017085631622

四,xml映射

image-20231018133819731

  • 随着用户的输入或外部条件的变化而变化的SQL语句,我们称为 动态SQL。
  • xml映射SQL主要是用来配置复杂的,动态的SQL语句。
  • resultType是单条记录封装的返回值的全类名。

一般我们会通过在配置文件中指定扫描mapper.xml路径

mybatis:
    # 搜索指定包别名
    typeAliasesPackage: com.gtzc.**.domain
    # 配置mapper的扫描,找到所有的mapper.xml映射文件
    mapperLocations: classpath*:mapper/**/*Mapper.xml
    # 加载全局的配置文件
    configLocation: classpath:mybatis/mybatis-config.xml

效果如图:

w

4.1 SQL语句标签

  1. select标签

    <select id="selectById" resultMap="BaseResultMap" parameterType="Object">
        select * from user where id=#{id}
    </select>
    
  2. update标签

    <update id="update" parameterType="Object">
    	update user set 
    		<if test="name != null"> NAME = #{name},</if>
    		<if test="sex != null"> SEX = #{sex} </if>
    	where ID = #{id}
    </update>
    
  3. delete标签

    <delete id="deleteById" parameterType="Object">
        delete from user where id=#{id}
    </delete>
    
  4. insert标签

    <insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
    	insert into user name=#{name}
    </insert>
    

    useGeneratedKeys和keyProperty是insert标签独有的属性

    • useGeneratedKeys=true:告诉 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来获取由数据库自动生成的主键值。
    • keyProperty="id":指定属性名,这个属性将会在插入操作完成后被赋值为数据库生成的主键值。在这个例子中,idDemoUser 对象的一个属性。

标签内属性说明:

  • parameterType:parameterType: 传入给语句的参数类型。
  • id:唯一标识需要和mapper文件里面的方法名保持一致。

4.2 接口方法传入多个参数解决方法

在上述标签中,我们的接口方法都只传入了一个参数,然后用parameterType来指定我们的值,但当我们的接口方法出现多个参数时解决方法:

4.2.1 使用Map

接口:

public interface UserMapper {
    User selectUser(@Param("id") int id, @Param("name") String name);
}

xml配置:

<select id="selectUser" parameterType="map" resultType="com.example.User">
    SELECT * FROM users WHERE id = #{id} AND name = #{name}
</select>

4.2.2 使用自定义对象

就是将多个参数新建一个对象来封装起来。

4.2.3 使用 @Param 注解

如果使用 MyBatis 3.4.0 及以上版本,可以直接在方法参数上使用 @Param 注解

public interface UserMapper {
    User selectUser(@Param("id") int id, @Param("name") String name);
}
<select id="selectUser" resultType="com.example.User">
    SELECT * FROM users WHERE id = #{id} AND name = #{name}
</select>

4.3 if标签

用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL。

如下图所示:

image-20231018141932200

4.4 choose标签

<choose> 标签类似于 Java 中的 switch 语句,用于在多个条件中选择一个执行分支。它可以包含多个 <when> 和一个 <otherwise> 标签,每个 <when> 标签表示一个条件分支,而 <otherwise> 标签表示默认分支。

<select id="selectUser" resultType="User">
    SELECT *
    FROM user
    <where>
        <choose>
            <when test="name != null">
                AND name = #{name}
            </when>
            <when test="age != null">
                AND age = #{age}
            </when>
            <otherwise>
                AND id = #{id}
            </otherwise>
        </choose>
    </where>
</select>

4.5 where标签

上述案例中,存在bug,当第一个条件name为null时会SQL会拼接成错误的SQL:

select * from emp where and gender = 1 order by update_time desc;

会多出一个and导致SQL语法错误,解决方法就是加上where标签,如下图:

image-20231018142340652

where标签有两个作用:

  1. 如果标签内的if条件都不成立他就不会生成where
  2. 如果标签内存在多余的and或者or会自动去除掉他.

4.6 Set标签

Set标签和where标签的作用都是为了防止因为if标签导致错误的SQL拼接。

set标签会去除多余的逗号即

update emp set id=1,name='zhangsan';

假设这个是动态SQL生成的,那么在if判断时可能会产生多余的逗号,这个时候就使用set标签包裹即可。

4.7 trim标签

trim 一般用于去除 SQL 语句中多余的 AND 关键字、逗号,或者给 SQL 语句前拼接 where、set 等后缀,可用于选择性插入、更新、删除或者条件查询等操作。trim 语法格式如下。

<trim prefix="前缀" suffix="后缀" prefixOverrides="忽略前缀字符" suffixOverrides="忽略后缀字符">
    SQL语句
</trim>

属性解释:

  • prefix:给sql语句拼接的前缀

  • suffix:给sql语句拼接的后缀

  • **prefixOverrides:**去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定

    假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND"

  • suffixOverrides:去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定

trim标签常见用法:

    <insert id="insertDsMatirailPurchaseRegister" parameterType="DsMatirailPurchaseRegister">
        insert into ds_matirail_purchase_register
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="purchaseId != null">purchase_id,</if>
            <if test="purchaseObjects != null">purchase_objects,</if>
         </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="purchaseId != null">#{purchaseId},</if>
            <if test="purchaseObjects != null">#{purchaseObjects},</if>
        </trim>
    </insert>

4.8 foreach标签

foreach用来遍历集合,用法如下图:

image-20231018143029205
  • collection: 遍历的集合
  • item: 遍历出来的元素
  • separator: 分隔行
  • open: 遍历开始前拼接的SQL片段
  • close: 遍历结束后拼接的SQL片段

forEach常见的用法:

  1. 批量删除

    @Mapper
    public interface DemoDao {
        int deleteByIds(@Param("ids") List<Integer> ids);
    }
    
    <delete id="deleteByIds">
        DELETE FROM DemoUser WHERE id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>
    
  2. 批量插入

    @Mapper
    public interface DemoDao {
        int insertUsers(@Param("users") List<DemoUser> users);
    }
    
    <insert id="insertUsers">
        INSERT INTO DemoUser (name, age) VALUES 
        <foreach collection="users" item="user" separator=",">
            (#{user.name}, #{user.age})
        </foreach>
    </insert>
    
  3. 批量查询

    @Mapper
    public interface DemoDao {
        List<DemoUser> selectByIds(@Param("ids") List<Integer> ids);
    }
    
    <select id="selectByIds" resultType="com.example.DemoUser">
        SELECT id, name, age
        FROM DemoUser
        WHERE id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>
    

4.9 SQL和include标签

sql标签和include标签是搭配使用的,sql标签用来将重复内容的sql抽取出来,include负责调用这个sql语句。

image-20231018143256203

4.10 resultMap标签

4.10.1 resultMap标签基础使用

作用:将SQL语句的查询返回结果和java对象绑定。

    <resultMap type="MmDevice" id="MmDeviceResult">
        <result property="deviceId"    column="device_id"    />
        <result property="groupId"    column="group_id"    />
        <result property="orderId"    column="order_id"    />
        <result property="goodsId"    column="goods_id"    />
        <result property="goodsStyle"    column="goods_style"    />
        <result property="updateTime"    column="update_time"    />
        <result property="status"    column="status"    />
        <result property="operateTime"    column="operate_time"    />
    </resultMap>
  • type对应的java类的类名

  • id是给select语句等需要返回对象的resultMap属性使用的

例如此时可以给select标签使用:

<select id="selectMmDeviceByDeviceId" parameterType="Long" resultMap="MmDeviceResult">
        select device_id, group_id, order_id, goods_id, goods_style, update_time, status, operate_time from mm_device where device_id = #{deviceId}
</select>

此时resultMap对应的就是resultMap的id。

属性解释:

  • property对应的是java类里面的属性名

  • column对应的是数据库里面字段名

    需要注意,resultType和resultMap不能同时使用!


4.10.1 resultMap的继承、复用、嵌套

**继承:**继承已存在的 resultMap 标签进行扩展

举例:

  • 先有如下文件

    public class User {
        private int id;
        private String username;
        private String firstName;
        // Getter 和 Setter 略
    }
    
  • xml的写法

        <!-- 基础的 resultMap 定义 -->
        <resultMap id="BaseUserResultMap" type="User">
            <id property="id" column="id" />
            <result property="username" column="username" />
            <result property="email" column="email" />
        </resultMap>
    
        <!-- 继承 BaseUserResultMap 的 resultMap,并增加一个额外的字段映射 -->
        <resultMap id="ExtendedUserResultMap" type="User" extends="BaseUserResultMap">
            <result property="firstName" column="first_name" />
        </resultMap>
    

**复用:**跨mapper文件引用现存的 resultMap 标签

假设我们有两个不同的 XML 文件,分别定义了不同的 resultMap,我们想要在一个文件中引用另一个文件中定义的 resultMap

  • File1

        <!-- File1.xml 中定义的 resultMap -->
        <resultMap id="BaseUserResultMap" type="User">
            <id property="id" column="id" />
            <result property="username" column="username" />
            <result property="email" column="email" />
        </resultMap>
    
    
  • File2

        <!-- File2.xml 中引用 File1.xml 中的 resultMap -->
        <resultMap id="ExtendedUserResultMap" type="User">
            <!--通过这个resultMap refild来实现复用-->
            <resultMap refid="BaseUserResultMap" />
    
            <result property="firstName" column="first_name" />
            <result property="lastName" column="last_name" />
        </resultMap>
    

也可以跨文件复用sql标签的内容

    <!-- File1.xml 中定义的 SQL 片段 -->
    <sql id="selectFields">
        id, username, email
    </sql>
    <!-- File2.xml 中引用 File1.xml 中的 SQL 片段 -->
    <select id="selectUsers" resultMap="ExtendedUserResultMap">
        SELECT
        <include refid="selectFields"/>
        FROM users
    </select>

**嵌套:**指在一个 resultMap 中嵌套另一个 resultMap<collection> 元素来处理数据库查询结果中的复杂对象关系。这种技术使得可以将多个数据库表中的数据映射到一个复杂的 Java 对象中,或者处理一对多关系的数据

  • 嵌套单个对象

    public class User{
        private int id;
        private String username;
        private String email;
        private UserDetails details;
    }
    private class UserDetails{
        private int id;
        private String firstName;
        private String lastName;
        private String address;
    }
    
        <!-- 定义 user 表的 resultMap -->
        <resultMap id="UserResultMap" type="User">
            <id property="id" column="id" />
            <result property="username" column="username" />
            <result property="email" column="email" />
            <!-- 嵌套 user_details 表的 resultMap -->
            <!--property对应的是java类中属性名,javaType对应的是java类中类名,resultMap对应下放的id-->
            <association property="details" javaType="UserDetails" resultMap="UserDetailsResultMap" />
        </resultMap>
    
        <!-- 定义 user_details 表的 resultMap -->
        <resultMap id="UserDetailsResultMap" type="UserDetails">
            <id property="id" column="id" />
            <result property="firstName" column="first_name" />
            <result property="lastName" column="last_name" />
            <result property="address" column="address" />
        </resultMap>
    
  • 嵌套集合对象

    public class User{
        private int id;
        private String username;
        private String email;
        //就是嵌套这个
        private List<Order> orders;
    }
    private class Order{
        private int id;
        private String orderNumber;
        private String orderDate;
    }
    
        <!-- 定义 user 表的 resultMap,嵌套 order 表的 collection -->
        <resultMap id="UserOrdersResultMap" type="User">
            <id property="id" column="id" />
            <result property="username" column="username" />
            <result property="email" column="email" />
            <!-- 嵌套 order 表的 collection -->
             <!--property对应的是java类中属性名,ofType对应的是java类中类名,resultMap对应下放的id-->
            <collection property="orders" ofType="Order" resultMap="OrderResultMap" />
        </resultMap>
    
        <!-- 定义 order 表的 resultMap -->
        <resultMap id="OrderResultMap" type="Order">
            <id property="id" column="id" />
            <result property="orderNumber" column="order_number" />
            <result property="orderDate" column="order_date" />
        </resultMap>
    

嵌套+继承复合写法:一般用于主子表的关系映射

public class User{
    private int id;
    private String username;
    private String email;
    //就是嵌套这个
    private List<Order> orders;
}
private class Order{
    private int id;
    private String orderNumber;
    private String orderDate;
}
    <!--主表映射-->
	<resultMap type="User" id="UserResult">
        <result property="id"    column="id"    />
		<result property="username"    column="username"    />
       <result property="email"    column="email"    />
    </resultMap>
	<!--子表映射-->
    <resultMap type="Order" id="OrderResult">
        <result property="id"    column="sub_id"    />
        <result property="orderNumber"    column="sub_order_number"    />
         <result property="orderDate"    column="sub_order_date"    />
    </resultMap>

	<!--type是主表的type,继承的值也是主表的resultMap的id,主子表查询的resultMap就采用这个的id-->
    <resultMap id="OrderExtendData" type="User" extends="UserResult">
        <!-- resultMap对应的是子表的resultMap的id-->
        <!-- notNullColumn代表不能为null的字段-->
        <!-- javaType代表的是List集合-->
        <!-- property对应的是子表在主表里面的属性名 -->
        <collection property="orders"
                    notNullColumn="sub_id" javaType="java.util.List"
                    resultMap="OrderResult" />
    </resultMap>

五,PageHelper分页查询

分页查询逻辑:

image-20231018151159417

SQL语句逻辑:

image-20231018151047026

需要在后端专门创建一个PageBean来存储分页查询的数据:

image-20231018151247008

后端执行流程:

image-20231018151512988

使用PageHelper进行分页查询与普通分页查询的对比:

image-20231018152133330

使用PageHelper需要引入依赖:

image-20231018152102202