# MyBatis
# 1. 核心概念与概述
MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生类型、接口和Java的POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
# 1.1 MyBatis的基本概念
- SqlSessionFactory:MyBatis的核心接口,负责创建SqlSession实例,通常是线程安全的,一个应用程序只需要一个SqlSessionFactory实例
- SqlSession:代表与数据库的一次会话,提供了执行SQL、获取映射器等方法,通常是非线程安全的,每个线程应该有自己的SqlSession实例
- Mapper接口:定义数据库操作方法的接口,MyBatis会为这些接口创建动态代理实现
- 映射文件:包含SQL语句和映射规则的XML文件,定义了如何将数据库结果映射到Java对象
- 参数映射:将Java对象的属性映射到SQL语句的参数
- 结果映射:将SQL查询结果映射到Java对象
- 动态SQL:根据条件动态生成SQL语句的功能
- 缓存:MyBatis提供的一级缓存和二级缓存机制,提高查询性能
# 1.2 MyBatis的特点
- 简单易用:相比其他ORM框架,MyBatis更加简单直观,学习成本低
- 灵活:MyBatis允许开发者直接编写SQL语句,对SQL的控制力强,适合复杂查询场景
- 映射功能强大:支持多种映射方式,包括XML配置和注解配置
- 动态SQL:提供了丰富的动态SQL标签,可以根据条件动态生成SQL语句
- 缓存机制:提供了一级缓存和二级缓存,提高查询性能
- 低侵入性:MyBatis对应用程序的侵入性低,POJO可以是普通的Java对象,不需要实现特定接口
- 易于集成:MyBatis可以与Spring、Spring Boot等框架轻松集成
# 1.3 MyBatis的应用场景
- 需要灵活控制SQL的场景:当业务逻辑复杂,需要优化SQL性能时
- 遗留系统迁移:当需要将遗留系统迁移到新系统,而数据库结构无法大规模修改时
- 对性能要求高的场景:当需要精确控制SQL执行计划,优化查询性能时
- 多数据库支持:当应用程序需要支持多种数据库时,MyBatis的SQL可以根据不同数据库进行调整
- 存储过程调用:当需要频繁调用存储过程时
# 2. MyBatis架构
# 2.1 整体架构
MyBatis的整体架构分为以下几层:
- API接口层:提供给应用程序调用的接口,如SqlSession接口
- 数据处理层:负责SQL的解析、执行和结果映射
- 基础支撑层:提供连接管理、事务管理、缓存管理等基础功能
MyBatis的核心组件及其关系如下:
应用程序 -> SqlSession -> Executor -> MappedStatement -> SQL执行 -> 结果映射
# 2.2 核心组件详解
# 2.2.1 SqlSessionFactory
SqlSessionFactory是MyBatis的核心接口,负责创建SqlSession实例。SqlSessionFactory通常通过SqlSessionFactoryBuilder来构建,SqlSessionFactoryBuilder读取MyBatis的配置文件,然后创建SqlSessionFactory实例。
SqlSessionFactory的特点:
- 线程安全,多个线程可以共享同一个SqlSessionFactory实例
- 一个应用程序通常只需要一个SqlSessionFactory实例
- 负责创建SqlSession实例
创建SqlSessionFactory的示例:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
# 2.2.2 SqlSession
SqlSession代表与数据库的一次会话,提供了执行SQL、获取映射器等方法。SqlSession是MyBatis与数据库交互的主要接口。
SqlSession的主要方法:
selectOne(String statement, Object parameter)
:执行查询,返回单个结果selectList(String statement, Object parameter)
:执行查询,返回结果列表insert(String statement, Object parameter)
:执行插入操作update(String statement, Object parameter)
:执行更新操作delete(String statement, Object parameter)
:执行删除操作commit()
:提交事务rollback()
:回滚事务getMapper(Class<T> type)
:获取映射器接口的代理实现close()
:关闭SqlSession
SqlSession的使用示例:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 执行SQL操作
User user = session.selectOne("com.example.UserMapper.selectUser", 1);
// 提交事务
session.commit();
}
# 2.2.3 Executor
Executor是MyBatis的内部核心接口,负责执行SQL语句。SqlSession的方法实际上是通过Executor来执行的。MyBatis提供了多种Executor实现:
- SimpleExecutor:默认的Executor实现,每次执行SQL语句都会创建新的Statement
- ReuseExecutor:重用Statement的Executor实现
- BatchExecutor:支持批量执行SQL语句的Executor实现
- CachingExecutor:支持缓存的Executor实现,是其他Executor的装饰器
# 2.2.4 MappedStatement
MappedStatement代表映射的SQL语句,包含了SQL语句、参数映射、结果映射等信息。MappedStatement是从MyBatis的配置文件或注解中解析出来的。
MappedStatement的主要属性:
id
:SQL语句的唯一标识符sqlSource
:SQL语句的源对象,负责生成SQL语句parameterMap
:参数映射信息resultMap
:结果映射信息statementType
:SQL语句的类型(STATEMENT、PREPARED、CALLABLE)fetchSize
:获取结果集的行数timeout
:SQL语句的超时时间keyGenerator
:主键生成器
# 2.2.5 TypeHandler
TypeHandler负责Java类型和JDBC类型之间的转换。MyBatis内置了多种TypeHandler,也支持自定义TypeHandler。
常用的TypeHandler:
IntegerTypeHandler
:处理Integer类型StringTypeHandler
:处理String类型DateTypeHandler
:处理Date类型BooleanTypeHandler
:处理Boolean类型
自定义TypeHandler示例:
public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
private Class<E> type;
public EnumTypeHandler(Class<E> type) {
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.name());
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
return value == null ? null : Enum.valueOf(type, value);
}
// 其他方法实现...
}
# 2.2.6 SqlSource
SqlSource负责生成SQL语句,是MappedStatement的重要组成部分。MyBatis提供了多种SqlSource实现:
- DynamicSqlSource:处理动态SQL的SqlSource实现
- RawSqlSource:处理静态SQL的SqlSource实现
- ProviderSqlSource:处理通过Provider注解生成SQL的SqlSource实现
- StaticSqlSource:处理预编译SQL的SqlSource实现
# 3. 配置MyBatis
# 3.1 配置文件概述
MyBatis的配置文件主要包括以下几个部分:
- properties:属性配置,可以从外部文件加载
- settings:MyBatis的全局配置参数
- typeAliases:类型别名配置,简化类名的使用
- typeHandlers:类型处理器配置
- objectFactory:对象工厂配置
- plugins:插件配置
- environments:环境配置,包含数据源和事务管理器
- databaseIdProvider:数据库标识提供者配置
- mappers:映射器配置,指定映射文件或接口的位置
# 3.2 核心配置示例
# 3.2.1 mybatis-config.xml配置
<?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>
<!-- 属性配置 -->
<properties resource="db.properties"/>
<!-- 设置 -->
<settings>
<!-- 开启驼峰命名转换 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 日志实现 -->
<setting name="logImpl" value="LOG4J"/>
</settings>
<!-- 类型别名 -->
<typeAliases>
<package name="com.example.entity"/>
</typeAliases>
<!-- 类型处理器 -->
<typeHandlers>
<typeHandler handler="com.example.handler.EnumTypeHandler"/>
</typeHandlers>
<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<!-- 事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 映射器 -->
<mappers>
<package name="com.example.mapper"/>
</mappers>
</configuration>
# 3.2.2 db.properties配置
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
# 3.3 Spring集成配置
MyBatis可以与Spring框架轻松集成,通过Spring的依赖注入和事务管理功能,简化MyBatis的使用。
# 3.3.1 Spring XML配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 加载属性文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 数据源配置 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- SqlSessionFactory配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<property name="typeAliasesPackage" value="com.example.entity"/>
</bean>
<!-- MapperScannerConfigurer配置,自动扫描Mapper接口 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- 事务管理器配置 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
# 3.3.2 Spring Boot配置
在Spring Boot中,MyBatis的配置更加简化,可以通过application.properties或application.yml文件进行配置。
application.properties配置:
# 数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
# MyBatis配置
mybatis.config-location=classpath:mybatis-config.xml
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.example.entity
# 日志配置
logging.level.com.example.mapper=debug
Spring Boot主类配置:
@SpringBootApplication
@MapperScan("com.example.mapper") // 扫描Mapper接口
public class MyBatisApplication {
public static void main(String[] args) {
SpringApplication.run(MyBatisApplication.class, args);
}
}
# 4. 映射器(Mapper)
# 4.1 映射器概述
映射器是MyBatis中最重要的组件之一,它定义了数据库操作的接口,MyBatis会为这些接口创建动态代理实现。映射器可以通过XML文件或注解来配置SQL语句和映射规则。
# 4.2 XML映射器
XML映射器是MyBatis中最常用的映射方式,它将SQL语句和映射规则定义在XML文件中。
# 4.2.1 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">
<!-- namespace指定Mapper接口的全限定名 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 结果映射定义 -->
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
<result property="createTime" column="create_time"/>
</resultMap>
<!-- 查询语句 -->
<select id="selectUserById" resultMap="userResultMap">
SELECT * FROM users WHERE id = #{id}
</select>
<!-- 插入语句 -->
<insert id="insertUser" parameterType="User">
INSERT INTO users (username, password, email, create_time)
VALUES (#{username}, #{password}, #{email}, #{createTime})
</insert>
<!-- 更新语句 -->
<update id="updateUser" parameterType="User">
UPDATE users
SET username = #{username},
password = #{password},
email = #{email}
WHERE id = #{id}
</update>
<!-- 删除语句 -->
<delete id="deleteUserById">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>
# 4.2.2 Mapper接口定义
public interface UserMapper {
// 根据ID查询用户
User selectUserById(Integer id);
// 查询所有用户
List<User> selectAllUsers();
// 插入用户
int insertUser(User user);
// 更新用户
int updateUser(User user);
// 根据ID删除用户
int deleteUserById(Integer id);
}
# 4.3 注解映射器
MyBatis也支持使用注解来配置映射器,将SQL语句直接写在接口方法的注解中。
public interface UserMapper {
// 根据ID查询用户
@Select("SELECT * FROM users WHERE id = #{id}")
User selectUserById(Integer id);
// 查询所有用户
@Select("SELECT * FROM users")
List<User> selectAllUsers();
// 插入用户
@Insert("INSERT INTO users (username, password, email, create_time) VALUES (#{username}, #{password}, #{email}, #{createTime})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
// 更新用户
@Update("UPDATE users SET username = #{username}, password = #{password}, email = #{email} WHERE id = #{id}")
int updateUser(User user);
// 根据ID删除用户
@Delete("DELETE FROM users WHERE id = #{id}")
int deleteUserById(Integer id);
}
# 4.4 结果映射
结果映射是MyBatis中非常重要的功能,它负责将SQL查询结果映射到Java对象。
# 4.4.1 简单结果映射
对于简单的情况,MyBatis可以自动映射结果,只需要确保Java对象的属性名与数据库表的列名一致,或者通过配置开启驼峰命名转换。
<!-- 开启驼峰命名转换 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 简单结果映射 -->
<select id="selectUserById" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
# 4.4.2 复杂结果映射
对于复杂的映射关系,如一对一、一对多、多对多等,可以使用resultMap来定义映射规则。
一对一映射示例:
<resultMap id="orderResultMap" type="Order">
<id property="id" column="id"/>
<result property="orderNo" column="order_no"/>
<result property="totalAmount" column="total_amount"/>
<result property="createTime" column="create_time"/>
<!-- 一对一关联映射 -->
<association property="user" javaType="User">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
</association>
</resultMap>
<select id="selectOrderById" resultMap="orderResultMap">
SELECT o.*, u.username, u.email
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.id = #{id}
</select>
一对多映射示例:
<resultMap id="userWithOrdersResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
<!-- 一对多关联映射 -->
<collection property="orders" ofType="Order">
<id property="id" 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="selectUserWithOrders" resultMap="userWithOrdersResultMap">
SELECT u.*, o.id as order_id, o.order_no, o.total_amount, o.create_time as order_create_time
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
# 5. 动态SQL
# 5.1 动态SQL概述
动态SQL是MyBatis的强大特性之一,它允许根据条件动态生成SQL语句,避免了拼接SQL语句的繁琐和安全问题。MyBatis提供了多种动态SQL标签,可以灵活组合使用。
# 5.2 常用动态SQL标签
# 5.2.1 if标签
if标签用于条件判断,根据条件决定是否包含某个SQL片段。
<select id="selectUsers" parameterType="User" resultType="User">
SELECT * FROM users WHERE 1=1
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</select>
# 5.2.2 choose、when、otherwise标签
choose、when、otherwise标签类似于Java中的switch-case语句,用于多条件判断。
<select id="selectUsersByCondition" parameterType="User" resultType="User">
SELECT * FROM users WHERE 1=1
<choose>
<when test="id != null">
AND id = #{id}
</when>
<when test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</when>
<otherwise>
AND create_time > #{createTime}
</otherwise>
</choose>
</select>
# 5.2.3 where、set标签
where标签用于处理SQL语句中的WHERE条件,它会自动去除多余的AND或OR关键字。set标签用于处理SQL语句中的SET子句,它会自动去除多余的逗号。
where标签示例:
<select id="selectUsers" parameterType="User" resultType="User">
SELECT * FROM users
<where>
<if test="username != null and username != ''">
username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</where>
</select>
set标签示例:
<update id="updateUser" parameterType="User">
UPDATE users
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="password != null and password != ''">
password = #{password},
</if>
<if test="email != null and email != ''">
email = #{email}
</if>
</set>
WHERE id = #{id}
</update>
# 5.2.4 foreach标签
foreach标签用于遍历集合,生成IN条件或批量操作的SQL语句。
IN条件示例:
<select id="selectUsersByIds" parameterType="java.util.List" resultType="User">
SELECT * FROM users WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
批量插入示例:
<insert id="batchInsertUsers" parameterType="java.util.List">
INSERT INTO users (username, password, email, create_time)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.password}, #{user.email}, #{user.createTime})
</foreach>
</insert>
# 5.2.5 trim标签
trim标签是一个更灵活的格式化标签,可以自定义前缀、后缀,以及需要去除的多余字符。
<select id="selectUsers" parameterType="User" resultType="User">
SELECT * FROM users
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</trim>
</select>
# 5.3 动态SQL最佳实践
- 合理使用动态SQL标签:根据实际需求选择合适的动态SQL标签
- 避免过于复杂的动态SQL:过于复杂的动态SQL会降低可读性和维护性
- 使用SQL片段:将重复的SQL片段抽取出来,提高复用性
- 注意参数类型:确保传入的参数类型与动态SQL中的测试条件匹配
- 使用OGNL表达式:熟悉OGNL表达式的语法,正确编写条件判断
# 6. 参数处理
# 6.1 参数传递方式
MyBatis支持多种参数传递方式,包括基本类型、POJO、Map、List等。
# 6.1.1 单个参数
当只有一个参数时,MyBatis可以直接使用参数名或任意名称来引用参数。
// Mapper接口
User selectUserById(Integer id);
// XML映射器
<select id="selectUserById" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
# 6.1.2 多个参数
当有多个参数时,MyBatis默认会将参数封装为Map,键为param1、param2...或者使用@Param注解指定参数名。
使用param1、param2...引用参数:
// Mapper接口
User selectUserByUsernameAndPassword(String username, String password);
// XML映射器
<select id="selectUserByUsernameAndPassword" resultType="User">
SELECT * FROM users WHERE username = #{param1} AND password = #{param2}
</select>
使用@Param注解:
// Mapper接口
User selectUserByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
// XML映射器
<select id="selectUserByUsernameAndPassword" resultType="User">
SELECT * FROM users WHERE username = #{username} AND password = #{password}
</select>
# 6.1.3 POJO参数
当参数较多时,可以将参数封装为POJO对象,MyBatis会自动映射POJO的属性到SQL语句的参数中。
// User类
public class User {
private Integer id;
private String username;
private String password;
private String email;
// getter和setter方法...
}
// Mapper接口
int updateUser(User user);
// XML映射器
<update id="updateUser" parameterType="User">
UPDATE users SET username = #{username}, password = #{password}, email = #{email} WHERE id = #{id}
</update>
# 6.1.4 Map参数
也可以使用Map来传递参数,MyBatis会根据Map的键来引用参数值。
// Mapper接口
List<User> selectUsersByMap(Map<String, Object> map);
// XML映射器
<select id="selectUsersByMap" parameterType="java.util.Map" resultType="User">
SELECT * FROM users WHERE username LIKE CONCAT('%', #{username}, '%') AND age > #{age}
</select>
// 使用示例
Map<String, Object> map = new HashMap<>();
map.put("username", "admin");
map.put("age", 18);
List<User> users = userMapper.selectUsersByMap(map);
# 6.1.5 List参数
List参数通常用于批量操作或生成IN条件。
// Mapper接口
List<User> selectUsersByIds(List<Integer> ids);
// XML映射器
<select id="selectUsersByIds" parameterType="java.util.List" resultType="User">
SELECT * FROM users WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
# 6.2 参数类型处理
MyBatis会自动处理Java类型和JDBC类型之间的转换,但在某些情况下,可能需要明确指定参数的JDBC类型。
<insert id="insertUser" parameterType="User">
INSERT INTO users (username, password, email, create_time)
VALUES (#{username, jdbcType=VARCHAR}, #{password, jdbcType=VARCHAR},
#{email, jdbcType=VARCHAR}, #{createTime, jdbcType=TIMESTAMP})
</insert>
# 6.3 特殊字符处理
在SQL语句中,有些特殊字符(如<、>、&等)需要进行转义,或者使用CDATA区段来包含这些字符。
使用转义字符:
<select id="selectUsersByCreateTime" parameterType="java.util.Date" resultType="User">
SELECT * FROM users WHERE create_time > #{createTime}
</select>
使用CDATA区段:
<select id="selectUsersByCreateTime" parameterType="java.util.Date" resultType="User">
<![CDATA[
SELECT * FROM users WHERE create_time > #{createTime}
]]>
</select>
# 7. 缓存机制
# 7.1 缓存概述
MyBatis提供了两级缓存机制,用于提高查询性能:
- 一级缓存:SqlSession级别的缓存,默认开启,同一个SqlSession内的查询会共享缓存
- 二级缓存:Mapper级别的缓存,默认关闭,需要手动开启,可以在多个SqlSession之间共享
# 7.2 一级缓存
一级缓存是SqlSession级别的缓存,每个SqlSession都有自己的缓存区域。当在同一个SqlSession中执行相同的查询时,MyBatis会先从缓存中获取结果,如果缓存中没有,才会执行SQL查询。
一级缓存的特点:
- 默认开启,不需要额外配置
- 作用域是SqlSession,不同的SqlSession之间的缓存相互独立
- 当执行增删改操作或调用SqlSession的clearCache()方法时,一级缓存会被清空
- 当SqlSession关闭时,一级缓存也会被清空
一级缓存示例:
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 第一次查询,从数据库获取数据
User user1 = mapper.selectUserById(1);
// 第二次查询,从一级缓存获取数据
User user2 = mapper.selectUserById(1);
// user1和user2是同一个对象
System.out.println(user1 == user2); // true
// 执行更新操作,清空一级缓存
mapper.updateUser(user1);
session.commit();
// 第三次查询,从数据库获取数据
User user3 = mapper.selectUserById(1);
// user1和user3不是同一个对象
System.out.println(user1 == user3); // false
}
# 7.3 二级缓存
二级缓存是Mapper级别的缓存,多个SqlSession可以共享同一个Mapper的缓存。二级缓存需要手动开启,并且实体类需要实现Serializable接口。
开启二级缓存的步骤:
- 在MyBatis的全局配置文件中开启二级缓存
- 在映射器XML文件中配置cache标签
- 确保实体类实现了Serializable接口
全局配置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
映射器配置:
<mapper namespace="com.example.mapper.UserMapper">
<!-- 配置二级缓存 -->
<cache/>
<!-- 其他SQL语句... -->
</mapper>
实体类实现Serializable接口:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// 属性和方法...
}
二级缓存示例:
// 第一个SqlSession
try (SqlSession session1 = sqlSessionFactory.openSession()) {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.selectUserById(1);
session1.close(); // SqlSession关闭时,数据会被写入二级缓存
}
// 第二个SqlSession
try (SqlSession session2 = sqlSessionFactory.openSession()) {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.selectUserById(1); // 从二级缓存获取数据
}
# 7.4 缓存配置参数
cache标签支持多个配置参数,可以根据需求进行调整:
<cache
eviction="LRU" <!-- 缓存淘汰策略:LRU(默认)、FIFO、SOFT、WEAK -->
flushInterval="60000" <!-- 缓存刷新间隔,单位毫秒 -->
size="1024" <!-- 缓存最多可以存储的对象数量 -->
readOnly="false" <!-- 是否只读 -->
/>
缓存淘汰策略:
- LRU(Least Recently Used):最近最少使用,移除最长时间不被使用的对象
- FIFO(First In First Out):先进先出,按对象进入缓存的顺序移除
- SOFT:软引用,基于JVM的垃圾回收机制和软引用规则移除对象
- WEAK:弱引用,基于JVM的垃圾回收机制和弱引用规则移除对象
# 7.5 缓存最佳实践
- 合理使用缓存:根据业务需求和数据特点,决定是否使用缓存
- 注意缓存一致性:当数据发生变化时,确保缓存被及时更新或清除
- 避免缓存过大:合理设置缓存大小,避免占用过多内存
- 选择合适的淘汰策略:根据数据访问模式,选择合适的缓存淘汰策略
- 禁用不需要缓存的查询:对于频繁变化的数据,可以禁用缓存
- 使用第三方缓存:对于大型应用,可以集成第三方缓存框架,如Ehcache、Redis等
# 8. 插件开发
# 8.1 插件概述
MyBatis的插件机制允许开发者在不修改MyBatis核心代码的情况下,对MyBatis的执行过程进行拦截和扩展。MyBatis插件可以拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler等核心组件的方法。
# 8.2 插件开发步骤
开发MyBatis插件主要包括以下步骤:
- 创建插件类,实现Interceptor接口
- 使用@Intercepts和@Signature注解配置要拦截的方法
- 在MyBatis配置文件中注册插件
插件实现示例:
@Intercepts({
@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
)
})
public class LogInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取方法参数
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
// 获取SQL语句信息
String sqlId = ms.getId();
String sqlCommandType = ms.getSqlCommandType().name();
// 记录日志
System.out.println("执行SQL: " + sqlId + ", 类型: " + sqlCommandType + ", 参数: " + parameter);
// 执行原方法
long startTime = System.currentTimeMillis();
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
// 记录执行时间
System.out.println("SQL执行耗时: " + (endTime - startTime) + "ms");
return result;
}
@Override
public Object plugin(Object target) {
// 使用Plugin.wrap方法生成代理对象
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 可以通过properties获取插件配置的参数
String logLevel = properties.getProperty("logLevel", "INFO");
System.out.println("插件日志级别: " + logLevel);
}
}
插件注册配置:
<configuration>
<!-- 其他配置... -->
<!-- 插件配置 -->
<plugins>
<plugin interceptor="com.example.interceptor.LogInterceptor">
<property name="logLevel" value="DEBUG"/>
</plugin>
</plugins>
<!-- 其他配置... -->
</configuration>
# 8.3 常用插件场景
- SQL执行日志记录:记录SQL语句的执行情况,便于调试和性能分析
- 分页插件:实现物理分页功能,简化分页查询
- 数据脱敏:对敏感数据进行脱敏处理
- 数据权限控制:根据用户权限过滤查询结果
- 性能监控:监控SQL执行性能,识别慢查询
- 参数加密/解密:对敏感参数进行加密存储,查询时解密
# 8.4 插件开发注意事项
- 了解MyBatis的执行流程:在开发插件前,需要了解MyBatis的执行流程和核心组件
- 注意插件的拦截范围:避免拦截过多的方法,影响性能
- 避免循环代理:在plugin方法中,需要判断目标对象是否已经被代理
- 处理好异常:在intercept方法中,需要正确处理异常,避免影响MyBatis的正常执行
- 注意线程安全:插件可能被多个线程同时访问,需要确保线程安全
- 测试插件功能:开发完成后,需要进行充分的测试,确保插件功能正常
# 9. 最佳实践
# 9.1 映射器设计最佳实践
- 合理划分Mapper接口:根据业务模块划分Mapper接口,保持接口的单一职责
- 使用参数注解:对于多参数方法,使用@Param注解指定参数名,提高代码可读性
- 合理使用结果映射:对于复杂的映射关系,使用resultMap定义映射规则
- 避免SQL语句过长:将复杂的SQL语句拆分为多个简单的SQL语句,或使用存储过程
- 使用SQL片段:将重复的SQL片段抽取出来,提高复用性
# 9.2 SQL编写最佳实践
- 使用参数占位符:使用#{}代替${},避免SQL注入
- 优化SQL语句:确保SQL语句高效,使用索引,避免全表扫描
- 使用动态SQL:根据条件动态生成SQL语句,避免拼接SQL
- 注意数据库兼容性:如果需要支持多种数据库,注意SQL语句的兼容性
- 使用别名:为表和列使用别名,提高SQL语句的可读性
# 9.3 性能优化最佳实践
- 使用缓存:合理使用一级缓存和二级缓存,提高查询性能
- 优化连接池:配置合适的连接池参数,避免连接泄露
- 批量操作:使用MyBatis的批量操作功能,减少数据库交互次数
- 延迟加载:对于关联对象,使用延迟加载,减少不必要的查询
- 合理设置fetchSize:根据查询结果集的大小,设置合适的fetchSize参数
- 使用可重用的SqlSession:在Web应用中,可以使用ThreadLocal管理SqlSession
# 9.4 事务管理最佳实践
- 使用Spring事务管理:在Spring环境中,使用Spring的事务管理功能,简化事务处理
- 合理设置事务隔离级别:根据业务需求,设置合适的事务隔离级别
- 控制事务范围:尽量缩小事务范围,避免长时间占用数据库连接
- 处理事务异常:正确处理事务中的异常,确保事务能够正确回滚
- 使用编程式事务或声明式事务:根据实际需求,选择合适的事务管理方式
# 9.5 安全最佳实践
- 防止SQL注入:使用#{}参数占位符,避免使用${}或拼接SQL
- 输入验证:对用户输入进行验证,确保数据的合法性
- 权限控制:在应用层实现细粒度的权限控制,限制用户的操作范围
- 敏感数据保护:对敏感数据进行加密存储,查询时解密
- 日志审计:记录关键操作的日志,便于审计和问题排查
# 10. 实践案例
# 10.1 电商系统用户管理案例
场景描述:某电商系统需要实现用户管理功能,包括用户的注册、登录、查询、更新等操作。
挑战:
- 用户数据量较大,需要优化查询性能
- 需要支持复杂的查询条件,如按用户名、邮箱、注册时间等条件查询
- 对用户密码等敏感信息需要进行加密处理
- 需要确保数据的一致性和安全性
解决方案:
- 使用MyBatis作为持久层框架,实现用户数据的CRUD操作
- 设计合理的Mapper接口和XML映射文件,封装SQL操作
- 使用动态SQL,根据不同的查询条件生成对应的SQL语句
- 对用户密码进行加密存储,使用BCrypt等加密算法
- 配置二级缓存,提高查询性能
- 与Spring集成,使用Spring的事务管理功能
核心代码示例:
User实体类:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String username;
private String password;
private String email;
private String phone;
private Date createTime;
private Date updateTime;
// getter和setter方法...
}
UserMapper接口:
public interface UserMapper {
// 根据ID查询用户
User selectUserById(Integer id);
// 根据用户名查询用户
User selectUserByUsername(String username);
// 根据条件查询用户列表
List<User> selectUsersByCondition(User user);
// 插入用户
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
// 更新用户
int updateUser(User user);
// 根据ID删除用户
int deleteUserById(Integer id);
}
UserMapper.xml映射文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 配置二级缓存 -->
<cache/>
<!-- 结果映射 -->
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
<result property="phone" column="phone"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<!-- 根据ID查询用户 -->
<select id="selectUserById" resultMap="userResultMap">
SELECT * FROM users WHERE id = #{id}
</select>
<!-- 根据用户名查询用户 -->
<select id="selectUserByUsername" resultMap="userResultMap">
SELECT * FROM users WHERE username = #{username}
</select>
<!-- 根据条件查询用户列表 -->
<select id="selectUsersByCondition" parameterType="User" resultMap="userResultMap">
SELECT * FROM users
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="phone != null and phone != ''">
AND phone = #{phone}
</if>
<if test="createTime != null">
AND create_time > #{createTime}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 插入用户 -->
<insert id="insertUser" parameterType="User">
INSERT INTO users (username, password, email, phone, create_time, update_time)
VALUES (#{username}, #{password}, #{email}, #{phone}, #{createTime}, #{updateTime})
</insert>
<!-- 更新用户 -->
<update id="updateUser" parameterType="User">
UPDATE users
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="password != null and password != ''">
password = #{password},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
<if test="phone != null and phone != ''">
phone = #{phone},
</if>
update_time = #{updateTime}
</set>
WHERE id = #{id}
</update>
<!-- 根据ID删除用户 -->
<delete id="deleteUserById">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>
效果:系统成功实现了用户管理功能,支持复杂的查询条件,查询性能良好,数据安全得到保障。
# 10.2 金融系统交易记录查询案例
场景描述:某金融系统需要实现交易记录的查询功能,支持按时间范围、交易类型、金额范围等多种条件查询。
挑战:
- 交易数据量巨大,每天产生数百万条交易记录
- 查询条件复杂,需要支持多维度的组合查询
- 查询性能要求高,响应时间需要控制在毫秒级
- 需要支持分页查询
解决方案:
- 使用MyBatis作为持久层框架,实现交易记录的查询功能
- 设计合理的索引,优化查询性能
- 使用动态SQL,根据不同的查询条件生成最优的SQL语句
- 配置二级缓存,缓存常用的查询结果
- 集成分页插件,简化分页查询
- 使用存储过程处理复杂的统计查询
效果:系统成功实现了交易记录的高效查询,支持多维度的组合查询,查询响应时间控制在毫秒级,满足了业务需求。
# 10.3 物流系统订单跟踪案例
场景描述:某物流系统需要实现订单跟踪功能,记录订单的每一个状态变化,并支持按订单号、时间范围、状态等条件查询订单轨迹。
挑战:
- 订单轨迹数据量大,每个订单可能有多个状态变化记录
- 需要支持按订单号、时间范围、状态等多条件组合查询
- 查询性能要求高,需要快速返回查询结果
- 数据需要实时更新和查询
解决方案:
- 使用MyBatis作为持久层框架,实现订单轨迹的CRUD操作
- 设计合理的数据库表结构,优化查询性能
- 使用动态SQL,根据不同的查询条件生成对应的SQL语句
- 对常用查询字段建立索引
- 与Spring集成,使用Spring的事务管理功能
- 实现订单轨迹的实时更新和查询
效果:系统成功实现了订单跟踪功能,支持多条件组合查询,查询性能良好,满足了业务需求。
# 11. 发展趋势
# 11.1 云原生支持
随着云原生技术的发展,MyBatis也在加强对云环境的支持:
- 提供与云数据库的集成,如RDS、云原生数据库等
- 支持容器化部署,提供Docker镜像
- 支持在Kubernetes环境中运行和管理
- 提供与Serverless架构的集成方案
# 11.2 性能优化
MyBatis持续优化性能,提高系统的吞吐量和响应速度:
- 优化SQL解析和执行过程
- 改进缓存机制,提高缓存命中率
- 优化连接池管理,减少连接创建和销毁的开销
- 提供更高效的批量操作支持
- 优化内存使用,减少GC压力
# 11.3 功能增强
MyBatis不断增强功能,提供更多的便利特性:
- 增强动态SQL功能,提供更多的动态SQL标签
- 提供更丰富的映射功能,支持复杂的数据映射关系
- 增强插件机制,提供更多的扩展点
- 提供更完善的类型处理支持
- 增强与其他框架的集成,如Spring Boot、Quarkus等
# 11.4 安全性增强
随着数据安全越来越受到重视,MyBatis不断加强安全特性:
- 提供更强大的SQL注入防护机制
- 增强数据加密和传输安全
- 提供更细粒度的访问控制
- 支持审计日志,记录所有操作
- 符合更多行业的安全合规要求
# 11.5 生态完善
MyBatis的生态系统不断完善,提供更多的工具和组件:
- 提供官方的代码生成工具MyBatis Generator
- 提供官方的分页插件MyBatis PageHelper
- 提供更多的第三方插件和扩展
- 提供更完善的文档和示例
- 活跃的社区支持和贡献