这是一份全面的Mybatis和MybatisPlus开发笔记,涵盖从基础使用到进阶特性的所有内容,适合日常开发参考和深入学习。
Mybatis核心基础
Mybatis简介与架构
什么是Mybatis
简要描述:MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。
核心概念:
- 持久层框架:专门处理数据持久化的框架
- 半自动ORM:相比Hibernate等全自动ORM,MyBatis需要手写SQL,提供更好的SQL控制能力
- SQL映射:通过XML或注解将SQL语句与Java方法进行映射
与其他框架的区别:
- vs JDBC:减少样板代码,提供更好的参数映射和结果集处理
- vs Hibernate:更灵活的SQL控制,更适合复杂查询和性能优化
- vs Spring Data JPA:更接近原生SQL,学习成本更低
Mybatis整体架构
核心架构层次:
应用层 (Application)
↓
API接口层 (SqlSession)
↓
数据处理层 (Executor, StatementHandler, ParameterHandler, ResultSetHandler)
↓
基础支撑层 (Configuration, MappedStatement, Cache)
↓
数据库层 (Database)
架构组件说明:
- Configuration:全局配置信息,包含所有配置项
- SqlSessionFactory:SqlSession工厂,负责创建SqlSession
- SqlSession:执行SQL的会话,提供操作数据库的API
- Executor:执行器,负责SQL的执行和缓存维护
- MappedStatement:映射语句,包含SQL信息和映射规则
核心组件详解
SqlSessionFactory
简要描述:SqlSessionFactory是MyBatis的核心工厂类,负责创建SqlSession实例。它是线程安全的,在应用运行期间应该只创建一次。
核心概念:
- 工厂模式:使用工厂模式创建SqlSession
- 单例模式:整个应用中通常只需要一个SqlSessionFactory实例
- 线程安全:可以被多个线程同时访问
创建方式:
// 方式1:通过XML配置文件创建
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 方式2:通过Java代码配置创建
DataSource dataSource = getDataSource(); // 获取数据源
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(UserMapper.class); // 添加Mapper
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
最佳实践:
- 使用单例模式管理SqlSessionFactory
- 在Spring环境中通过依赖注入管理
- 避免频繁创建和销毁
SqlSession
简要描述:SqlSession是MyBatis工作的主要顶层API,它提供了执行SQL命令、获取映射器和管理事务的方法。
核心概念:
- 会话概念:代表一次数据库会话
- 非线程安全:每个线程都应该有自己的SqlSession实例
- 事务管理:负责事务的提交和回滚
基本使用:
// 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
// 方式1:直接执行SQL
User user = sqlSession.selectOne("com.example.UserMapper.selectUser", 1);
// 方式2:通过Mapper接口
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user2 = mapper.selectUser(1);
// 提交事务
sqlSession.commit();
} finally {
// 关闭会话
sqlSession.close();
}
重要方法说明:
// 查询方法
T selectOne(String statement, Object parameter); // 查询单个对象
List<E> selectList(String statement, Object parameter); // 查询列表
Map<K,V> selectMap(String statement, Object parameter, String mapKey); // 查询Map
// 增删改方法
int insert(String statement, Object parameter); // 插入
int update(String statement, Object parameter); // 更新
int delete(String statement, Object parameter); // 删除
// 事务管理
void commit(); // 提交事务
void rollback(); // 回滚事务
void close(); // 关闭会话
使用注意事项:
- SqlSession不是线程安全的,不能在多线程间共享
- 使用完毕后必须关闭,建议使用try-with-resources
- 在Spring环境中通过SqlSessionTemplate管理
Mapper接口
简要描述:Mapper接口是MyBatis的核心特性之一,它允许你定义接口方法来映射SQL语句,无需编写实现类。
核心概念:
- 接口映射:通过接口方法名映射到SQL语句
- 动态代理:MyBatis使用动态代理技术生成接口实现
- 类型安全:提供编译时类型检查
定义Mapper接口:
public interface UserMapper {
// 查询单个用户
User selectUser(Long id);
// 查询用户列表
List<User> selectUsers(@Param("name") String name, @Param("age") Integer age);
// 插入用户
int insertUser(User user);
// 更新用户
int updateUser(User user);
// 删除用户
int deleteUser(Long id);
// 复杂查询
List<User> selectUsersByCondition(@Param("condition") UserCondition condition);
}
对应的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">
<!-- 查询单个用户 -->
<select id="selectUser" parameterType="long" resultType="User">
SELECT id, name, age, email
FROM user
WHERE id = #{id}
</select>
<!-- 查询用户列表 -->
<select id="selectUsers" resultType="User">
SELECT id, name, age, email
FROM user
WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</select>
<!-- 插入用户 -->
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, age, email)
VALUES (#{name}, #{age}, #{email})
</insert>
</mapper>
注解方式映射:
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectUser(Long id);
@Insert("INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
@Update("UPDATE user SET name=#{name}, age=#{age} WHERE id=#{id}")
int updateUser(User user);
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteUser(Long id);
}
Executor执行器
简要描述:Executor是MyBatis的核心执行器,负责SQL语句的执行、参数设置、结果集处理和缓存管理。
核心概念:
- 执行器类型:Simple、Reuse、Batch三种类型
- 缓存管理:负责一级缓存的维护
- SQL执行:协调StatementHandler、ParameterHandler、ResultSetHandler工作
执行器类型详解:
// SimpleExecutor:简单执行器(默认)
// 每次执行都会创建新的Statement
public class SimpleExecutor extends BaseExecutor {
// 特点:简单直接,每次都创建新的Statement
// 适用:一般的CRUD操作
}
// ReuseExecutor:重用执行器
// 重用Statement,减少Statement创建开销
public class ReuseExecutor extends BaseExecutor {
// 特点:重用相同SQL的Statement
// 适用:有重复SQL执行的场景
}
// BatchExecutor:批量执行器
// 支持批量操作,提高批量插入/更新性能
public class BatchExecutor extends BaseExecutor {
// 特点:支持JDBC批量操作
// 适用:大量数据的批量操作
}
配置执行器类型:
<!-- 在mybatis-config.xml中配置 -->
<configuration>
<settings>
<!-- SIMPLE, REUSE, BATCH -->
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
</configuration>
执行流程:
// Executor执行SQL的基本流程
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler) {
// 1. 获取BoundSql(包含SQL和参数信息)
BoundSql boundSql = ms.getBoundSql(parameter);
// 2. 创建缓存Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 3. 查询(先查缓存,再查数据库)
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
Mybatis 核心配置
全局配置文件详解
简要描述:MyBatis的全局配置文件(通常命名为mybatis-config.xml)包含了影响MyBatis行为的设置和属性信息。
核心概念:
- 全局配置:影响整个MyBatis实例的配置
- 层次结构:配置元素有严格的顺序要求
- 环境配置:支持多环境配置(开发、测试、生产)
完整配置文件结构:
<?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>
<!-- 1. 属性配置 -->
<properties resource="database.properties">
<property name="username" value="dev_user"/>
<property name="password" value="dev_password"/>
</properties>
<!-- 2. 设置配置 -->
<settings>
<!-- 开启驼峰命名转换 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置执行器类型 -->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!-- 设置超时时间 -->
<setting name="defaultStatementTimeout" value="25"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 日志实现 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 3. 类型别名 -->
<typeAliases>
<!-- 单个别名 -->
<typeAlias alias="User" type="com.example.entity.User"/>
<!-- 包扫描 -->
<package name="com.example.entity"/>
</typeAliases>
<!-- 4. 类型处理器 -->
<typeHandlers>
<typeHandler handler="com.example.handler.MyTypeHandler"/>
<package name="com.example.handler"/>
</typeHandlers>
<!-- 5. 插件配置 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
<!-- 6. 环境配置 -->
<environments default="development">
<!-- 开发环境 -->
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<!-- 生产环境 -->
<environment id="production">
<transactionManager type="MANAGED"/>
<dataSource type="JNDI">
<property name="data_source" value="java:comp/env/jdbc/MyDataSource"/>
</dataSource>
</environment>
</environments>
<!-- 7. 数据库厂商标识 -->
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="PostgreSQL" value="postgresql"/>
</databaseIdProvider>
<!-- 8. 映射器配置 -->
<mappers>
<!-- 单个映射文件 -->
<mapper resource="com/example/mapper/UserMapper.xml"/>
<!-- 接口映射 -->
<mapper class="com.example.mapper.UserMapper"/>
<!-- 包扫描 -->
<package name="com.example.mapper"/>
</mappers>
</configuration>
重要设置详解:
<settings>
<!-- 自动映射策略 -->
<setting name="autoMappingBehavior" value="PARTIAL"/> <!-- NONE, PARTIAL, FULL -->
<!-- 自动映射未知列行为 -->
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <!-- NONE, WARNING, FAILING -->
<!-- 缓存配置 -->
<setting name="cacheEnabled" value="true"/> <!-- 二级缓存开关 -->
<setting name="localCacheScope" value="SESSION"/> <!-- SESSION, STATEMENT -->
<!-- 延迟加载配置 -->
<setting name="lazyLoadingEnabled" value="false"/> <!-- 延迟加载开关 -->
<setting name="aggressiveLazyLoading" value="false"/> <!-- 积极延迟加载 -->
<!-- 执行器配置 -->
<setting name="defaultExecutorType" value="SIMPLE"/> <!-- SIMPLE, REUSE, BATCH -->
<!-- 超时配置 -->
<setting name="defaultStatementTimeout" value="25"/> <!-- SQL执行超时时间 -->
<setting name="defaultFetchSize" value="100"/> <!-- 驱动结果集获取数量 -->
<!-- 返回值配置 -->
<setting name="returnInstanceForEmptyRow" value="false"/> <!-- 空行返回实例 -->
<!-- 日志配置 -->
<setting name="logImpl" value="SLF4J"/> <!-- SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING -->
<!-- 命名转换 -->
<setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 下划线转驼峰 -->
<!-- 多结果集处理 -->
<setting name="multipleResultSetsEnabled" value="true"/> <!-- 多结果集支持 -->
<!-- 空值处理 -->
<setting name="callSettersOnNulls" value="false"/> <!-- null值调用setter -->
<setting name="jdbcTypeForNull" value="OTHER"/> <!-- null值的JDBC类型 -->
</settings>
映射文件配置
简要描述:映射文件是MyBatis的核心,定义了SQL语句与Java方法的映射关系。每个映射文件对应一个Mapper接口。
核心概念:
- 命名空间:映射文件的唯一标识,通常对应Mapper接口的全限定名
- SQL映射:定义具体的SQL语句和参数映射
- 结果映射:定义查询结果与Java对象的映射关系
映射文件基本结构:
<?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">
<!-- 1. 缓存配置 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
<!-- 2. 缓存引用 -->
<cache-ref namespace="com.example.mapper.CommonMapper"/>
<!-- 3. 结果映射 -->
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<result property="age" column="user_age"/>
<result property="email" column="user_email"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
<!-- 4. SQL片段 -->
<sql id="userColumns">
user_id, user_name, user_age, user_email, create_time
</sql>
<!-- 5. 参数映射 -->
<parameterMap id="userParameterMap" type="User">
<parameter property="id" jdbcType="BIGINT"/>
<parameter property="name" jdbcType="VARCHAR"/>
<parameter property="age" jdbcType="INTEGER"/>
<parameter property="email" jdbcType="VARCHAR"/>
</parameterMap>
<!-- 6. 查询语句 -->
<select id="selectUser" parameterType="long" resultMap="userResultMap">
SELECT <include refid="userColumns"/>
FROM user
WHERE user_id = #{id}
</select>
<!-- 7. 插入语句 -->
<insert id="insertUser" parameterType="User"
useGeneratedKeys="true" keyProperty="id" keyColumn="user_id">
INSERT INTO user (user_name, user_age, user_email, create_time)
VALUES (#{name}, #{age}, #{email}, #{createTime})
</insert>
<!-- 8. 更新语句 -->
<update id="updateUser" parameterType="User">
UPDATE user
SET user_name = #{name},
user_age = #{age},
user_email = #{email}
WHERE user_id = #{id}
</update>
<!-- 9. 删除语句 -->
<delete id="deleteUser" parameterType="long">
DELETE FROM user WHERE user_id = #{id}
</delete>
</mapper>
数据源配置
简要描述:数据源配置是MyBatis连接数据库的核心配置,支持多种数据源类型和连接池配置。
核心概念:
- Environment环境:包含事务管理器和数据源的配置环境
- DataSource数据源:提供数据库连接的组件
- TransactionManager事务管理器:管理数据库事务的组件
- 连接池:管理数据库连接的池化技术
基本数据源配置
<!-- mybatis-config.xml中的数据源配置 -->
<environments default="development">
<!-- 开发环境 -->
<environment id="development">
<!-- 事务管理器:JDBC或MANAGED -->
<transactionManager type="JDBC"/>
<!-- 数据源类型:POOLED、UNPOOLED、JNDI -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
<!-- 连接池配置 -->
<property name="poolMaximumActiveConnections" value="20"/> <!-- 最大活跃连接数 -->
<property name="poolMaximumIdleConnections" value="5"/> <!-- 最大空闲连接数 -->
<property name="poolMaximumCheckoutTime" value="20000"/> <!-- 最大检出时间 -->
<property name="poolTimeToWait" value="20000"/> <!-- 等待时间 -->
</dataSource>
</environment>
<!-- 生产环境 -->
<environment id="production">
<transactionManager type="JDBC"/>
<dataSource type="JNDI">
<property name="data_source" value="java:comp/env/jdbc/mybatis"/>
</dataSource>
</environment>
</environments>
外部配置文件
<!-- 引用外部配置文件 -->
<properties resource="database.properties">
<!-- 可以在这里定义默认值 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
# database.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username=root
password=password
多数据源配置
<!-- 多数据源环境配置 -->
<environments default="master">
<!-- 主数据库 -->
<environment id="master">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://master:3306/mybatis_demo"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
<!-- 从数据库 -->
<environment id="slave">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://slave:3306/mybatis_demo"/>
<property name="username" value="readonly"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
类型处理器
简要描述:类型处理器(TypeHandler)负责Java类型与JDBC类型之间的转换,MyBatis提供了丰富的内置类型处理器,也支持自定义类型处理器。
核心概念:
- 类型转换:Java类型与数据库类型之间的双向转换
- 内置处理器:MyBatis提供的常用类型处理器
- 自定义处理器:针对特殊需求开发的类型处理器
- 泛型处理:支持泛型的类型处理器
内置类型处理器
// MyBatis内置的常用类型处理器
public class TypeHandlerRegistry {
// 基本类型处理器
register(Boolean.class, new BooleanTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(Long.class, new LongTypeHandler());
register(String.class, new StringTypeHandler());
// 日期时间处理器
register(Date.class, new DateTypeHandler());
register(Timestamp.class, new DateTypeHandler());
register(LocalDate.class, new LocalDateTypeHandler());
register(LocalDateTime.class, new LocalDateTimeTypeHandler());
// 大对象处理器
register(byte[].class, new ByteArrayTypeHandler());
register(Blob.class, new BlobTypeHandler());
register(Clob.class, new ClobTypeHandler());
}
自定义类型处理器
// 枚举类型处理器示例
public enum UserStatus {
ACTIVE(1, "激活"),
INACTIVE(0, "未激活"),
DELETED(-1, "已删除");
private final int code;
private final String description;
UserStatus(int code, String description) {
this.code = code;
this.description = description;
}
// getter方法
public int getCode() { return code; }
public String getDescription() { return description; }
// 根据code获取枚举
public static UserStatus fromCode(int code) {
for (UserStatus status : values()) {
if (status.code == code) {
return status;
}
}
throw new IllegalArgumentException("Unknown code: " + code);
}
}
// 自定义枚举类型处理器
@MappedTypes(UserStatus.class)
@MappedJdbcTypes(JdbcType.INTEGER)
public class UserStatusTypeHandler implements TypeHandler<UserStatus> {
@Override
public void setParameter(PreparedStatement ps, int i, UserStatus parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
ps.setNull(i, Types.INTEGER);
} else {
ps.setInt(i, parameter.getCode());
}
}
@Override
public UserStatus getResult(ResultSet rs, String columnName) throws SQLException {
int code = rs.getInt(columnName);
return rs.wasNull() ? null : UserStatus.fromCode(code);
}
@Override
public UserStatus getResult(ResultSet rs, int columnIndex) throws SQLException {
int code = rs.getInt(columnIndex);
return rs.wasNull() ? null : UserStatus.fromCode(code);
}
@Override
public UserStatus getResult(CallableStatement cs, int columnIndex) throws SQLException {
int code = cs.getInt(columnIndex);
return cs.wasNull() ? null : UserStatus.fromCode(code);
}
}
JSON类型处理器
// JSON类型处理器,用于处理JSON字段
@MappedTypes({Object.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class JsonTypeHandler<T> implements TypeHandler<T> {
private static final ObjectMapper objectMapper = new ObjectMapper();
private Class<T> type;
public JsonTypeHandler(Class<T> type) {
this.type = type;
}
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
ps.setNull(i, Types.VARCHAR);
} else {
try {
ps.setString(i, objectMapper.writeValueAsString(parameter));
} catch (JsonProcessingException e) {
throw new SQLException("Error converting object to JSON", e);
}
}
}
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
return parseJson(json);
}
@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
String json = rs.getString(columnIndex);
return parseJson(json);
}
@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
String json = cs.getString(columnIndex);
return parseJson(json);
}
private T parseJson(String json) throws SQLException {
if (json == null || json.trim().isEmpty()) {
return null;
}
try {
return objectMapper.readValue(json, type);
} catch (JsonProcessingException e) {
throw new SQLException("Error parsing JSON", e);
}
}
}
类型处理器注册
<!-- 在mybatis-config.xml中注册类型处理器 -->
<typeHandlers>
<!-- 单个注册 -->
<typeHandler handler="com.example.handler.UserStatusTypeHandler"/>
<!-- 包扫描注册 -->
<package name="com.example.handler"/>
<!-- 指定Java类型和JDBC类型 -->
<typeHandler javaType="com.example.entity.UserProfile"
jdbcType="VARCHAR"
handler="com.example.handler.JsonTypeHandler"/>
</typeHandlers>
插件配置
简要描述:MyBatis插件机制基于拦截器模式,允许在SQL执行的关键点进行拦截和自定义处理,常用于分页、性能监控、数据权限等场景。
核心概念:
- 拦截器:实现Interceptor接口的插件组件
- 拦截点:可以被拦截的四个核心对象
- 方法签名:指定要拦截的具体方法
- 插件链:多个插件按顺序执行的链式结构
可拦截的四大对象
// 1. Executor - 执行器,负责SQL的执行
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
// 2. StatementHandler - 语句处理器,负责SQL语句的预处理
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
@Signature(type = StatementHandler.class, method = "parameterize", args = {Statement.class})
})
// 3. ParameterHandler - 参数处理器,负责参数的设置
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})
// 4. ResultSetHandler - 结果集处理器,负责结果集的处理
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
SQL性能监控插件
// SQL执行时间监控插件
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
public class SqlPerformanceInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(SqlPerformanceInterceptor.class);
private long slowSqlThreshold = 1000; // 慢SQL阈值,单位毫秒
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
// 执行原方法
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
// 获取SQL语句
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
// 记录SQL执行信息
if (executionTime > slowSqlThreshold) {
logger.warn("Slow SQL detected: {} ms, SQL: {}", executionTime, sql);
} else {
logger.info("SQL executed: {} ms, SQL: {}", executionTime, sql);
}
return result;
} catch (Exception e) {
logger.error("SQL execution failed", e);
throw e;
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
String threshold = properties.getProperty("slowSqlThreshold");
if (threshold != null) {
this.slowSqlThreshold = Long.parseLong(threshold);
}
}
}
分页插件
// 简单的分页插件实现
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class PageInterceptor implements Interceptor {
private static final ThreadLocal<Page> PAGE_THREAD_LOCAL = new ThreadLocal<>();
public static void startPage(int pageNum, int pageSize) {
PAGE_THREAD_LOCAL.set(new Page(pageNum, pageSize));
}
public static void clearPage() {
PAGE_THREAD_LOCAL.remove();
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
Page page = PAGE_THREAD_LOCAL.get();
if (page == null) {
return invocation.proceed();
}
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String originalSql = boundSql.getSql();
// 构建分页SQL
String pageSql = buildPageSql(originalSql, page);
// 通过反射修改SQL
Field sqlField = BoundSql.class.getDeclaredField("sql");
sqlField.setAccessible(true);
sqlField.set(boundSql, pageSql);
return invocation.proceed();
}
private String buildPageSql(String originalSql, Page page) {
int offset = (page.getPageNum() - 1) * page.getPageSize();
return originalSql + " LIMIT " + offset + ", " + page.getPageSize();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 可以从配置中读取参数
}
// 分页参数类
public static class Page {
private int pageNum;
private int pageSize;
public Page(int pageNum, int pageSize) {
this.pageNum = pageNum;
this.pageSize = pageSize;
}
// getter方法
public int getPageNum() { return pageNum; }
public int getPageSize() { return pageSize; }
}
}
插件注册配置
<!-- 在mybatis-config.xml中注册插件 -->
<plugins>
<!-- SQL性能监控插件 -->
<plugin interceptor="com.example.plugin.SqlPerformanceInterceptor">
<property name="slowSqlThreshold" value="2000"/>
</plugin>
<!-- 分页插件 -->
<plugin interceptor="com.example.plugin.PageInterceptor"/>
<!-- 数据权限插件 -->
<plugin interceptor="com.example.plugin.DataPermissionInterceptor">
<property name="enable" value="true"/>
</plugin>
</plugins>
映射器与SQL映射
简要描述:映射器与SQL映射是MyBatis的核心功能,通过Mapper接口和映射文件将Java方法与SQL语句进行绑定,支持XML配置和注解两种方式。
核心概念:
- Mapper接口:定义数据访问方法的Java接口
- 映射文件:包含SQL语句和映射规则的XML文件
- 注解映射:通过注解直接在接口方法上定义SQL
- 参数映射:Java参数与SQL参数之间的映射关系
Mapper接口开发
简要描述:Mapper接口是MyBatis中定义数据访问方法的核心组件,通过接口方法与SQL语句进行映射,支持各种参数类型和返回类型。
核心概念:
- 接口代理:MyBatis通过动态代理生成接口实现
- 方法映射:接口方法与SQL语句的一对一映射
- 参数绑定:方法参数与SQL参数的绑定规则
- 返回类型:支持多种返回类型的自动映射
基本Mapper接口
// 用户数据访问接口
public interface UserMapper {
// 根据ID查询用户
User selectById(Long id);
// 查询所有用户
List<User> selectAll();
// 根据条件查询用户
List<User> selectByCondition(UserQuery query);
// 插入用户
int insert(User user);
// 更新用户
int update(User user);
// 删除用户
int deleteById(Long id);
// 批量插入
int batchInsert(List<User> users);
// 统计用户数量
int count();
// 检查用户是否存在
boolean exists(Long id);
}
参数处理方式
public interface UserMapper {
// 1. 单个参数(基本类型)
User selectById(Long id);
User selectByName(String name);
// 2. 多个参数(使用@Param注解)
List<User> selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
// 3. 对象参数
List<User> selectByCondition(UserQuery query);
// 4. Map参数
List<User> selectByMap(Map<String, Object> params);
// 5. 集合参数
List<User> selectByIds(@Param("ids") List<Long> ids);
// 6. 混合参数
List<User> selectByConditionAndIds(@Param("query") UserQuery query, @Param("ids") List<Long> ids);
}
返回类型处理
public interface UserMapper {
// 1. 单个对象
User selectById(Long id);
// 2. 集合
List<User> selectAll();
// 3. 基本类型
int count();
long countByStatus(Integer status);
String getNameById(Long id);
// 4. Map类型
Map<String, Object> selectAsMap(Long id);
// 5. Map集合(以某个字段为key)
@MapKey("id")
Map<Long, User> selectAllAsMap();
// 6. Optional类型(MyBatis 3.5+)
Optional<User> selectOptionalById(Long id);
// 7. 游标(大数据量处理)
Cursor<User> selectCursor();
// 8. 自定义结果处理器
void selectWithHandler(ResultHandler<User> handler);
}
Mapper接口注册
<!-- 在mybatis-config.xml中注册Mapper -->
<mappers>
<!-- 1. 单个注册 -->
<mapper resource="com/example/mapper/UserMapper.xml"/>
<mapper class="com.example.mapper.UserMapper"/>
<mapper url="file:///var/mappers/UserMapper.xml"/>
<!-- 2. 包扫描注册 -->
<package name="com.example.mapper"/>
</mappers>
// 在Java配置中注册
Configuration configuration = new Configuration();
configuration.addMapper(UserMapper.class);
XML映射文件
简要描述:XML映射文件是MyBatis中定义SQL语句和映射规则的核心配置文件,提供了丰富的标签和功能来处理复杂的SQL映射需求。
核心概念:
- 命名空间:映射文件的唯一标识,通常对应Mapper接口
- SQL语句标签:select、insert、update、delete等
- 参数映射:parameterType和参数占位符
- 结果映射:resultType和resultMap
基本映射文件结构
<?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">
<!-- namespace必须与Mapper接口全限定名一致 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 结果映射定义 -->
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="user_name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<!-- SQL片段定义 -->
<sql id="userColumns">
id, user_name, age, email, create_time, update_time
</sql>
<!-- 查询语句 -->
<select id="selectById" parameterType="long" resultMap="userResultMap">
SELECT <include refid="userColumns"/>
FROM user
WHERE id = #{id}
</select>
<!-- 插入语句 -->
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (user_name, age, email, create_time)
VALUES (#{name}, #{age}, #{email}, #{createTime})
</insert>
<!-- 更新语句 -->
<update id="update" parameterType="User">
UPDATE user
SET user_name = #{name},
age = #{age},
email = #{email},
update_time = #{updateTime}
WHERE id = #{id}
</update>
<!-- 删除语句 -->
<delete id="deleteById" parameterType="long">
DELETE FROM user WHERE id = #{id}
</delete>
</mapper>
复杂查询映射
<!-- 条件查询 -->
<select id="selectByCondition" parameterType="UserQuery" resultMap="userResultMap">
SELECT <include refid="userColumns"/>
FROM user
<where>
<if test="name != null and name != ''">
AND user_name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 批量查询 -->
<select id="selectByIds" resultMap="userResultMap">
SELECT <include refid="userColumns"/>
FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<!-- 分页查询 -->
<select id="selectPage" parameterType="map" resultMap="userResultMap">
SELECT <include refid="userColumns"/>
FROM user
<where>
<if test="query.name != null and query.name != ''">
AND user_name LIKE CONCAT('%', #{query.name}, '%')
</if>
</where>
ORDER BY create_time DESC
LIMIT #{offset}, #{limit}
</select>
关联查询映射
<!-- 一对一关联查询 -->
<resultMap id="userWithProfileResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="user_name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
<!-- 一对一关联 -->
<association property="profile" javaType="UserProfile">
<id property="id" column="profile_id"/>
<result property="avatar" column="avatar"/>
<result property="bio" column="bio"/>
<result property="website" column="website"/>
</association>
</resultMap>
<select id="selectWithProfile" parameterType="long" resultMap="userWithProfileResultMap">
SELECT u.id, u.user_name, u.age, u.email,
p.id as profile_id, p.avatar, p.bio, p.website
FROM user u
LEFT JOIN user_profile p ON u.id = p.user_id
WHERE u.id = #{id}
</select>
<!-- 一对多关联查询 -->
<resultMap id="userWithOrdersResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="user_name"/>
<result property="age" column="age"/>
<result property="email" column="email"/>
<!-- 一对多关联 -->
<collection property="orders" ofType="Order">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<result property="amount" column="amount"/>
<result property="status" column="order_status"/>
<result property="createTime" column="order_create_time"/>
</collection>
</resultMap>
<select id="selectWithOrders" parameterType="long" resultMap="userWithOrdersResultMap">
SELECT u.id, u.user_name, u.age, u.email,
o.id as order_id, o.order_no, o.amount, o.status as order_status, o.create_time as order_create_time
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
注解映射
简要描述:注解映射是MyBatis提供的另一种SQL映射方式,通过在Mapper接口方法上使用注解直接定义SQL语句,适用于简单的SQL操作。
核心概念:
- SQL注解:@Select、@Insert、@Update、@Delete
- 参数注解:@Param、@Options等
- 结果注解:@Results、@Result等
- 动态SQL:@SelectProvider、@InsertProvider等
基本注解使用
public interface UserMapper {
// 查询注解
@Select("SELECT id, user_name as name, age, email, create_time FROM user WHERE id = #{id}")
User selectById(Long id);
// 插入注解
@Insert("INSERT INTO user(user_name, age, email, create_time) VALUES(#{name}, #{age}, #{email}, #{createTime})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
// 更新注解
@Update("UPDATE user SET user_name = #{name}, age = #{age}, email = #{email} WHERE id = #{id}")
int update(User user);
// 删除注解
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteById(Long id);
// 多参数查询
@Select("SELECT id, user_name as name, age, email FROM user WHERE user_name = #{name} AND age = #{age}")
List<User> selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
// 统计查询
@Select("SELECT COUNT(*) FROM user WHERE status = #{status}")
int countByStatus(@Param("status") Integer status);
}
复杂结果映射注解
public interface UserMapper {
// 使用@Results注解定义结果映射
@Select("SELECT id, user_name, age, email, create_time, update_time FROM user WHERE id = #{id}")
@Results(id = "userResultMap", value = {
@Result(property = "id", column = "id", id = true),
@Result(property = "name", column = "user_name"),
@Result(property = "age", column = "age"),
@Result(property = "email", column = "email"),
@Result(property = "createTime", column = "create_time"),
@Result(property = "updateTime", column = "update_time")
})
User selectByIdWithMapping(Long id);
// 复用结果映射
@Select("SELECT id, user_name, age, email, create_time, update_time FROM user")
@ResultMap("userResultMap")
List<User> selectAll();
// 一对一关联映射
@Select("SELECT u.id, u.user_name, u.age, u.email, p.avatar, p.bio " +
"FROM user u LEFT JOIN user_profile p ON u.id = p.user_id WHERE u.id = #{id}")
@Results({
@Result(property = "id", column = "id", id = true),
@Result(property = "name", column = "user_name"),
@Result(property = "age", column = "age"),
@Result(property = "email", column = "email"),
@Result(property = "profile", column = "id",
one = @One(select = "selectProfileByUserId"))
})
User selectWithProfile(Long id);
@Select("SELECT id, avatar, bio, website FROM user_profile WHERE user_id = #{userId}")
UserProfile selectProfileByUserId(Long userId);
// 一对多关联映射
@Select("SELECT id, user_name, age, email FROM user WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id", id = true),
@Result(property = "name", column = "user_name"),
@Result(property = "age", column = "age"),
@Result(property = "email", column = "email"),
@Result(property = "orders", column = "id",
many = @Many(select = "selectOrdersByUserId"))
})
User selectWithOrders(Long id);
@Select("SELECT id, order_no, amount, status, create_time FROM orders WHERE user_id = #{userId}")
List<Order> selectOrdersByUserId(Long userId);
}
动态SQL注解
public interface UserMapper {
// 使用Provider类提供动态SQL
@SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
List<User> selectByCondition(UserQuery query);
@InsertProvider(type = UserSqlProvider.class, method = "batchInsert")
int batchInsert(@Param("users") List<User> users);
@UpdateProvider(type = UserSqlProvider.class, method = "updateSelective")
int updateSelective(User user);
}
// SQL提供者类
public class UserSqlProvider {
public String selectByCondition(UserQuery query) {
SQL sql = new SQL();
sql.SELECT("id, user_name as name, age, email, create_time");
sql.FROM("user");
if (query.getName() != null && !query.getName().isEmpty()) {
sql.WHERE("user_name LIKE CONCAT('%', #{name}, '%')");
}
if (query.getMinAge() != null) {
sql.WHERE("age >= #{minAge}");
}
if (query.getMaxAge() != null) {
sql.WHERE("age <= #{maxAge}");
}
if (query.getEmail() != null && !query.getEmail().isEmpty()) {
sql.WHERE("email = #{email}");
}
sql.ORDER_BY("create_time DESC");
return sql.toString();
}
public String batchInsert(@Param("users") List<User> users) {
StringBuilder sql = new StringBuilder();
sql.append("INSERT INTO user (user_name, age, email, create_time) VALUES ");
for (int i = 0; i < users.size(); i++) {
if (i > 0) {
sql.append(", ");
}
sql.append("(#{users[").append(i).append("].name}, ")
.append("#{users[").append(i).append("].age}, ")
.append("#{users[").append(i).append("].email}, ")
.append("#{users[").append(i).append("].createTime})");
}
return sql.toString();
}
public String updateSelective(User user) {
SQL sql = new SQL();
sql.UPDATE("user");
if (user.getName() != null) {
sql.SET("user_name = #{name}");
}
if (user.getAge() != null) {
sql.SET("age = #{age}");
}
if (user.getEmail() != null) {
sql.SET("email = #{email}");
}
sql.SET("update_time = NOW()");
sql.WHERE("id = #{id}");
return sql.toString();
}
}
参数映射详解
简要描述:参数映射是MyBatis中处理Java方法参数与SQL参数绑定的核心机制,支持多种参数类型和绑定方式。
核心概念:
- 参数绑定:Java参数与SQL占位符的绑定关系
- 参数类型:基本类型、对象类型、集合类型等
- 参数注解:@Param注解指定参数名称
- 参数处理:MyBatis内部的参数处理机制
单个参数处理
public interface UserMapper {
// 基本类型参数
User selectById(Long id); // 参数名可以是任意值
User selectByName(String name); // 在SQL中使用#{任意名称}
List<User> selectByAge(Integer age);
// 包装类型参数
List<User> selectByStatus(Integer status); // 支持null值检查
User selectByEmail(String email);
}
<!-- 单个参数的XML映射 -->
<select id="selectById" parameterType="long" resultType="User">
SELECT * FROM user WHERE id = #{id} <!-- 参数名可以任意 -->
</select>
<select id="selectByName" parameterType="string" resultType="User">
SELECT * FROM user WHERE user_name = #{name} <!-- 或者#{value}、#{_parameter} -->
</select>
<!-- 参数为null的处理 -->
<select id="selectByStatus" parameterType="integer" resultType="User">
SELECT * FROM user
<where>
<if test="_parameter != null">
status = #{status}
</if>
</where>
</select>
多个参数处理
public interface UserMapper {
// 使用@Param注解(推荐方式)
List<User> selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
// 不使用@Param注解(不推荐)
List<User> selectByNameAndAgeOld(String name, Integer age);
// 混合参数类型
List<User> selectByCondition(@Param("name") String name,
@Param("minAge") Integer minAge,
@Param("maxAge") Integer maxAge,
@Param("status") Integer status);
}
<!-- 使用@Param注解的映射 -->
<select id="selectByNameAndAge" resultType="User">
SELECT * FROM user
WHERE user_name = #{name} AND age = #{age}
</select>
<!-- 不使用@Param注解的映射(使用arg0, arg1或param1, param2) -->
<select id="selectByNameAndAgeOld" resultType="User">
SELECT * FROM user
WHERE user_name = #{arg0} AND age = #{arg1}
<!-- 或者使用 #{param1} 和 #{param2} -->
</select>
<!-- 复杂条件查询 -->
<select id="selectByCondition" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND user_name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
对象参数处理
// 查询条件对象
public class UserQuery {
private String name;
private Integer minAge;
private Integer maxAge;
private String email;
private Integer status;
private Date startDate;
private Date endDate;
// getter和setter方法
}
public interface UserMapper {
// 对象参数
List<User> selectByQuery(UserQuery query);
// 对象参数插入
int insert(User user);
// 对象参数更新
int update(User user);
// 嵌套对象参数
List<User> selectByUserAndProfile(@Param("user") User user, @Param("profile") UserProfile profile);
}
<!-- 对象参数映射 -->
<select id="selectByQuery" parameterType="UserQuery" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND user_name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
<if test="startDate != null">
AND create_time >= #{startDate}
</if>
<if test="endDate != null">
AND create_time <= #{endDate}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 对象参数插入 -->
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (user_name, age, email, status, create_time)
VALUES (#{name}, #{age}, #{email}, #{status}, #{createTime})
</insert>
<!-- 嵌套对象参数 -->
<select id="selectByUserAndProfile" resultType="User">
SELECT * FROM user u
LEFT JOIN user_profile p ON u.id = p.user_id
<where>
<if test="user.name != null and user.name != ''">
AND u.user_name = #{user.name}
</if>
<if test="user.age != null">
AND u.age = #{user.age}
</if>
<if test="profile.avatar != null and profile.avatar != ''">
AND p.avatar = #{profile.avatar}
</if>
</where>
</select>
Map参数处理
public interface UserMapper {
// Map参数
List<User> selectByMap(Map<String, Object> params);
// 分页查询Map参数
List<User> selectPage(Map<String, Object> params);
// 复杂查询Map参数
List<User> selectByComplexMap(Map<String, Object> conditions);
}
<!-- Map参数映射 -->
<select id="selectByMap" parameterType="map" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND user_name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
<!-- 分页查询 -->
<select id="selectPage" parameterType="map" resultType="User">
SELECT * FROM user
<where>
<if test="query != null">
<if test="query.name != null and query.name != ''">
AND user_name LIKE CONCAT('%', #{query.name}, '%')
</if>
<if test="query.status != null">
AND status = #{query.status}
</if>
</if>
</where>
ORDER BY create_time DESC
LIMIT #{offset}, #{limit}
</select>
集合参数处理
public interface UserMapper {
// List参数
List<User> selectByIds(@Param("ids") List<Long> ids);
// Array参数
List<User> selectByIdArray(@Param("ids") Long[] ids);
// 批量插入
int batchInsert(@Param("users") List<User> users);
// 批量更新
int batchUpdate(@Param("users") List<User> users);
// Set参数
List<User> selectByStatusSet(@Param("statusSet") Set<Integer> statusSet);
}
<!-- List参数映射 -->
<select id="selectByIds" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<!-- Array参数映射 -->
<select id="selectByIdArray" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<!-- 批量插入 -->
<insert id="batchInsert">
INSERT INTO user (user_name, age, email, create_time)
VALUES
<foreach collection="users" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.email}, #{user.createTime})
</foreach>
</insert>
<!-- 批量更新(MySQL特有语法) -->
<update id="batchUpdate">
<foreach collection="users" item="user" separator=";">
UPDATE user
SET user_name = #{user.name}, age = #{user.age}, email = #{user.email}
WHERE id = #{user.id}
</foreach>
</update>
<!-- Set参数映射 -->
<select id="selectByStatusSet" resultType="User">
SELECT * FROM user
<where>
<if test="statusSet != null and statusSet.size() > 0">
status IN
<foreach collection="statusSet" item="status" open="(" close=")" separator=",">
#{status}
</foreach>
</if>
</where>
</select>
参数类型转换
public interface UserMapper {
// 日期参数处理
List<User> selectByDateRange(@Param("startDate") Date startDate,
@Param("endDate") Date endDate);
// 枚举参数处理
List<User> selectByUserType(@Param("userType") UserType userType);
// JSON参数处理
List<User> selectByJsonCondition(@Param("jsonData") String jsonData);
// 自定义类型参数
List<User> selectByCustomType(@Param("customParam") CustomType customParam);
}
<!-- 日期参数映射 -->
<select id="selectByDateRange" resultType="User">
SELECT * FROM user
<where>
<if test="startDate != null">
AND create_time >= #{startDate, jdbcType=TIMESTAMP}
</if>
<if test="endDate != null">
AND create_time <= #{endDate, jdbcType=TIMESTAMP}
</if>
</where>
</select>
<!-- 枚举参数映射 -->
<select id="selectByUserType" resultType="User">
SELECT * FROM user
WHERE user_type = #{userType, jdbcType=VARCHAR, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
</select>
<!-- JSON参数映射 -->
<select id="selectByJsonCondition" resultType="User">
SELECT * FROM user
WHERE JSON_EXTRACT(extra_data, '$.condition') = #{jsonData, jdbcType=VARCHAR}
</select>
参数验证和处理
public interface UserMapper {
// 参数验证示例
List<User> selectWithValidation(@Param("name") String name,
@Param("age") Integer age);
}
<!-- 参数验证和处理 -->
<select id="selectWithValidation" resultType="User">
SELECT * FROM user
<where>
<!-- 字符串参数验证 -->
<if test="name != null and name != '' and name.length() > 0">
AND user_name = #{name}
</if>
<!-- 数值参数验证 -->
<if test="age != null and age > 0 and age < 150">
AND age = #{age}
</if>
<!-- 防止SQL注入的参数处理 -->
<if test="name != null">
AND user_name = #{name, jdbcType=VARCHAR}
</if>
</where>
</select>
特殊参数处理
public interface UserMapper {
// RowBounds分页参数(MyBatis内置)
List<User> selectWithRowBounds(RowBounds rowBounds);
// ResultHandler结果处理器
void selectWithHandler(ResultHandler<User> handler);
// 混合特殊参数
void selectWithMixedParams(@Param("name") String name,
RowBounds rowBounds,
ResultHandler<User> handler);
}
<!-- RowBounds不需要在XML中声明,MyBatis自动处理 -->
<select id="selectWithRowBounds" resultType="User">
SELECT * FROM user ORDER BY create_time DESC
</select>
<!-- ResultHandler也不需要在XML中声明 -->
<select id="selectWithHandler" resultType="User">
SELECT * FROM user ORDER BY create_time DESC
</select>
<!-- 混合参数处理 -->
<select id="selectWithMixedParams" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
user_name LIKE CONCAT('%', #{name}, '%')
</if>
</where>
ORDER BY create_time DESC
</select>
结果映射详解
简要描述:结果映射是MyBatis将查询结果转换为Java对象的核心机制,支持简单映射和复杂的关联映射。
核心概念:
- 自动映射:根据列名和属性名自动映射
- 手动映射:通过resultMap精确控制映射关系
- 关联映射:处理对象间的关联关系
- 延迟加载:按需加载关联对象
resultType
简要描述:resultType是最简单的结果映射方式,适用于简单的数据类型和JavaBean,MyBatis会自动进行映射。
核心概念:
- 自动映射:根据列名和属性名进行自动映射
- 类型转换:自动进行基本的类型转换
- 命名规则:支持下划线转驼峰命名
基本使用:
<!-- 基本数据类型 -->
<select id="countUsers" resultType="int">
SELECT COUNT(*) FROM user
</select>
<select id="getUserNames" resultType="string">
SELECT name FROM user WHERE status = 1
</select>
<!-- JavaBean映射 -->
<select id="selectUser" parameterType="long" resultType="User">
SELECT id, name, age, email, create_time
FROM user
WHERE id = #{id}
</select>
<!-- Map映射 -->
<select id="selectUserAsMap" parameterType="long" resultType="map">
SELECT id, name, age, email
FROM user
WHERE id = #{id}
</select>
<!-- 集合映射 -->
<select id="selectAllUsers" resultType="User">
SELECT id, name, age, email, create_time
FROM user
WHERE status = 1
ORDER BY create_time DESC
</select>
类型别名配置:
<!-- 在mybatis-config.xml中配置别名 -->
<typeAliases>
<typeAlias alias="User" type="com.example.entity.User"/>
<typeAlias alias="UserVO" type="com.example.vo.UserVO"/>
<!-- 包扫描方式 -->
<package name="com.example.entity"/>
</typeAliases>
自动映射配置:
<settings>
<!-- 开启驼峰命名转换 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 自动映射策略:NONE, PARTIAL, FULL -->
<setting name="autoMappingBehavior" value="PARTIAL"/>
</settings>
resultMap
简要描述:resultMap提供了更精确的结果映射控制,可以处理复杂的映射关系、类型转换和关联映射。
核心概念:
- 精确映射:明确指定每个字段的映射关系
- 类型处理:支持自定义类型处理器
- 继承机制:支持resultMap继承
- 关联映射:支持一对一、一对多关联
基本结构:
<resultMap id="userResultMap" type="User">
<!-- 主键映射 -->
<id property="id" column="user_id" jdbcType="BIGINT"/>
<!-- 普通字段映射 -->
<result property="name" column="user_name" jdbcType="VARCHAR"/>
<result property="age" column="user_age" jdbcType="INTEGER"/>
<result property="email" column="user_email" jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="status" column="status" jdbcType="INTEGER" typeHandler="com.example.handler.StatusTypeHandler"/>
</resultMap>
<!-- 使用resultMap -->
<select id="selectUser" parameterType="long" resultMap="userResultMap">
SELECT user_id, user_name, user_age, user_email, create_time, status
FROM user
WHERE user_id = #{id}
</select>
复杂映射示例:
<!-- 用户详细信息映射 -->
<resultMap id="userDetailResultMap" type="UserDetail">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<result property="age" column="user_age"/>
<result property="email" column="user_email"/>
<!-- 枚举类型映射 -->
<result property="gender" column="gender"
typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
<!-- 日期类型映射 -->
<result property="birthday" column="birthday"
jdbcType="DATE" javaType="java.time.LocalDate"/>
<!-- JSON字段映射 -->
<result property="preferences" column="preferences"
typeHandler="com.example.handler.JsonTypeHandler"/>
<!-- 计算字段 -->
<result property="fullName" column="full_name"/>
</resultMap>
<select id="selectUserDetail" parameterType="long" resultMap="userDetailResultMap">
SELECT
user_id,
user_name,
user_age,
user_email,
gender,
birthday,
preferences,
CONCAT(first_name, ' ', last_name) AS full_name
FROM user
WHERE user_id = #{id}
</select>
resultMap继承:
<!-- 基础映射 -->
<resultMap id="baseUserMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<result property="email" column="user_email"/>
</resultMap>
<!-- 继承基础映射 -->
<resultMap id="extendedUserMap" type="User" extends="baseUserMap">
<result property="age" column="user_age"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
关联映射
简要描述:关联映射用于处理对象间的关联关系,包括一对一(association)和一对多(collection)关联。
一对一关联(association)
核心概念:
- 嵌套查询:通过额外的查询获取关联对象
- 嵌套结果:在一个查询中获取所有数据
- 延迟加载:按需加载关联对象
嵌套查询方式:
<!-- 用户和用户详情的一对一关联 -->
<resultMap id="userWithProfileMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<result property="email" column="user_email"/>
<!-- 一对一关联:嵌套查询 -->
<association property="profile" column="user_id"
javaType="UserProfile"
select="selectUserProfile"/>
</resultMap>
<!-- 主查询 -->
<select id="selectUserWithProfile" parameterType="long" resultMap="userWithProfileMap">
SELECT user_id, user_name, user_email
FROM user
WHERE user_id = #{id}
</select>
<!-- 关联查询 -->
<select id="selectUserProfile" parameterType="long" resultType="UserProfile">
SELECT profile_id, user_id, avatar, bio, phone
FROM user_profile
WHERE user_id = #{userId}
</select>
嵌套结果方式:
<!-- 嵌套结果映射 -->
<resultMap id="userWithProfileNestedMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<result property="email" column="user_email"/>
<!-- 一对一关联:嵌套结果 -->
<association property="profile" javaType="UserProfile">
<id property="profileId" column="profile_id"/>
<result property="avatar" column="avatar"/>
<result property="bio" column="bio"/>
<result property="phone" column="phone"/>
</association>
</resultMap>
<!-- 联表查询 -->
<select id="selectUserWithProfileNested" parameterType="long" resultMap="userWithProfileNestedMap">
SELECT
u.user_id,
u.user_name,
u.user_email,
p.profile_id,
p.avatar,
p.bio,
p.phone
FROM user u
LEFT JOIN user_profile p ON u.user_id = p.user_id
WHERE u.user_id = #{id}
</select>
一对多关联(collection)
核心概念:
- 集合映射:将多个记录映射为集合
- ofType属性:指定集合元素的类型
- 分组处理:处理重复的主记录
嵌套查询方式:
<!-- 用户和订单的一对多关联 -->
<resultMap id="userWithOrdersMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<result property="email" column="user_email"/>
<!-- 一对多关联:嵌套查询 -->
<collection property="orders" column="user_id"
ofType="Order"
select="selectOrdersByUserId"/>
</resultMap>
<!-- 主查询 -->
<select id="selectUserWithOrders" parameterType="long" resultMap="userWithOrdersMap">
SELECT user_id, user_name, user_email
FROM user
WHERE user_id = #{id}
</select>
<!-- 关联查询 -->
<select id="selectOrdersByUserId" parameterType="long" resultType="Order">
SELECT order_id, user_id, order_no, total_amount, create_time
FROM orders
WHERE user_id = #{userId}
ORDER BY create_time DESC
</select>
嵌套结果方式:
<!-- 嵌套结果映射 -->
<resultMap id="userWithOrdersNestedMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<result property="email" column="user_email"/>
<!-- 一对多关联:嵌套结果 -->
<collection property="orders" ofType="Order">
<id property="orderId" column="order_id"/>
<result property="orderNo" column="order_no"/>
<result property="totalAmount" column="total_amount"/>
<result property="createTime" column="order_create_time"/>
</collection>
</resultMap>
<!-- 联表查询 -->
<select id="selectUserWithOrdersNested" parameterType="long" resultMap="userWithOrdersNestedMap">
SELECT
u.user_id,
u.user_name,
u.user_email,
o.order_id,
o.order_no,
o.total_amount,
o.create_time AS order_create_time
FROM user u
LEFT JOIN orders o ON u.user_id = o.user_id
WHERE u.user_id = #{id}
ORDER BY o.create_time DESC
</select>
集合映射
简要描述:集合映射专门处理返回集合类型的查询结果,包括List、Set、Map等集合类型。
List集合映射:
<!-- 返回用户列表 -->
<select id="selectUserList" resultType="User">
SELECT user_id AS id, user_name AS name, user_email AS email
FROM user
WHERE status = 1
ORDER BY create_time DESC
</select>
<!-- 返回Map列表 -->
<select id="selectUserMapList" resultType="map">
SELECT user_id, user_name, user_email, create_time
FROM user
WHERE status = 1
</select>
Map集合映射:
<!-- 返回以ID为key的Map -->
<select id="selectUserMap" resultType="User">
SELECT user_id AS id, user_name AS name, user_email AS email
FROM user
WHERE status = 1
</select>
<!-- 在Mapper接口中使用@MapKey注解 -->
@MapKey("id")
Map<Long, User> selectUserMap();
复杂集合映射:
<!-- 分组统计映射 -->
<resultMap id="userStatMap" type="map">
<result property="department" column="department"/>
<result property="count" column="user_count"/>
<result property="avgAge" column="avg_age"/>
</resultMap>
<select id="selectUserStatByDepartment" resultMap="userStatMap">
SELECT
department,
COUNT(*) AS user_count,
AVG(age) AS avg_age
FROM user
WHERE status = 1
GROUP BY department
ORDER BY user_count DESC
</select>
延迟加载配置:
<!-- 全局配置 -->
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭积极延迟加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 延迟加载触发方法 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
<!-- 单独配置延迟加载,局部的懒加载策略优先级要高于全局的懒加载策略 -->
<association property="profile" column="user_id"
javaType="UserProfile"
select="selectUserProfile"
fetchType="lazy"/> <!-- lazy或eager -->
动态SQL深入解析
简要描述:动态SQL是MyBatis的强大特性之一,它允许根据不同的条件动态构建SQL语句,避免了大量的字符串拼接工作。
核心概念:
- 条件判断:根据参数值决定SQL片段是否包含
- 循环处理:处理集合类型的参数
- SQL片段复用:提高代码复用性
- OGNL表达式:使用OGNL表达式进行条件判断
if条件判断
简要描述:if标签是最常用的动态SQL标签,用于条件判断,只有当test条件为true时,才会包含标签内的SQL片段。
基本语法:
<if test="条件表达式">
SQL片段
</if>
实际应用案例:
<!-- 动态查询用户 -->
<select id="selectUsersByCondition" parameterType="User" resultType="User">
SELECT id, name, age, email, status
FROM user
WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
</select>
<!-- 动态更新用户信息 -->
<update id="updateUserSelective" parameterType="User">
UPDATE user
SET update_time = NOW()
<if test="name != null and name != ''">
, name = #{name}
</if>
<if test="age != null">
, age = #{age}
</if>
<if test="email != null and email != ''">
, email = #{email}
</if>
<if test="status != null">
, status = #{status}
</if>
WHERE id = #{id}
</update>
常用条件表达式:
<!-- 字符串判断 -->
<if test="name != null and name != ''"> <!-- 非空字符串 -->
<if test="name != null and name.length() > 0"> <!-- 字符串长度大于0 -->
<if test="name != null and !name.equals('')"> <!-- 不等于空字符串 -->
<!-- 数值判断 -->
<if test="age != null"> <!-- 非空数值 -->
<if test="age != null and age > 0"> <!-- 大于0的数值 -->
<if test="age != null and age >= 18 and age <= 65"> <!-- 范围判断 -->
<!-- 集合判断 -->
<if test="list != null and list.size() > 0"> <!-- 非空集合 -->
<if test="array != null and array.length > 0"> <!-- 非空数组 -->
<!-- 布尔判断 -->
<if test="isActive != null and isActive"> <!-- 布尔值为true -->
<if test="isDeleted == null or !isDeleted"> <!-- 未删除 -->
choose/when/otherwise多条件选择
简要描述:choose标签类似于Java中的switch语句,用于多条件分支判断。when相当于case,otherwise相当于default。
基本语法:
<choose>
<when test="条件1">
SQL片段1
</when>
<when test="条件2">
SQL片段2
</when>
<otherwise>
默认SQL片段
</otherwise>
</choose>
实际应用案例:
<!-- 根据不同条件排序 -->
<select id="selectUsersWithDynamicOrder" parameterType="map" resultType="User">
SELECT id, name, age, email, create_time
FROM user
WHERE status = 1
ORDER BY
<choose>
<when test="orderBy == 'name'">
name ASC
</when>
<when test="orderBy == 'age'">
age DESC
</when>
<when test="orderBy == 'createTime'">
create_time DESC
</when>
<otherwise>
id ASC
</otherwise>
</choose>
</select>
<!-- 根据用户类型查询不同字段 -->
<select id="selectUserByType" parameterType="map" resultType="map">
SELECT
<choose>
<when test="userType == 'admin'">
id, name, email, role, permissions, last_login_time
</when>
<when test="userType == 'vip'">
id, name, email, vip_level, vip_expire_time
</when>
<otherwise>
id, name, email
</otherwise>
</choose>
FROM user
WHERE id = #{userId}
</select>
where条件处理
简要描述:where标签用于智能处理SQL语句中的WHERE子句,自动去除多余的AND、OR等。
核心功能:
- 自动添加WHERE关键字
- 去除第一个AND或OR
- 如果没有条件,不添加WHERE
<!-- 使用where标签优化条件查询 -->
<select id="selectUsersByCondition" parameterType="User" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY create_time DESC
</select>
set更新处理
简要描述:set标签用于智能处理SQL语句中的SET子句,自动去除最后一个逗号。
核心功能:
- 自动添加SET关键字
- 去除最后一个逗号
- 如果没有更新字段,会报错
<!-- 使用set标签优化更新操作 -->
<update id="updateUserSelective" parameterType="User">
UPDATE user
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
<if test="status != null">
status = #{status},
</if>
update_time = NOW()
</set>
WHERE id = #{id}
</update>
foreach循环处理
简要描述:foreach标签用于遍历集合,常用于IN查询、批量插入、批量更新等场景。
基本语法:
<foreach collection="集合名称" item="元素变量名" index="索引变量名"
open="开始字符" close="结束字符" separator="分隔符">
#{item}
</foreach>
实际应用案例:
<!-- IN查询 -->
<select id="selectUsersByIds" parameterType="list" resultType="User">
SELECT id, name, age, email
FROM user
WHERE id IN
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<!-- 批量插入 -->
<insert id="batchInsertUsers" parameterType="list">
INSERT INTO user (name, age, email, create_time)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.email}, NOW())
</foreach>
</insert>
trim自定义处理
简要描述:trim标签是where和set标签的通用版本,提供更灵活的前缀、后缀处理能力。
基本语法:
<trim prefix="前缀" suffix="后缀" prefixOverrides="要去除的前缀" suffixOverrides="要去除的后缀">
SQL片段
</trim>
实际应用案例:
<!-- 等价于where标签 -->
<select id="selectUsers" parameterType="User" resultType="User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</trim>
</select>
<!-- 动态INSERT语句 -->
<insert id="insertUserSelective" parameterType="User">
INSERT INTO user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null">
name,
</if>
<if test="age != null">
age,
</if>
create_time
</trim>
VALUES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null">
#{name},
</if>
<if test="age != null">
#{age},
</if>
NOW()
</trim>
</insert>
bind变量绑定
简要描述:bind标签用于创建变量并绑定到上下文中,常用于字符串拼接、复杂表达式计算等场景。
基本语法:
<bind name="变量名" value="表达式"/>
实际应用案例:
<!-- 模糊查询优化 -->
<select id="selectUsersByName" parameterType="string" resultType="User">
<bind name="pattern" value="'%' + name + '%'"/>
SELECT * FROM user
WHERE name LIKE #{pattern}
</select>
<!-- 复杂字符串处理 -->
<select id="selectUsersByKeyword" parameterType="map" resultType="User">
<bind name="namePattern" value="'%' + keyword + '%'"/>
<bind name="emailPattern" value="'%' + keyword + '%'"/>
SELECT * FROM user
WHERE name LIKE #{namePattern}
OR email LIKE #{emailPattern}
</select>
动态SQL最佳实践
性能优化建议:
- 避免过度使用动态SQL,简单查询直接写静态SQL
- 合理使用缓存,相同条件的动态SQL会生成相同的缓存key
- 注意SQL注入风险,使用#{}而不是${}
代码规范建议:
- 保持SQL的可读性,适当使用换行和缩进
- 复杂的动态逻辑考虑拆分为多个方法
- 使用有意义的变量名和注释
调试技巧:
- 开启SQL日志,观察生成的实际SQL
- 使用MyBatis的SQL构建器进行单元测试
- 注意空值和边界条件的处理