MyBatis 详细介绍以及最佳实践
目录
MyBatis简介
什么是MyBatis
MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程和高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集,使用简单的XML或注解来配置和映射原生信息,将接口和Java的POJOs映射成数据库中的记录。
主要特点
- 灵活性: 支持自定义SQL,不限制数据库表结构
- 简单性: 配置简单,学习成本低
- 性能: 支持缓存,性能优秀
- 动态SQL: 支持动态SQL构建
- 插件机制: 支持自定义插件扩展功能
适用场景
- 复杂查询场景
- 需要优化SQL性能的项目
- 多数据库支持
- 存储过程调用
- 批量操作
核心概念
1. 核心组件
MyBatis架构
├── SqlSessionFactory - 创建SqlSession的工厂
├── SqlSession - 执行SQL的主要接口
├── Executor - SQL执行器
├── StatementHandler - 语句处理器
├── ParameterHandler - 参数处理器
├── ResultSetHandler - 结果集处理器
└── TypeHandler - 类型处理器
2. 配置文件结构
<!-- mybatis-config.xml -->
<configuration>
<properties resource="database.properties"/>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
<typeAliases>
<package name="com.example.entity"/>
</typeAliases>
<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>
<mappers>
<package name="com.example.mapper"/>
</mappers>
</configuration>
使用技巧
1. 动态SQL
if条件判断
<select id="findUsers" resultType="User">
SELECT * FROM users
<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>
</where>
</select>
choose-when-otherwise
<select id="findUsersByCondition" resultType="User">
SELECT * FROM users
<where>
<choose>
<when test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</when>
<when test="email != null and email != ''">
AND email = #{email}
</when>
<otherwise>
AND status = 'ACTIVE'
</otherwise>
</choose>
</where>
</select>
foreach循环
<select id="findUsersByIds" resultType="User">
SELECT * FROM users
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<insert id="batchInsertUsers">
INSERT INTO users (name, email, age) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.name}, #{user.email}, #{user.age})
</foreach>
</insert>
set标签
<update id="updateUser">
UPDATE users
<set>
<if test="name != null">name = #{name},</if>
<if test="email != null">email = #{email},</if>
<if test="age != null">age = #{age},</if>
<if test="status != null">status = #{status},</if>
</set>
WHERE id = #{id}
</update>
2. 结果映射
基本结果映射
<resultMap id="UserResultMap" type="User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<result column="age" property="age"/>
<result column="create_time" property="createTime"/>
</resultMap>
关联映射
<resultMap id="UserWithOrdersResultMap" type="User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<!-- 一对多关联 -->
<collection property="orders" ofType="Order">
<id column="order_id" property="id"/>
<result column="order_no" property="orderNo"/>
<result column="amount" property="amount"/>
</collection>
</resultMap>
<select id="findUserWithOrders" resultMap="UserWithOrdersResultMap">
SELECT
u.id, u.name, u.email,
o.id as order_id, o.order_no, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
嵌套查询
<resultMap id="UserWithOrdersNestedResultMap" type="User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<collection property="orders" select="findOrdersByUserId" column="id"/>
</resultMap>
<select id="findUserWithOrdersNested" resultMap="UserWithOrdersNestedResultMap">
SELECT id, name, email FROM users WHERE id = #{id}
</select>
<select id="findOrdersByUserId" resultType="Order">
SELECT id, order_no, amount FROM orders WHERE user_id = #{userId}
</select>
3. 缓存配置
一级缓存
<!-- 默认开启,作用域为SqlSession -->
<select id="findUserById" resultType="User" useCache="true">
SELECT * FROM users WHERE id = #{id}
</select>
二级缓存
<!-- 在mapper.xml中配置 -->
<cache
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="true"/>
<!-- 在具体查询中使用 -->
<select id="findUserById" resultType="User" useCache="true">
SELECT * FROM users WHERE id = #{id}
</select>
自定义缓存
public class CustomCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new ConcurrentHashMap<>();
public CustomCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public int getSize() {
return cache.size();
}
}
4. 插件开发
分页插件示例
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class PaginationInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(statementHandler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
// 获取分页参数
PageHelper pageHelper = PageHelper.getPageHelper();
if (pageHelper != null) {
String sql = (String) metaObject.getValue("delegate.boundSql.sql");
// 修改SQL添加分页
String pageSql = addPagination(sql, pageHelper.getPageNum(), pageHelper.getPageSize());
metaObject.setValue("delegate.boundSql.sql", pageSql);
}
return invocation.proceed();
}
private String addPagination(String sql, int pageNum, int pageSize) {
int offset = (pageNum - 1) * pageSize;
return sql + " LIMIT " + offset + "," + pageSize;
}
}
重难点解析
1. N+1查询问题
问题描述
在关联查询中,如果使用嵌套查询,可能会导致N+1查询问题,即执行1次主查询和N次关联查询。
解决方案
<!-- 使用JOIN查询替代嵌套查询 -->
<select id="findUsersWithOrders" resultMap="UserWithOrdersResultMap">
SELECT
u.id, u.name, u.email,
o.id as order_id, o.order_no, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
<!-- 使用延迟加载 -->
<resultMap id="UserWithOrdersLazyResultMap" type="User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<collection property="orders" select="findOrdersByUserId" column="id"
fetchType="lazy"/>
</resultMap>
2. 批量操作性能
问题描述
批量插入或更新时,如果使用循环执行单条SQL,性能较差。
解决方案
<!-- 批量插入 -->
<insert id="batchInsertUsers">
INSERT INTO users (name, email, age) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.name}, #{user.email}, #{user.age})
</foreach>
</insert>
<!-- 批量更新 -->
<update id="batchUpdateUsers">
<foreach collection="users" item="user" separator=";">
UPDATE users
SET name = #{user.name}, email = #{user.email}, age = #{user.age}
WHERE id = #{user.id}
</foreach>
</update>
Java代码实现
@Transactional
public void batchInsertUsers(List<User> users) {
if (users == null || users.isEmpty()) {
return;
}
// 分批处理,避免单条SQL过长
int batchSize = 1000;
for (int i = 0; i < users.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, users.size());
List<User> batch = users.subList(i, endIndex);
userMapper.batchInsertUsers(batch);
}
}
3. 动态SQL性能优化
问题描述
动态SQL可能导致SQL语句变化,影响执行计划缓存。
解决方案
<!-- 使用<where>标签避免WHERE子句问题 -->
<select id="findUsers" resultType="User">
SELECT * FROM users
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
<!-- 使用<choose>标签提供默认查询 -->
<select id="findUsersByCondition" resultType="User">
SELECT * FROM users
<where>
<choose>
<when test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</when>
<when test="email != null and email != ''">
AND email = #{email}
</when>
<otherwise>
AND status = 'ACTIVE'
</otherwise>
</choose>
</where>
</select>
4. 事务管理
问题描述
MyBatis本身不提供事务管理,需要与Spring等框架集成。
解决方案
@Service
@Transactional
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(rollbackFor = Exception.class)
public void createUserWithOrders(User user, List<Order> orders) {
// 插入用户
userMapper.insertUser(user);
// 插入订单
for (Order order : orders) {
order.setUserId(user.getId());
orderMapper.insertOrder(order);
}
// 如果出现异常,事务会回滚
}
}
Spring Boot集成
1. 依赖配置
Maven依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
Gradle依赖
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.1'
runtimeOnly 'mysql:mysql-connector-java'
2. 配置文件
application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true
cache-enabled: true
lazy-loading-enabled: true
aggressive-lazy-loading: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3. 主配置类
主应用类
@SpringBootApplication
@MapperScan("com.example.mapper")
public class MyBatisApplication {
public static void main(String[] args) {
SpringApplication.run(MyBatisApplication.class, args);
}
}
4. 实体类
User实体
@Data
@TableName("users")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String email;
private Integer age;
private String status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
// 关联属性
@TableField(exist = false)
private List<Order> orders;
}
5. Mapper接口
UserMapper接口
@Mapper
public interface UserMapper {
// 基本CRUD操作
@Insert("INSERT INTO users(name, email, age, status) VALUES(#{name}, #{email}, #{age}, #{status})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
@Select("SELECT * FROM users WHERE id = #{id}")
User findUserById(Long id);
@Update("UPDATE users SET name=#{name}, email=#{email}, age=#{age}, status=#{status} WHERE id=#{id}")
int updateUser(User user);
@Delete("DELETE FROM users WHERE id = #{id}")
int deleteUser(Long id);
// 复杂查询
List<User> findUsersByCondition(UserQuery query);
// 批量操作
int batchInsertUsers(List<User> users);
int batchUpdateUsers(List<User> users);
// 关联查询
User findUserWithOrders(Long id);
List<User> findUsersWithOrdersByCondition(UserQuery query);
}
6. XML映射文件
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">
<!-- 结果映射 -->
<resultMap id="UserResultMap" type="User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<result column="age" property="age"/>
<result column="status" property="status"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<!-- 基础查询 -->
<select id="findUsersByCondition" resultMap="UserResultMap">
SELECT * FROM users
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 批量插入 -->
<insert id="batchInsertUsers">
INSERT INTO users (name, email, age, status) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.name}, #{user.email}, #{user.age}, #{user.status})
</foreach>
</insert>
<!-- 关联查询 -->
<resultMap id="UserWithOrdersResultMap" type="User" extends="UserResultMap">
<collection property="orders" ofType="Order">
<id column="order_id" property="id"/>
<result column="order_no" property="orderNo"/>
<result column="amount" property="amount"/>
<result column="order_status" property="status"/>
</collection>
</resultMap>
<select id="findUserWithOrders" resultMap="UserWithOrdersResultMap">
SELECT
u.*,
o.id as order_id, o.order_no, o.amount, o.status as order_status
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
</mapper>
7. 服务层
UserService实现
@Service
@Transactional
public class UserService {
@Autowired
private UserMapper userMapper;
public User createUser(User user) {
user.setStatus("ACTIVE");
userMapper.insertUser(user);
return user;
}
public User findUserById(Long id) {
return userMapper.findUserById(id);
}
public List<User> findUsersByCondition(UserQuery query) {
return userMapper.findUsersByCondition(query);
}
public User findUserWithOrders(Long id) {
return userMapper.findUserWithOrders(id);
}
@Transactional(rollbackFor = Exception.class)
public void batchCreateUsers(List<User> users) {
if (users != null && !users.isEmpty()) {
userMapper.batchInsertUsers(users);
}
}
public void updateUser(User user) {
userMapper.updateUser(user);
}
public void deleteUser(Long id) {
userMapper.deleteUser(id);
}
}
具体场景使用
1. 分页查询
分页查询实现
@Service
public class UserService {
public PageResult<User> findUsersByPage(UserQuery query, int pageNum, int pageSize) {
// 计算偏移量
int offset = (pageNum - 1) * pageSize;
// 查询总数
int total = userMapper.countUsersByCondition(query);
// 查询数据
List<User> users = userMapper.findUsersByPage(query, offset, pageSize);
return new PageResult<>(users, total, pageNum, pageSize);
}
}
XML配置
<select id="findUsersByPage" resultMap="UserResultMap">
SELECT * FROM users
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
</where>
ORDER BY create_time DESC
LIMIT #{offset}, #{pageSize}
</select>
<select id="countUsersByCondition" resultType="int">
SELECT COUNT(*) FROM users
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
</where>
</select>
2. 多条件查询
查询条件类
@Data
public class UserQuery {
private String name;
private String email;
private Integer minAge;
private Integer maxAge;
private String status;
private LocalDateTime startTime;
private LocalDateTime endTime;
private List<String> statuses;
}
动态SQL
<select id="findUsersByComplexCondition" resultMap="UserResultMap">
SELECT * FROM users
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="statuses != null and statuses.size() > 0">
AND status IN
<foreach collection="statuses" item="status" open="(" separator="," close=")">
#{status}
</foreach>
</if>
<if test="startTime != null">
AND create_time >= #{startTime}
</if>
<if test="endTime != null">
AND create_time <= #{endTime}
</if>
</where>
ORDER BY create_time DESC
</select>
3. 批量操作
批量插入
@Transactional
public void batchInsertUsers(List<User> users) {
if (users == null || users.isEmpty()) {
return;
}
// 分批处理,避免单条SQL过长
int batchSize = 1000;
for (int i = 0; i < users.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, users.size());
List<User> batch = users.subList(i, endIndex);
userMapper.batchInsertUsers(batch);
}
}
批量更新
<update id="batchUpdateUsers">
<foreach collection="users" item="user" separator=";">
UPDATE users
SET name = #{user.name},
email = #{user.email},
age = #{user.age},
status = #{user.status},
update_time = NOW()
WHERE id = #{user.id}
</foreach>
</update>
4. 存储过程调用
存储过程定义
DELIMITER //
CREATE PROCEDURE GetUserOrders(IN userId INT)
BEGIN
SELECT u.name, u.email, o.order_no, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = userId;
END //
DELIMITER ;
MyBatis调用
<select id="getUserOrdersByProcedure" statementType="CALLABLE" resultMap="UserOrderResultMap">
{call GetUserOrders(#{userId,mode=IN})}
</select>
Java代码
public List<UserOrder> getUserOrdersByProcedure(Long userId) {
Map<String, Object> params = new HashMap<>();
params.put("userId", userId);
userMapper.getUserOrdersByProcedure(params);
return (List<UserOrder>) params.get("result");
}
源码分析
1. 核心执行流程
SqlSession执行流程
// SqlSession执行查询的流程
public class DefaultSqlSession implements SqlSession {
@Override
public <T> T selectOne(String statement, Object parameter) {
// 1. 获取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 2. 执行查询
List<T> list = selectList(statement, parameter);
// 3. 返回结果
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
// 1. 获取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 2. 执行查询
return executor.query(ms, wrapCollection(parameter), RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
}
}
Executor执行流程
public class SimpleExecutor extends BaseExecutor {
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultSetHandler resultSetHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 1. 准备Statement
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultSetHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// 2. 执行查询
return handler.query(stmt, resultSetHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
}
2. 插件机制
插件接口
public interface Interceptor {
// 拦截方法
Object intercept(Invocation invocation) throws Throwable;
// 生成代理对象
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 设置属性
default void setProperties(Properties properties) {
}
}
插件包装
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap)
);
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
}
3. 缓存机制
一级缓存
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap<>();
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
}
二级缓存
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
private <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list);
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
设计模式
1. 代理模式
动态代理
// MyBatis使用动态代理为Mapper接口创建实现类
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
2. 工厂模式
SqlSessionFactory
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
3. 建造者模式
Configuration构建
public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>();
protected final Map<String, ResultMap> resultMaps = new StrictMap<>();
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>();
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>();
protected final Map<String, ResultSetHandler> resultSetHandlers = new StrictMap<>();
protected final Map<String, StatementHandler> statementHandlers = new StrictMap<>();
// 使用建造者模式构建Configuration
public static class Builder {
private Configuration configuration = new Configuration();
public Builder addMapper(Class<?> type) {
configuration.addMapper(type);
return this;
}
public Builder addMappers(String packageName) {
configuration.addMappers(packageName);
return this;
}
public Configuration build() {
return configuration;
}
}
}
4. 策略模式
执行器策略
public enum ExecutorType {
SIMPLE, REUSE, BATCH
}
public class Configuration {
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
5. 模板方法模式
BaseExecutor
public abstract class BaseExecutor implements Executor {
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// 模板方法,子类实现具体的查询逻辑
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultSetHandler resultSetHandler, BoundSql boundSql) throws SQLException;
// 公共方法
protected CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
// ... 其他参数
return cacheKey;
}
}
最佳实践
1. 性能优化
批量操作
// 使用批量操作提高性能
@Transactional
public void batchProcessUsers(List<User> users) {
if (users.size() > 1000) {
// 分批处理
int batchSize = 1000;
for (int i = 0; i < users.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, users.size());
List<User> batch = users.subList(i, endIndex);
userMapper.batchInsertUsers(batch);
}
} else {
userMapper.batchInsertUsers(users);
}
}
延迟加载
<!-- 配置延迟加载 -->
<resultMap id="UserWithOrdersLazyResultMap" type="User">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<collection property="orders" select="findOrdersByUserId" column="id"
fetchType="lazy"/>
</resultMap>
2. 错误处理
异常处理
@Service
public class UserService {
public User findUserById(Long id) {
try {
return userMapper.findUserById(id);
} catch (Exception e) {
log.error("查询用户失败,用户ID: {}", id, e);
throw new ServiceException("查询用户失败", e);
}
}
@Transactional(rollbackFor = Exception.class)
public void createUser(User user) {
try {
userMapper.insertUser(user);
} catch (DuplicateKeyException e) {
log.error("创建用户失败,用户邮箱已存在: {}", user.getEmail(), e);
throw new BusinessException("用户邮箱已存在");
} catch (Exception e) {
log.error("创建用户失败", e);
throw new ServiceException("创建用户失败", e);
}
}
}
3. 日志记录
SQL日志
# application.yml
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 或者使用log4j2
# log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl
自定义日志
@Component
public class SqlPerformanceInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(SqlPerformanceInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
if (duration > 1000) { // 超过1秒的SQL记录警告
logger.warn("SQL执行时间过长: {}ms", duration);
} else if (duration > 100) { // 超过100ms的SQL记录信息
logger.info("SQL执行时间: {}ms", duration);
}
}
}
}
4. 配置管理
环境配置
# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis_demo_dev
username: dev_user
password: dev_password
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-server:3306/mybatis_demo_prod
username: prod_user
password: prod_password
动态配置
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
// 动态配置
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(true);
configuration.setLazyLoadingEnabled(true);
configuration.setAggressiveLazyLoading(false);
factoryBean.setConfiguration(configuration);
return factoryBean.getObject();
}
}
总结
MyBatis是一个功能强大、灵活性高的持久层框架,通过合理使用其特性,可以构建高效、可维护的数据访问层。
关键要点
- 理解核心概念: 掌握SqlSession、Executor、StatementHandler等核心组件
- 熟练使用动态SQL: 灵活处理复杂查询条件
- 合理配置缓存: 提高查询性能
- 掌握插件机制: 扩展框架功能
- 理解设计模式: 深入理解框架架构
应用场景
- 复杂查询: 动态SQL构建多条件查询
- 批量操作: 高效的批量插入和更新
- 关联查询: 灵活的结果映射和关联查询
- 性能优化: 缓存配置和SQL优化
- 扩展开发: 自定义插件和拦截器
技术优势
- 灵活性: 支持自定义SQL和复杂查询
- 性能: 优秀的缓存机制和批量操作
- 可维护性: 清晰的配置和映射结构
- 扩展性: 丰富的插件机制
- 社区支持: 活跃的社区和丰富的文档
通过合理使用MyBatis,可以构建高效、可维护的数据访问层,为应用程序提供强大的数据操作能力。

219

被折叠的 条评论
为什么被折叠?



