Appearance
简介
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数及获取结果集的工作。
MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)为数据库中的记录。
MyBatis 的特点
- 简单易学:MyBatis 入门相对简单,学习成本低。
- SQL 可控:直接编写原生 SQL,SQL 语句灵活,可控性强。
- 提供映射标签:支持对象与数据库之间的 ORM 字段关系映射。
- 动态 SQL:强大的动态 SQL 能够满足各种需求。
- 接口绑定:支持将接口和 XML 映射文件绑定。
在SpringBoot中集成
环境搭建与基础配置
- Maven依赖配置
xml
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis整合 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>- application.yml核心配置
yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml # XML映射文件路径
type-aliases-package: com.example.entity # 实体类别名
configuration:
map-underscore-to-camel-case: true # 自动驼峰转换基本使用
接口和xml文件定义
CRUD操作详解
- Mapper接口定义
java
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User getById(@Param("id") Long id);
@Insert("INSERT INTO users(name,email) VALUES(#{name},#{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@Update("UPDATE users SET name=#{name} WHERE id=#{id}")
int updateName(@Param("id") Long id, @Param("name") String name);
@Delete("DELETE FROM users WHERE id = #{id}")
int deleteById(Long id);
}- XML映射文件示例(resources/mapper/UserMapper.xml)
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.mapper.UserMapper">
<resultMap id="userMap" type="User">
<id column="id" property="id"/>
<result column="create_time" property="createTime"/>
</resultMap>
<select id="selectByCondition" resultMap="userMap">
SELECT * FROM users
WHERE status = #{status}
<if test="name != null">
AND name LIKE CONCAT('%',#{name},'%')
</if>
</select>
</mapper>动态 SQL 标签
标签
MyBatis 提供了强大的动态 SQL 功能,借助类似于 JSTL 或者其他类似的 XML 基础的文本处理机制。
if 标签
<select id="findByCondition" parameterType="User" resultType="User">
SELECT * FROM user
WHERE 1=1
<if test="username != null and username != ''">
AND username = #{username}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</select>choose、when、otherwise 标签
<select id="findByIdOrUsername" parameterType="User" resultType="User">
SELECT * FROM user
WHERE
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="username != null and username != ''">
username = #{username}
</when>
<otherwise>
1=2 <!-- 无效的条件,保证查询不到数据 -->
</otherwise>
</choose>
</select>foreach 标签
<select id="findByIds" parameterType="list" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach item="id" index="index" collection="list"
open="(" separator="," close=")">
#{id}
</foreach>
</select>动态SQL使用
动态SQL实战
- 动态条件查询
xml
<select id="searchUsers" resultType="User">
SELECT * FROM users
<where>
<if test="name != null">
AND name LIKE #{name}
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<choose>
<when test="status != null">
AND status = #{status}
</when>
<otherwise>
AND status = 1
</otherwise>
</choose>
</where>
ORDER BY create_time DESC
</select>- 批量插入示例
xml
<insert id="batchInsert">
INSERT INTO users (name, email)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.email})
</foreach>
</insert>高级查询
高级查询与优化
一对一映射
- 在我们平时进行SQL查询时,往往会有
一对一的情况,比如说我们这里有资源分类ums_resource_category和资源ums_resource两张表,资源和分类就是一对一的关系,如果你不想改动原实体类的话,可以编写一个扩展类继承UmsResource,并包含UmsResourceCategory属性;
- 在我们平时进行SQL查询时,往往会有
/**
* @auther macrozheng
* @description UmsResource扩展类
* @date 2022/10/20
* @github https://github.com/macrozheng
*/
@Data
public class UmsResourceExt extends UmsResource {
private UmsResourceCategory category;
}- 例如我们需要编写一个
根据资源ID获取资源及分类信息的方法;
/**
* @auther macrozheng
* @description 自定义UmsResource表查询
* @date 2022/10/20
* @github https://github.com/macrozheng
*/
@Repository
public interface UmsResourceDao {
/**
* 根据资源ID获取资源及分类信息
*/
UmsResourceExt selectResourceWithCategory(Long id);
}- 在xml中的具体SQL实现如下,我们可以通过给
ums_resource_category表中字段取以category.xxx的别名来自动进行自动映射;
<select id="selectResourceWithCategory" resultType="com.macro.mall.tiny.domain.UmsResourceExt">
select ur.id,
ur.create_time as createTime,
ur.name,
ur.url,
ur.description,
ur.category_id as categoryId,
urc.id as "category.id",
urc.name as "category.name",
urc.sort as "category.sort",
urc.create_time as "category.createTime"
from ums_resource ur
left join ums_resource_category urc on ur.category_id = urc.id
where ur.id = #{id}
</select>- 当然除了这种方式以外,我们还可以通过
ResultMap+association标签来实现,不过在此之前我们在编写xml文件的时候,一般习惯于先给当前文件写一个BaseResultMap,用于对当前表的字段和对象属性进行直接映射,例如在UmsResourceCategoryDao.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.macro.mall.tiny.dao.UmsResourceCategoryDao">
<resultMap id="BaseResultMap" type="com.macro.mall.tiny.model.UmsResourceCategory">
<id property="id" column="id"/>
<result property="createTime" column="create_time"/>
<result property="name" column="name"/>
<result property="sort" column="sort"/>
</resultMap>
</mapper>- 在
UmsResourceDao.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.macro.mall.tiny.dao.UmsResourceDao">
<resultMap id="BaseResultMap" type="com.macro.mall.tiny.model.UmsResource">
<id property="id" column="id"/>
<result property="createTime" column="create_time"/>
<result property="name" column="name"/>
<result property="url" column="url"/>
<result property="description" column="description"/>
<result property="categoryId" column="category_id"/>
</resultMap>
</mapper>- 编写完成后,我们的一对一
ResultMap实现就很简单了,我们可以使用association标签进行一对一管理,配置columnPrefix属性将匹配到的字段直接映射到关联对象中去; `
<resultMap id="ResourceWithCategoryMap" type="com.macro.mall.tiny.domain.UmsResourceExt" extends="BaseResultMap">
<association property="category" resultMap="com.macro.mall.tiny.dao.UmsResourceCategoryDao.BaseResultMap" columnPrefix="category_"/>
</resultMap>- 然后再编写下Dao中方法对应SQL实现即可,这里直接使用上面的ResultMap,同时给
ums_resource_category表中的字段指定了category_前缀以便于映射。
<select id="selectResourceWithCategory2" resultMap="ResourceWithCategoryMap">
select ur.id,
ur.create_time,
ur.name,
ur.url,
ur.description,
ur.category_id,
urc.id as category_id,
urc.name as category_name,
urc.sort as category_sort,
urc.create_time as category_create_time
from ums_resource ur
left join ums_resource_category urc on ur.category_id = urc.id
where ur.id = #{id}
</select>一对多映射
- 一对多关联映射
通过collection标签建立一对多关系;
xml
<resultMap id="userWithOrdersMap" type="User">
<id column="user_id" property="id"/>
<collection property="orders" ofType="Order">
<id column="order_id" property="id"/>
<result column="order_no" property="orderNo"/>
</collection>
</resultMap>
<select id="getUserWithOrders" resultMap="userWithOrdersMap">
SELECT u.id as user_id, o.id as order_id, o.order_no
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{userId}
</select>分页插件
- PageHelper分页实现
可以直接使用开源的PageHelper插件即可,首先在pom.xml中添加它的Starter;
<!--MyBatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper-starter.version}</version>
</dependency>在查询方法之前使用它的startPage方法传入分页参数即可,分页后的得到的数据可以在users中获取到。
java
// Service层实现
public PageInfo<User> getUsersByPage(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
return new PageInfo<>(users);
}二级缓存
- 二级缓存配置
xml
<!-- 开启Mapper接口级别的二级缓存 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
<!-- 在application.yml中启用全局缓存 -->
mybatis:
configuration:
cache-enabled: true关键问题说明
- 参数传递陷阱
- 多个参数必须使用@Param注解
- 集合参数应使用
collection="array/list"(数组/List)
- 动态SQL最佳实践
- 优先使用
<where>标签避免空WHERE问题 <set>标签用于UPDATE语句自动处理逗号
- 性能优化建议
- 大数据量分页使用PageHelper物理分页
- 关联查询N+1问题使用
<collection>的fetchType="lazy"
以上内容已通过MyBatis 3.5+和SpringBoot 3.x环境验证,建议结合官方文档进行扩展学习。
在实际开发中,建议使用MyBatis Generator或MyBatis-Plus进行效率提升。
其他
MyBatis 缓存机制
一级缓存
- 作用域: SqlSession 级别。
- 特性: 默认开启,无需配置。同一个 SqlSession 内,相同的查询会从缓存中获取数据。
二级缓存
- 作用域: Mapper 映射文件级别,基于 namespace。
二级缓存配置
xml
<!-- 开启Mapper接口级别的二级缓存 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
<!-- 在application.yml中启用全局缓存 -->
mybatis:
configuration:
cache-enabled: true插件机制
后续可以记录一下分页插件等内容,这里大概了解一下。
MyBatis 提供了强大的插件机制,可以在某些特定点拦截执行。
- 拦截类型: Executor、ParameterHandler、ResultSetHandler、StatementHandler。
- 实现步骤:
- 创建自定义插件类,实现
Interceptor接口。 - 在
mybatis-config.xml中注册插件。
示例:
package com.example.plugin;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
@Intercepts({@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
)})
public class MyPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 在执行方法前后添加自定义逻辑
System.out.println("Before method execute");
Object result = invocation.proceed();
System.out.println("After method execute");
return result;
}
@Override
public void setProperties(Properties properties) {
// 读取配置的属性
}
}在 mybatis-config.xml 中注册插件:
<plugins>
<plugin interceptor="com.example.plugin.MyPlugin">
<!-- 配置插件属性 -->
<property name="someProperty" value="someValue"/>
</plugin>
</plugins>