《SpringBoot 3:入门与应用实战》第 13 章 整合 MyBatis MyBatis 简单开发 阅读笔记 39

《SpringBoot 3:入门与应用实战》第 13 章 整合 MyBatis MyBatis 简单开发 阅读笔记 39

13.3 MyBatis 简单开发

13.3.1 常用的配置属性

在 MyBatis 原生的配置文件 SqlMapConfig.xml 中可配置的内容非常多,在 Spring Boot 整合之后部分配置可以使用全局配置文件代替,这些配置属性都以 mybatis.* 格式命名。表列举了一些常见的配置属性,并且在 SqlMapConfig.xml 中的配置位置也一并展示,读者可以借助 IDE 查看全部支持的配置属性。

以下是 Spring Boot 整合 MyBatis 时常用的配置属性,按功能分类整理:

🗂️ MyBatis 基础配置(mybatis.*

配置属性 说明 示例值
mybatis.mapper-locations Mapper XML 映射文件路径,支持通配符 classpath:mapper/**/*.xml
mybatis.type-aliases-package 实体类别名包扫描路径,XML 中可直接用类名 com.example.entity
mybatis.type-handlers-package TypeHandler 扫描路径 com.example.handler
mybatis.config-location MyBatis 配置文件位置(与 configuration 节点互斥) classpath:mybatis-config.xml
mybatis.check-config-location 启动时是否检查配置文件存在 false
mybatis.executor-type 执行器类型:SIMPLE / REUSE / BATCH SIMPLE

⚙️ MyBatis Configuration 配置(mybatis.configuration.*

配置属性 说明 默认值
map-underscore-to-camel-case 开启下划线字段自动映射为驼峰属性 true
cache-enabled 是否开启二级缓存 true
lazy-loading-enabled 是否开启延迟加载 false
aggressive-lazy-loading 是否激进懒加载(任一方法调用即加载全部) true
log-impl 指定日志实现(开发环境常用 StdOutImpl 打印 SQL) null
use-generated-keys 是否允许 JDBC 自动获取生成的主键 false
use-column-label 是否使用列标签代替列名进行结果映射 true
default-statement-timeout SQL 执行默认超时时间(秒) 未设置
auto-mapping-behavior 自动映射策略:NONE / PARTIAL / FULL PARTIAL
local-cache-scope 一级缓存范围:SESSION / STATEMENT SESSION
call-setters-on-nulls 结果为 null 时是否调用 Setter 方法 false
default-enum-type-handler 默认枚举类型处理器

另一个比较常用的配置是 SQL 语句的打印,在 Spring Boot 中配置 Mapper 执行时打印 SQL 和参数的方式是直接在 application.yaml 中配置日志级别。

yaml 复制代码
logging.level.com.linkedbear.springboot.mybatis.mapper=debug

13.3.2 注解式 Mapper 接口

项目开发的多数场景下使用 Mapper 接口与 mapper.xml 一一对应,借助动态代理机制完成关联,另外对于一些相对简单的 SQL 语句完全可以直接在 Mapper 接口上使用 CRUD 注解完成接口定义。代码是 UserMapper 接口中使用 CRUD 注解定义的 3 个方法,它同样可以使用占位符传入参数,只需要相应地在参数列表中使用 @Param 给参数定义名称。

java 复制代码
package com.yangjunbo.springboot.mybatis;

import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface UserMapper {

    void save(User user);
    List<User> findAll();

    @Select("select * from tbl_user where name like concat('%', #{name}, '%')")
    List<User> findAllByNameLike(@Param("name") String name);

    @Delete("delete from tbl_user where id = #{id}")
    int deleteById(String id);

    @Update("CREATE TABLE tbl_role (\n"
            + "  id int(11) NOT NULL AUTO_INCREMENT,\n"
            + "  code varchar(20) NULL,\n"
            + "  name varchar(32) NULL,\n"
            + "  PRIMARY KEY (id)\n"
            + ");")
    int excuteDDL();

}

可以发现 MyBatis 本身提供了 CRUD 的基础注解,可以编写类似于 mapper.xml 中的 SQL 语句,对于简单 SQL 的编写效率会更高,甚至还可以使用 @Update 执行 DDL 语句。

13.3.3 动态 SQL

MyBatis 相较于 spring-jdbc 中的 JdbcTemplate 等简单的 JDBC 封装,其一大优势就是灵活的动态 SQL 机制,合理利用动态 SQL 机制可以编写出多样的符合业务场景的查询、写入数据库的 SQL 语句。代码列举了几种动态 SQL 的使用方式,这些动态 SQL 的标签都是日常开发中使用频率最高的。

xml 复制代码
    <select id="findAllByCondition" parameterType="map" resultType="User">
        select * from tbl_user
        <where> <!-- 使用where标签设置查询条件,可以屏蔽掉第一个多余的and -->
            <if test="id != null"> <!-- 使用if标签判断和拼接 -->
                and id = #{id}
            </if>
            <if test="name != null and name != ''">
                and name like concat('%', #{name}, '%')
            </if>
            <if test="ids != null">
                and id in <!-- 使用foreach标签遍历集合 -->
                <foreach collection="ids" item="id" open="(" close=")" separator=",">
                    #{id}
                </foreach>
            </if>
        </where>
    </select>
 
    <update id="updateById">
        update tbl_user
        <set> <!-- 使用set标签设置属性,可以屏蔽掉最后一个逗号 -->
            <if test="name != null and name != ''">
                name = #{name},
            </if>
            <if test="tel != null and tel != ''">
                tel = #{tel},
            </if>
        </set>
        where id = #{id}
    </update>

简单来看,动态 SQL 可以分为 select 类、update 类以及通用抽取的 SQL 片段 3 种类别。其中 select 类的动态 SQL 可以使用的标签最多,可以实现判断、选择、循环、截取等动态 SQL 逻辑,update 类的动态 SQL 也可以针对 SQL 语法中的一些场景进行比较实用的处理。

13.3.4 缓存机制

MyBatis 考虑运行时的查询效率,引入了两层级缓存机制,其中一级缓存是 SqlSession 级别的,二级缓存是 SqlSessionFactory 级别的。

通常情况下,MyBatis 的一级缓存默认开启并自动使用,一级缓存基于 SqlSession,也就是基于一个事务,所以在一个事务中连续两次发起同样的查询动作后,第一次查询的结果会存入缓存中,第二次查询动作将直接使用第一次查询的缓存结果返回。下面可以简单测试一下效果,编写一个新的 Service 方法,标注 @Transactional 后连续调用两次 UserMapper 的 findAll 方法。随后,在 UserController 中编写一个 Handler 方法,以调用 UserService 的 testCache1 方法。

java 复制代码
package com.yangjunbo.springboot.mybatis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Transactional(rollbackFor = Exception.class)
    public List<User> test() {
        User user = new User();
        user.setName("test mybatis");
        user.setTel("7654321");
        userMapper.save(user);
    
        return userMapper.findAll();
    }

    @Transactional(rollbackFor = Exception.class)
    public void testCache1() {
        System.out.println("发起第一次查询:");
        userMapper.findAll();
        System.out.println("发起第二次查询:");
        userMapper.findAll();
    }

}
java 复制代码
package com.yangjunbo.springboot.mybatis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/test1")
    public List<User> test1() {
        return userService.test();
    }

    @GetMapping("/test2")
    public void test2() {
        userService.testCache1();
    }

}

重启工程后访问 /user/test2 接口,观察控制台的输出结果如下,可以发现发起第二次查询时的确没有发送 SQL 语句,证明一级缓存已经生效。

MyBatis 的二级缓存虽然在 Spring Boot 整合中默认开启,但若要使用具体的二级缓存需要手动开启,开启的方式是在 Mapper 接口上标注 @CacheNamespace,或者在 mapper.xml 中编写一个空的 cache 标签即可。二级缓存以 namespace 为单位隔离,一个 namespace 共享一个二级缓存区域。二级缓存是 SqlSessionFactory 级别的,在一个 SqlSessionFactory 的范围内创建的 SqlSession 均可以共享这些缓存。通常在项目开发中不会主动使用 MyBatis 的二级缓存,尤其是在分布式或微服务项目中,因为 MyBatis 的二级缓存默认保存在内存中,如果多个微服务实例在不同的时间点缓存数据,则有可能出现数据不一致的情况。

13.3.5 插件机制

MyBatis 中的最后一个重要机制是插件机制,也就是所谓的拦截器。MyBatis 的插件本身是一些能拦截某些 MyBatis 核心组件方法、增强功能的拦截器,MyBatis 允许在 SQL 语句执行过程中的某些切入点进行拦截增强,共有四种可供增强的切入点:

  • Executor(update, query, flushStatements, commit, rollback, getTransaction,close, isClosed);
  • ParameterHandler(getParameterObject, setParameters);
  • ResultSetHandler(handleResultSets, handleOutputParameters);
  • StatementHandler(prepare, parameterize, batch, update, query)。

MyBatis 插件的使用场景比较广泛,可以应用于分页、数据权限过滤、性能分析等场景。PageHelper 是一个基于 Interceptor 的分页插件,它通过拦截查询语句并根据连接数据库的类型将其动态重构为适用于分页场景的 SQL 语句。

代码展示了 PageHelper 的使用方式,在发起查询之前只需要调用 PageHelper 的 startPage 方法即可开启分页。

另一种使用方式是将查询结果再封装一层,PageHelper 提供了一个描述分页相关的对象 PageInfo,这个对象中不仅有分页查询的列表数据,还包含当前页码、每页大小、总条数等信息,可以在查询完数据后手动构造一个 PageInfo 对象,这样响应到客户端的数据中就会包含上述提到的信息。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.1.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yangjunbo</groupId>
    <artifactId>springboot-mybatis-a</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-mybatis-a</name>
    <description>springboot-mybatis-a</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webmvc-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>6.1.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
java 复制代码
package com.yangjunbo.springboot.mybatis;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.yangjunbo.springboot.mybatis.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Transactional(rollbackFor = Exception.class)
    public List<User> test() {
        User user = new User();
        user.setName("test mybatis");
        user.setTel("7654321");
        userMapper.save(user);
    
        return userMapper.findAll();
    }

    @Transactional(rollbackFor = Exception.class)
    public void testCache1() {
        System.out.println("发起第一次查询:");
        userMapper.findAll();
        System.out.println("发起第二次查询:");
        userMapper.findAll();
    }

    public List<User> testPage1() {
        PageHelper.startPage(1, 2);
        return userMapper.findAll();
    }

    public PageInfo<User> testPage2(Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<User> userList = userMapper.findAll();
        return new PageInfo<>(userList);
    }

}
java 复制代码
package com.yangjunbo.springboot.mybatis;

import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/test1")
    public List<User> test1() {
        return userService.test();
    }

    @GetMapping("/test2")
    public void test2() {
        userService.testCache1();
    }

    @GetMapping("/test3")
    public List<User> test3() {
        return userService.testPage1();
    }

    @GetMapping("/test4")
    public PageInfo<User> test4() {
        return userService.testPage2(1,2);
    }

}
相关推荐
xieliyu.1 小时前
计算机网络:Fiddler 抓包工具使用教程 + HTTP 协议报文格式详解
java·笔记·计算机网络·测试工具·http·java-ee·fiddler
点心的游戏开发世界1 小时前
GDScript 入门笔记(二):变量与数据类型
笔记·游戏引擎·godot
Mr.敦的私房菜2 小时前
【SpringEvent】Spring Boot / Spring Framework 事件大全
spring boot·spring
程序员阿明2 小时前
spring boot4+springAI 2加redis多轮对话存储
spring boot·redis·后端
蒸蒸yyyyzwd2 小时前
CPP选手秋招准备学习笔记day25
笔记·学习
Mr.敦的私房菜2 小时前
【SpringBoot请求记录】Spring Boot Actuator 自定义记录 HTTP 服务请求
spring boot·mvc·运维开发
java1234_小锋2 小时前
MyBatis-Plus 3.5.15 已全面支持 Spring Boot 4.0 及 Jackson 3.0
java·spring boot·mybatis
xixingzhe22 小时前
spring boot项目接口访问慢问题解决方案
java·数据库·spring boot
MetaLite2 小时前
SpringBoot整合FastJson2数据脱敏-接口日志与失败降级
java·spring boot·后端