Mybatis学习笔记(一)
这是一份全面的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:直接执行SQLUser 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. 创建缓存KeyCacheKey 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 configurationPUBLIC "-//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> {@Overridepublic 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());}}@Overridepublic UserStatus getResult(ResultSet rs, String columnName) throws SQLException {int code = rs.getInt(columnName);return rs.wasNull() ? null : UserStatus.fromCode(code);}@Overridepublic UserStatus getResult(ResultSet rs, int columnIndex) throws SQLException {int code = rs.getInt(columnIndex);return rs.wasNull() ? null : UserStatus.fromCode(code);}@Overridepublic 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;}@Overridepublic 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);}}}@Overridepublic T getResult(ResultSet rs, String columnName) throws SQLException {String json = rs.getString(columnName);return parseJson(json);}@Overridepublic T getResult(ResultSet rs, int columnIndex) throws SQLException {String json = rs.getString(columnIndex);return parseJson(json);}@Overridepublic 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阈值,单位毫秒@Overridepublic 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;}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic 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();}@Overridepublic 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();// 构建分页SQLString pageSql = buildPageSql(originalSql, page);// 通过反射修改SQLField 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();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic 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 userWHERE 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 DESCLIMIT #{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.websiteFROM user uLEFT JOIN user_profile p ON u.id = p.user_idWHERE 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_timeFROM user uLEFT JOIN orders o ON u.id = o.user_idWHERE 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 uLEFT 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 DESCLIMIT #{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 userWHERE id IN<foreach collection="ids" item="id" open="(" close=")" separator=",">#{id}</foreach>
</select><!-- Array参数映射 -->
<select id="selectByIdArray" resultType="User">SELECT * FROM userWHERE 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 userWHERE user_type = #{userType, jdbcType=VARCHAR, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
</select><!-- JSON参数映射 -->
<select id="selectByJsonCondition" resultType="User">SELECT * FROM userWHERE 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_timeFROM user WHERE id = #{id}
</select><!-- Map映射 -->
<select id="selectUserAsMap" parameterType="long" resultType="map">SELECT id, name, age, emailFROM user WHERE id = #{id}
</select><!-- 集合映射 -->
<select id="selectAllUsers" resultType="User">SELECT id, name, age, email, create_timeFROM user WHERE status = 1ORDER 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, statusFROM 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_nameFROM 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_emailFROM user WHERE user_id = #{id}
</select><!-- 关联查询 -->
<select id="selectUserProfile" parameterType="long" resultType="UserProfile">SELECT profile_id, user_id, avatar, bio, phoneFROM 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.phoneFROM user uLEFT JOIN user_profile p ON u.user_id = p.user_idWHERE 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_emailFROM user WHERE user_id = #{id}
</select><!-- 关联查询 -->
<select id="selectOrdersByUserId" parameterType="long" resultType="Order">SELECT order_id, user_id, order_no, total_amount, create_timeFROM 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_timeFROM user uLEFT JOIN orders o ON u.user_id = o.user_idWHERE 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 emailFROM user WHERE status = 1ORDER BY create_time DESC
</select><!-- 返回Map列表 -->
<select id="selectUserMapList" resultType="map">SELECT user_id, user_name, user_email, create_timeFROM 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 emailFROM 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_ageFROM user WHERE status = 1GROUP BY departmentORDER 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, statusFROM userWHERE 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 userSET 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_timeFROM userWHERE status = 1ORDER 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 userWHERE 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, emailFROM userWHERE 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 userWHERE name LIKE #{pattern}
</select><!-- 复杂字符串处理 -->
<select id="selectUsersByKeyword" parameterType="map" resultType="User"><bind name="namePattern" value="'%' + keyword + '%'"/><bind name="emailPattern" value="'%' + keyword + '%'"/>SELECT * FROM userWHERE name LIKE #{namePattern}OR email LIKE #{emailPattern}
</select>
动态SQL最佳实践
性能优化建议:
- 避免过度使用动态SQL,简单查询直接写静态SQL
- 合理使用缓存,相同条件的动态SQL会生成相同的缓存key
- 注意SQL注入风险,使用#{}而不是${}
代码规范建议:
- 保持SQL的可读性,适当使用换行和缩进
- 复杂的动态逻辑考虑拆分为多个方法
- 使用有意义的变量名和注释
调试技巧:
- 开启SQL日志,观察生成的实际SQL
- 使用MyBatis的SQL构建器进行单元测试
- 注意空值和边界条件的处理