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的最大价值。
核心要点回顾:
- 核心组件:SqlSession生命周期、Mapper代理机制
- 缓存机制:一级缓存和二级缓存的使用场景
- 动态SQL:灵活的条件构建和批量操作优化
- 插件开发:拦截器机制和自定义插件实现
面试建议:
- 理解底层代理机制,不要只停留在使用层面
- 掌握缓存的使用场景和注意事项
- 能够编写高效的动态SQL
- 了解插件开发,体现技术深度
本章核心要点:
- ✅ MyBatis核心组件和SqlSession生命周期
- ✅ 一级缓存和二级缓存机制详解
- ✅ 动态SQL标签和批量操作优化
- ✅ MyBatis-Plus扩展功能使用
- ✅ 插件开发原理和实战案例
下一章预告: 数据库与缓存 - MySQL优化、Redis应用、缓存设计模式
#java面试##面试##java速通##秋招投递攻略##秋招笔面试记录#Java面试圣经 文章被收录于专栏
Java面试圣经