思途Mybatis学习 0805

一、MyBatis 简介

  • 定义 :MyBatis 是一个 半自动化的 ORM(Object-Relational Mapping)框架,用于将 Java 对象与数据库记录进行映射。
  • 特点
    • 轻量级、灵活、易于掌握。
    • SQL 与代码分离,支持动态 SQL。
    • 相比 Hibernate 等全自动 ORM 框架,MyBatis 提供更高的 SQL 控制能力。

二、Spring Boot 整合 MyBatis 步骤

1. 添加依赖

pom.xml 中引入 MyBatis 启动器:

复制代码

xml

深色版本

复制代码
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version> <!-- 推荐使用最新稳定版本 -->
</dependency>

⚠️ 注意:该依赖会自动包含 mybatismybatis-spring 和自动配置模块。


2. 配置文件设置(application.yml)

复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  type-aliases-package: com.example.demo.entity    # 别名包路径
  mapper-locations: classpath:mapper/*.xml         # XML 映射文件位置
  configuration:
    map-underscore-to-camel-case: true             # 开启驼峰命名自动映射 a_column -> aColumn

3. 编写 Mapper 接口

使用 @Mapper 注解标记接口,或在启动类上加 @MapperScan 扫描包。

复制代码
@Mapper
public interface UserMapper {
    User findById(Long id);
}

或在主启动类上:

复制代码
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4. 编写映射 XML 文件(Mapper XML)

文件路径:resources/mapper/UserMapper.xml

复制代码
<?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.example.demo.mapper.UserMapper">

    <select id="findById" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>

</mapper>

✅ 注意:

  • namespace 必须对应 Mapper 接口的全限定名。
  • id 在同一个 XML 中必须唯一,且与接口方法名一致。
  • #{} 是预编译占位符,防止 SQL 注入;${} 是字符串拼接,需谨慎使用。

5. 安装 MyBatis-X 插件(推荐)

IntelliJ IDEA 插件:MyBatisX

  • 快速跳转:Mapper 接口 ↔ XML 映射文件。
  • 自动生成 CRUD 方法和 SQL 片段。
  • 提高开发效率。

三、动态 SQL 与参数传递

MyBatis 使用 OGNL(Object-Graph Navigation Language)表达式 解析参数,通过反射获取对象属性值。

1. 单个参数(POJO 或 Map)

可以直接通过属性名访问:

复制代码
public User findByName(User user); // user.getName()

<select id="findByName" resultType="User">
    SELECT * FROM user WHERE name = #{name}
</select>

#{name} 自动从 User 对象中调用 getName() 获取值。


2. 多个参数(必须指定参数名)

Java 方法:

复制代码
List<User> findUsers(@Param("name") String name, @Param("age") int age);

XML 使用:

复制代码
<select id="findUsers" resultType="User">
    SELECT * FROM user
    WHERE name LIKE #{name} AND age > #{age}
</select>

❗ 若不使用 @Param,多个参数需用 param1, param2arg0, arg1 引用,可读性差,强烈建议使用 @Param 显式命名


3. 使用 <bind> 标签构造新变量

用于模糊查询等场景:

复制代码
<select id="findByNameLike" parameterType="string" resultType="User">
    <bind name="pattern" value="'%' + _parameter + '%'" />
    SELECT * FROM user WHERE name LIKE #{pattern}
</select>

或:

复制代码
<bind name="nameLike" value="'%'+name+'%'"/>
AND name LIKE #{nameLike}

🔍 说明:_parameter 表示整个参数对象。若参数是简单类型(如 String),则直接可用。


4. ${} vs #{}

方式 是否预编译 是否防注入 适用场景
#{} ✅ 是 ✅ 安全 绝大多数情况(推荐)
${} ❌ 否 ❌ 易被注入 动态表名、排序字段等

示例:

复制代码
ORDER BY ${columnName}  <!-- 只能用 ${} -->

⚠️ 使用 ${} 时务必校验输入合法性!


四、结果映射(ResultMap)

1. 手动映射字段(解决列名与属性不一致)

复制代码
<resultMap id="userResultMap" type="User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="email" column="user_email"/>
</resultMap>

<select id="findAll" resultMap="userResultMap">
    SELECT user_id, user_name, user_email FROM user
</select>

2. 关联查询(一对一、一对多)

(1)一对一关联

例如:User ↔ Profile

复制代码
<resultMap id="userWithProfileMap" type="User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="profile" javaType="Profile">
        <id property="id" column="pid"/>
        <result property="bio" column="bio"/>
    </association>
</resultMap>

<select id="findUserWithProfile" resultMap="userWithProfileMap">
    SELECT u.id, u.name, p.id AS pid, p.bio
    FROM user u
    LEFT JOIN profile p ON u.id = p.user_id
    WHERE u.id = #{id}
</select>

(2)一对多关联

例如:Department ↔ List<Employee>

复制代码
<resultMap id="deptWithEmpsMap" type="Department">
    <id property="id" column="dept_id"/>
    <result property="name" column="dept_name"/>
    <collection property="employees" ofType="Employee">
        <id property="id" column="emp_id"/>
        <result property="name" column="emp_name"/>
    </collection>
</resultMap>

<select id="findDeptWithEmployees" resultMap="deptWithEmpsMap">
    SELECT d.id AS dept_id, d.name AS dept_name,
           e.id AS emp_id, e.name AS emp_name
    FROM department d
    LEFT JOIN employee e ON d.id = e.dept_id
    WHERE d.id = #{id}
</select>

五、N+1 查询问题与性能优化

1. 什么是 N+1 问题?

当使用 associationcollection 延迟加载时,MyBatis 可能先查主表(1次),再对每条记录发起关联查询(N次),共 N+1 次 SQL。

2. 解决方案

✅ 方案一:使用 联合查询 + 嵌套映射(推荐)

如上一对多示例,一次性 JOIN 查询,避免 N+1。

✅ 方案二:开启 延迟加载(Lazy Loading)
复制代码
mybatis:
  configuration:
    lazy-loading-enabled: true
    aggressive-lazy-loading: false  # false:仅加载被调用的属性

⚠️ 延迟加载会创建新会话,一级缓存失效

✅ 方案三:使用 二级缓存

提升跨会话的数据复用性。

开启步骤:
  1. 全局开启缓存:

    mybatis:
    configuration:
    cache-enabled: true

  2. 在 Mapper XML 中声明缓存:

    <mapper namespace="com.example.mapper.UserMapper"> <cache/>
    复制代码
     <select id="findById" useCache="true" resultType="User">
         SELECT * FROM user WHERE id = #{id}
     </select>
    
     <update id="updateUser" flushCache="true">
         UPDATE user SET name = #{name} WHERE id = #{id}
     </update>
    </mapper>
  • useCache="true":查询结果放入二级缓存(默认 select 为 true)。
  • flushCache="true":执行后清空缓存(默认 insert/update/delete 为 true)。
  • 实体类需实现 Serializable 接口。

六、自动分页(集成 PageHelper)

1. 添加依赖

复制代码
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.7</version>
</dependency>

2. 使用

复制代码
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public PageInfo<User> getUsers(int pageNum, int pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<User> users = userMapper.findAll();
        return new PageInfo<>(users);
    }
}

返回 JSON 示例:

复制代码
{
  "pageNum": 1,
  "pageSize": 10,
  "total": 100,
  "pages": 10,
  "list": [...]
}

七、补充知识点

1. @RestController@ResponseBody

  • @RestController = @Controller + @ResponseBody
  • 所有方法返回值直接作为响应体(JSON/XML),无需额外标注 @ResponseBody

2. 类型处理器(TypeHandler)

自定义 Java 类型与数据库类型的转换,如枚举、LocalDateTime 等。

复制代码
@MappedTypes(LocalDate.class)
@MappedJdbcTypes(JdbcType.DATE)
public class LocalDateTypeHandler extends BaseTypeHandler<LocalDate> {
    // 实现 setParameter、getResult 方法
}

注册方式:

复制代码
@Configuration
public class MyBatisConfig {
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.getTypeHandlerRegistry()
            .register(LocalDate.class, new LocalDateTypeHandler());
    }
}

八、总结

功能 推荐做法
参数传递 多参数用 @Param 显式命名
模糊查询 <bind> 构造 pattern,配合 #{}
字段映射 列名转驼峰开启 map-underscore-to-camel-case
关联查询 优先使用 JOIN + resultMap 避免 N+1
性能优化 合理使用一级/二级缓存、延迟加载
分页 集成 PageHelper
开发效率 安装 MyBatisX 插件
相关推荐
浮游本尊38 分钟前
Java学习第22天 - 云原生与容器化
java
渣哥2 小时前
原来 Java 里线程安全集合有这么多种
java
间彧3 小时前
Spring Boot集成Spring Security完整指南
java
间彧3 小时前
Spring Secutiy基本原理及工作流程
java
Java水解4 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆6 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学6 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole7 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊7 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端