Java秋招之MyBatis与数据访问

第7章 MyBatis与数据访问

面试重要程度:⭐⭐⭐⭐

常见提问方式:MyBatis缓存机制、动态SQL、插件原理

预计阅读时间:35分钟

开场白

兄弟,MyBatis作为Java生态中最流行的持久层框架,在面试中出现频率相当高。特别是它的缓存机制、动态SQL、插件开发等特性,经常被面试官拿来考察你对ORM框架的理解深度。

今天我们就把MyBatis的核心原理搞透,让你在面试中展现出对数据访问层的深度理解。

🏗️ 7.1 MyBatis核心组件

SqlSession生命周期

面试重点:

面试官:"说说MyBatis的核心组件,SqlSession的生命周期是怎样的?"

核心组件架构:

// 1. SqlSessionFactory - 会话工厂(单例)
public class MyBatisConfig {
    
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource());
        factory.setMapperLocations(new PathMatchingResourcePatternResolver()
            .getResources("classpath:mapper/*.xml"));
        return factory.getObject();
    }
}

// 2. SqlSession - 会话(线程不安全,需要每次创建)
public class UserService {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    public User findUserById(Long id) {
        // 手动管理SqlSession(不推荐)
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            return mapper.selectById(id);
        }
    }
}

// 3. Mapper接口 - 映射器(推荐使用)
@Mapper
public interface UserMapper {
    User selectById(@Param("id") Long id);
    List<User> selectByCondition(@Param("condition") UserCondition condition);
    int insert(User user);
    int update(User user);
    int deleteById(@Param("id") Long id);
}

SqlSession生命周期管理:

// Spring集成后的最佳实践
@Service
public class UserService {
    
    // Spring自动管理SqlSession生命周期
    @Autowired
    private UserMapper userMapper;
    
    @Transactional
    public void transferUser(Long fromId, Long toId, BigDecimal amount) {
        // 同一个事务中,使用同一个SqlSession
        User fromUser = userMapper.selectById(fromId);
        User toUser = userMapper.selectById(toId);
        
        fromUser.setBalance(fromUser.getBalance().subtract(amount));
        toUser.setBalance(toUser.getBalance().add(amount));
        
        userMapper.update(fromUser);
        userMapper.update(toUser);
        
        // 事务结束时,SqlSession自动关闭
    }
}

Mapper接口代理机制

面试深入:

面试官:"MyBatis是如何通过接口就能执行SQL的?底层原理是什么?"

代理机制实现:

// MapperProxyFactory - Mapper代理工厂
public class MapperProxyFactory<T> {
    
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
    
    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
    
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        // JDK动态代理创建Mapper实例
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), 
            new Class[] { mapperInterface }, mapperProxy);
    }
}

// MapperProxy - 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 {
            // Object方法直接调用
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (method.isDefault()) {
                // 接口默认方法
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        
        // 获取MapperMethod并执行
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
    
    private MapperMethod cachedMapperMethod(Method method) {
        return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
    }
}

// MapperMethod - 封装方法调用
public class MapperMethod {
    
    private final SqlCommand command;
    private final MethodSignature method;
    
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                } else {
                    Object param = method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(command.getName(), param);
                }
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        return result;
    }
}

🗄️ 7.2 缓存机制深入

一级缓存与二级缓存

面试高频:

面试官:"MyBatis的缓存机制是怎样的?一级缓存和二级缓存有什么区别?"

一级缓存(SqlSession级别):

@Test
public void testFirstLevelCache() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        // 第一次查询,从数据库获取
        User user1 = mapper.selectById(1L);
        System.out.println("第一次查询: " + user1);
        
        // 第二次查询,从一级缓存获取(同一个SqlSession)
        User user2 = mapper.selectById(1L);
        System.out.println("第二次查询: " + user2);
        
        System.out.println("是否为同一对象: " + (user1 == user2)); // true
        
        // 执行更新操作,一级缓存被清空
        mapper.update(new User(1L, "新名称"));
        
        // 第三次查询,重新从数据库获取
        User user3 = mapper.selectById(1L);
        System.out.println("更新后查询: " + user3);
    }
}

二级缓存(Mapper级别):

<!-- 在Mapper.xml中开启二级缓存 -->
<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 开启二级缓存 -->
    <cache 
        eviction="LRU"           <!-- 缓存回收策略 -->
        flushInterval="60000"    <!-- 刷新间隔60秒 -->
        size="512"               <!-- 缓存对象数量 -->
        readOnly="false"/>       <!-- 是否只读 -->
    
    <select id="selectById" resultType="User" useCache="true">
        SELECT * FROM user WHERE id = #{id}
    </select>
    
    <!-- 不使用二级缓存 -->
    <select id="selectSensitiveData" resultType="User" useCache="false">
        SELECT * FROM user WHERE id = #{id}
    </select>
    
</mapper>

缓存配置详解:

// 自定义缓存实现
public class RedisCache implements Cache {
    
    private final String id;
    private final RedisTemplate<String, Object> redisTemplate;
    
    public RedisCache(String id) {
        this.id = id;
        this.redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
    }
    
    @Override
    public String getId() {
        return id;
    }
    
    @Override
    public void putObject(Object key, Object value) {
        String redisKey = getRedisKey(key);
        redisTemplate.opsForValue().set(redisKey, value, 30, TimeUnit.MINUTES);
    }
    
    @Override
    public Object getObject(Object key) {
        String redisKey = getRedisKey(key);
        return redisTemplate.opsForValue().get(redisKey);
    }
    
    @Override
    public Object removeObject(Object key) {
        String redisKey = getRedisKey(key);
        Object value = redisTemplate.opsForValue().get(redisKey);
        redisTemplate.delete(redisKey);
        return value;
    }
    
    @Override
    public void clear() {
        Set<String> keys = redisTemplate.keys(id + ":*");
        if (keys != null && !keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
    }
    
    @Override
    public int getSize() {
        Set<String> keys = redisTemplate.keys(id + ":*");
        return keys != null ? keys.size() : 0;
    }
    
    private String getRedisKey(Object key) {
        return id + ":" + key.toString();
    }
}

缓存失效策略:

// 缓存失效的几种情况
public class CacheInvalidationDemo {
    
    @Test
    public void testCacheInvalidation() {
        // 1. 执行增删改操作,缓存自动失效
        userMapper.insert(new User("张三"));
        userMapper.update(new User(1L, "李四"));
        userMapper.deleteById(2L);
        
        // 2. 手动清除缓存
        sqlSession.clearCache(); // 清除一级缓存
        
        // 3. 跨SqlSession的二级缓存
        try (SqlSession session1 = sqlSessionFactory.openSession()) {
            UserMapper mapper1 = session1.getMapper(UserMapper.class);
            mapper1.selectById(1L); // 查询并缓存
            session1.commit(); // 提交后才会放入二级缓存
        }
        
        try (SqlSession session2 = sqlSessionFactory.openSession()) {
            UserMapper mapper2 = session2.getMapper(UserMapper.class);
            mapper2.selectById(1L); // 从二级缓存获取
        }
    }
}

🔄 7.3 动态SQL与性能优化

动态SQL标签详解

面试重点:

面试官:"MyBatis的动态SQL有哪些标签?如何优化复杂的动态查询?"

核心动态SQL标签:

<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 1. if标签 - 条件判断 -->
    <select id="selectByCondition" resultType="User">
        SELECT * FROM user
        WHERE 1=1
        <if test="name != null and name != ''">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </select>
    
    <!-- 2. where标签 - 智能处理WHERE条件 -->
    <select id="selectByConditionWithWhere" 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="status != null">
                AND status = #{status}
            </if>
        </where>
    </select>
    
    <!-- 3. choose/when/otherwise - 多分支选择 -->
    <select id="selectByPriority" resultType="User">
        SELECT * FROM user
        <where>
            <choose>
                <when test="id != null">
                    id = #{id}
                </when>
                <when test="email != null and email != ''">
                    email = #{email}
                </when>
                <when test="phone != null and phone != ''">
                    phone = #{phone}
                </when>
                <otherwise>
                    status = 1
                </otherwise>
            </choose>
        </where>
    </select>
    
    <!-- 4. foreach标签 - 循环处理 -->
    <select id="selectByIds" resultType="User">
        SELECT * FROM user
        WHERE id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>
    
    <!-- 5. set标签 - 动态更新 -->
    <update id="updateSelective">
        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>
            update_time = NOW()
        </set>
        WHERE id = #{id}
    </update>
    
    <!-- 6. trim标签 - 自定义格式化 -->
    <select id="selectByConditionWithTrim" resultType="User">
        SELECT * FROM user
        <trim prefix="WHERE" prefixOverrides="AND |OR ">
            <if test="name != null and name != ''">
                AND name = #{name}
            </if>
            <if test="age != null">
                AND age = #{age}
            </if>
        </trim>
    </select>
    
</mapper>

复杂动态查询优化:

<!-- 复杂查询条件封装 -->
<sql id="userConditions">
    <where>
        <if test="condition.name != null and condition.name != ''">
            AND name LIKE CONCAT('%', #{condition.name}, '%')
        </if>
        <if test="condition.ageMin != null">
            AND age >= #{condition.ageMin}
        </if>
        <if test="condition.ageMax != null">
            AND age <= #{condition.ageMax}
        </if>
        <if test="condition.statuses != null and condition.statuses.size() > 0">
            AND status IN
            <foreach collection="condition.statuses" item="status" open="(" separator="," close=")">
                #{status}
            </foreach>
        </if>
        <if test="condition.createTimeStart != null">
            AND create_time >= #{condition.createTimeStart}
        </if>
        <if test="condition.createTimeEnd != null">
            AND create_time <= #{condition.createTimeEnd}
        </if>
    </where>
</sql>

<!-- 使用公共条件片段 -->
<select id="selectByComplexCondition" resultType="User">
    SELECT * FROM user
    <include refid="userConditions"/>
    <if test="orderBy != null and orderBy != ''">
        ORDER BY ${orderBy}
    </if>
    <if test="limit != null and limit > 0">
        LIMIT #{offset}, #{limit}
    </if>
</select>

<select id="countByComplexCondition" resultType="int">
    SELECT COUNT(*) FROM user
    <include refid="userConditions"/>
</select>

批量操作优化

批量插入优化:

<!-- 方式1:单条SQL批量插入(推荐) -->
<insert id="batchInsert" 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>

<!-- 方式2:多条SQL批量插入 -->
<insert id="batchInsertMultiple" parameterType="list">
    <foreach collection="list" item="user" separator=";">
        INSERT INTO user (name, age, email, create_time)
        VALUES (#{user.name}, #{user.age}, #{user.email}, NOW())
    </foreach>
</insert>

Java代码批量处理:

@Service
public class UserBatchService {
    
    @Autowired
    private UserMapper userMapper;
    
    // 批量插入优化
    @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.batchInsert(batch);
        }
    }
    
    // 批量更新优化
    @Transactional
    public void batchUpdateUsers(List<User> users) {
        // 使用SqlSession批量模式
        try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            
            for (User user : users) {
                mapper.update(user);
            }
            
            sqlSession.commit();
        }
    }
}

🔌 7.4 MyBatis-Plus扩展功能

代码生成器

面试加分项:

// MyBatis-Plus代码生成器配置
public class CodeGenerator {
    
    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();
        
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("developer");
        gc.setOpen(false);
        gc.setServiceName("%sService"); // 去掉Service接口的首字母I
        mpg.setGlobalConfig(gc);
        
        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("password");
        mpg.setDataSource(dsc);
        
        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.example");
        pc.setEntity("entity");
        pc.setMapper("mapper");
        pc.setService("service");
        pc.setController("controller");
        mpg.setPackageInfo(pc);
        
        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude("user", "order"); // 指定表名
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setControllerMappingHyphenStyle(true);
        mpg.setStrategy(strategy);
        
        mpg.execute();
    }
}

条件构造器

强大的查询构建:

@Service
public class UserQueryService {
    
    @Autowired
    private UserMapper userMapper;
    
    // 复杂条件查询
    public List<User> queryUsers(UserQueryDTO queryDTO) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        
        // 字符串条件
        queryWrapper.like(StringUtils.isNotBlank(queryDTO.getName()), "name", queryDTO.getName())
                   .eq(StringUtils.isNotBlank(queryDTO.getEmail()), "email", queryDTO.getEmail());
        
        // 数值条件
        queryWrapper.ge(queryDTO.getAgeMin() != null, "age", queryDTO.getAgeMin())
                   .le(queryDTO.getAgeMax() != null, "age", queryDTO.getAgeMax());
        
        // 时间范围
        queryWrapper.ge(queryDTO.getCreateTimeStart() != null, "create_time", queryDTO.getCreateTimeStart())
                   .le(queryDTO.getCreateTimeEnd() != null, "create_time", queryDTO.getCreateTimeEnd());
        
        // IN条件
        queryWrapper.in(CollectionUtils.isNotEmpty(queryDTO.getStatuses()), "status", queryDTO.getStatuses());
        
        // 排序
        queryWrapper.orderByDesc("create_time");
        
        return userMapper.selectList(queryWrapper);
    }
    
    // Lambda条件构造器(类型安全)
    public List<User> queryUsersWithLambda(UserQueryDTO queryDTO) {
        LambdaQueryWrapper<User> lambdaQuery = new LambdaQueryWrapper<>();
        
        lambdaQuery.like(StringUtils.isNotBlank(queryDTO.getName()), User::getName, queryDTO.getName())
                  .eq(StringUtils.isNotBlank(queryDTO.getEmail()), User::getEmail, queryDTO.getEmail())
                  .ge(queryDTO.getAgeMin() != null, User::getAge, queryDTO.getAgeMin())
                  .le(queryDTO.getAgeMax() != null, User::getAge, queryDTO.getAgeMax())
                  .in(CollectionUtils.isNotEmpty(queryDTO.getStatuses()), User::getStatus, queryDTO.getStatuses())
                  .orderByDesc(User::getCreateTime);
        
        return userMapper.selectList(lambdaQuery);
    }
    
    // 更新条件构造器
    public boolean updateUserStatus(List<Long> userIds, Integer status) {
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.in("id", userIds)
                    .set("status", status)
                    .set("update_time", LocalDateTime.now());
        
        return userMapper.update(null, updateWrapper) > 0;
    }
}

分页插件

分页查询优化:

// 分页插件配置
@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 分页插件
        PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        paginationInterceptor.setMaxLimit(1000L); // 最大分页数量
        paginationInterceptor.setOverflow(false); // 溢出总页数后是否进行处理
        interceptor.addInnerInterceptor(paginationInterceptor);
        
        return interceptor;
    }
}

// 分页查询使用
@Service
public class UserPageService {
    
    @Autowired
    private UserMapper userMapper;
    
    public IPage<User> pageUsers(int current, int size, UserQueryDTO queryDTO) {
        Page<User> page = new Page<>(current, size);
        
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 构建查询条件...
        
        return userMapper.selectPage(page, queryWrapper);
    }
    
    // 自定义分页查询
    public IPage<UserVO> pageUserVOs(int current, int size, UserQueryDTO queryDTO) {
        Page<UserVO> page = new Page<>(current, size);
        return userMapper.selectUserVOPage(page, queryDTO);
    }
}

// Mapper中的自定义分页
@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    // 自定义分页查询
    IPage<UserVO> selectUserVOPage(Page<UserVO> page, @Param("query") UserQueryDTO queryDTO);
}

💡 百度真题:MyBatis插件开发实战

面试场景:

面试官:"如何开发一个MyBatis插件?比如实现SQL执行时间监控?"

插件开发实现:

// 1. 实现Interceptor接口
@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})
})
@Component
public class SqlExecutionTimeInterceptor implements Interceptor {
    
    private static final Logger logger = LoggerFactory.getLogger(SqlExecutionTimeInterceptor.class);
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        try {
            // 执行原方法
            Object result = invocation.proceed();
            
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            
            // 记录执行时间
            logExecutionTime(invocation, executionTime);
            
            // 慢SQL告警
            if (executionTime > 1000) { // 超过1秒的慢SQL
                handleSlowSql(invocation, executionTime);
            }
            
            return result;
            
        } catch (Exception e) {
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            
            logger.error("SQL执行异常,耗时: {}ms, SQL: {}", executionTime, getSqlInfo(invocation), e);
            throw e;
        }
    }
    
    private void logExecutionTime(Invocation invocation, long executionTime) {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        String sqlId = mappedStatement.getId();
        String sql = getSqlInfo(invocation);
        
        logger.info("SQL执行完成 - ID: {}, 耗时: {}ms, SQL: {}", sqlId, executionTime, sql);
    }
    
    private void handleSlowSql(Invocation invocation, long executionTime) {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        String sqlId = mappedStatement.getId();
        String sql = getSqlInfo(invocation);
        
        logger.warn("慢SQL告警 - ID: {}, 耗时: {}ms, SQL: {}", sqlId, executionTime, sql);
        
        // 发送告警通知
        sendSlowSqlAlert(sqlId, executionTime, sql);
    }
    
    private String getSqlInfo(Invocation invocation) {
        try {
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            Object parameter = invocation.getArgs()[1];
            
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            return boundSql.getSql().replaceAll("\\s+", " ").trim();
        } catch (Exception e) {
            return "无法获取SQL信息";
        }
    }
    
    private void sendSlowSqlAlert(String sqlId, long executionTime, String sql) {
        // 实现告警逻辑:钉钉、邮件、短信等
        // 这里只是示例
        logger.error("慢SQL告警: sqlId={}, executionTime={}ms, sql={}", sqlId, executionTime, sql);
    }
    
    @Override
    public Object plugin(Object target) {
        // 使用Plugin.wrap包装目标对象
        return Plugin.wrap(target, this);
    }
    
    @Override
    public void setProperties(Properties properties) {
        // 设置插件属性
    }
}

// 2. 注册插件
@Configuration
public class MyBatisPluginConfig {
    
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> {
            // 添加插件
            configuration.addInterceptor(new SqlExecutionTimeInterceptor());
        };
    }
}

分页插件实现:

@Intercepts({
    @Signature(type = Executor.class, method = "query", 
               args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class CustomPaginationInterceptor implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        
        // 判断是否需要分页
        if (rowBounds == null || rowBounds == RowBounds.DEFAULT) {
            return invocation.proceed();
        }
        
        // 获取原始SQL
        BoundSql boundSql = ms.getBoundSql(parameter);
        String originalSql = boundSql.getSql();
        
        // 构建分页SQL
        String paginationSql = buildPaginationSql(originalSql, rowBounds);
        
        // 创建新的BoundSql
        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), paginationSql, 
            boundSql.getParameterMappings(), parameter);
        
        // 创建新的MappedStatement
        MappedStatement newMs = copyMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
        args[0] = newMs;
        args[2] = RowBounds.DEFAULT; // 重置RowBounds
        
        return invocation.proceed();
    }
    
    private String buildPaginationSql(String originalSql, RowBounds rowBounds) {
        StringBuilder sql = new StringBuilder(originalSql);
        sql.append(" LIMIT ").append(rowBounds.getOffset()).append(", ").append(rowBounds.getLimit());
        return sql.toString();
    }
    
    private MappedStatement copyMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), 
            newSqlSource, ms.getSqlCommandType());
        
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        
        return builder.build();
    }
    
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    
    @Override
    public void setProperties(Properties properties) {
        // 插件属性配置
    }
    
    // 内部类:BoundSql的SqlSource实现
    public static class BoundSqlSqlSource implements SqlSource {
        private final BoundSql boundSql;
        
        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }
        
        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}

总结

MyBatis作为优秀的持久层框架,其灵活性和可扩展性是面试考察的重点。掌握这些核心原理,不仅能应对面试,更能在实际项目中发挥MyBatis的最大价值。

核心要点回顾:

  1. 核心组件:SqlSession生命周期、Mapper代理机制
  2. 缓存机制:一级缓存和二级缓存的使用场景
  3. 动态SQL:灵活的条件构建和批量操作优化
  4. 插件开发:拦截器机制和自定义插件实现

面试建议:

  • 理解底层代理机制,不要只停留在使用层面
  • 掌握缓存的使用场景和注意事项
  • 能够编写高效的动态SQL
  • 了解插件开发,体现技术深度

本章核心要点:

  • ✅ MyBatis核心组件和SqlSession生命周期
  • ✅ 一级缓存和二级缓存机制详解
  • ✅ 动态SQL标签和批量操作优化
  • ✅ MyBatis-Plus扩展功能使用
  • ✅ 插件开发原理和实战案例

下一章预告: 数据库与缓存 - MySQL优化、Redis应用、缓存设计模式

#java面试##面试##java速通##秋招投递攻略##秋招笔面试记录#
Java面试圣经 文章被收录于专栏

Java面试圣经

全部评论

相关推荐

评论
1
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务