MyBatis读这篇文章就够啦!

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是一个功能强大、灵活性高的持久层框架,通过合理使用其特性,可以构建高效、可维护的数据访问层。

关键要点

  1. 理解核心概念: 掌握SqlSession、Executor、StatementHandler等核心组件
  2. 熟练使用动态SQL: 灵活处理复杂查询条件
  3. 合理配置缓存: 提高查询性能
  4. 掌握插件机制: 扩展框架功能
  5. 理解设计模式: 深入理解框架架构

应用场景

  • 复杂查询: 动态SQL构建多条件查询
  • 批量操作: 高效的批量插入和更新
  • 关联查询: 灵活的结果映射和关联查询
  • 性能优化: 缓存配置和SQL优化
  • 扩展开发: 自定义插件和拦截器

技术优势

  • 灵活性: 支持自定义SQL和复杂查询
  • 性能: 优秀的缓存机制和批量操作
  • 可维护性: 清晰的配置和映射结构
  • 扩展性: 丰富的插件机制
  • 社区支持: 活跃的社区和丰富的文档

通过合理使用MyBatis,可以构建高效、可维护的数据访问层,为应用程序提供强大的数据操作能力。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值