7.1 MyBatis核心组件

面试重要程度:⭐⭐⭐⭐⭐

常见提问方式:SqlSession生命周期、Mapper接口代理机制

预计阅读时间:20分钟

开场白

兄弟,MyBatis的核心组件是面试中的高频考点!特别是SqlSession的生命周期管理和Mapper接口的代理机制,这些都是体现你对MyBatis底层原理理解的关键点。

今天我们就把这些核心组件的工作原理彻底搞清楚,让你在面试中展现出对ORM框架的深度理解。

🏗️ SqlSession生命周期

核心组件架构

面试必问:

面试官:"说说MyBatis的核心组件,它们之间是什么关系?"

标准回答框架:

// MyBatis核心组件层次结构
SqlSessionFactoryBuilder  // 构建器(一次性使用)
    ↓
SqlSessionFactory        // 会话工厂(应用级单例)
    ↓
SqlSession              // 会话(线程不安全,请求级)
    ↓
Mapper                  // 映射器(代理对象)

详细实现:

// 1. SqlSessionFactoryBuilder - 构建SqlSessionFactory
public class MyBatisConfig {
    
    public SqlSessionFactory createSqlSessionFactory() throws IOException {
        // 读取配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        
        // 构建SqlSessionFactory(只需要构建一次)
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
            .build(inputStream);
        
        return sqlSessionFactory;
    }
}

// 2. SqlSessionFactory - 创建SqlSession的工厂
@Configuration
public class SpringMyBatisConfig {
    
    @Bean
    @Singleton  // 单例模式,整个应用共享
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setMapperLocations(new PathMatchingResourcePatternResolver()
            .getResources("classpath:mapper/*.xml"));
        
        // 配置MyBatis设置
        org.apache.ibatis.session.Configuration configuration = 
            new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(true);
        factory.setConfiguration(configuration);
        
        return factory.getObject();
    }
}

// 3. SqlSession - 执行SQL的会话
public class UserService {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    // 手动管理SqlSession(不推荐,仅用于理解原理)
    public User findUserById(Long id) {
        SqlSession sqlSession = null;
        try {
            // 创建SqlSession
            sqlSession = sqlSessionFactory.openSession();
            
            // 获取Mapper
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            
            // 执行查询
            return mapper.selectById(id);
            
        } finally {
            // 必须关闭SqlSession
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
    
    // 事务管理示例
    public void transferBalance(Long fromId, Long toId, BigDecimal amount) {
        SqlSession sqlSession = null;
        try {
            // 开启手动提交事务
            sqlSession = sqlSessionFactory.openSession(false);
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            
            // 业务操作
            User fromUser = mapper.selectById(fromId);
            User toUser = mapper.selectById(toId);
            
            fromUser.setBalance(fromUser.getBalance().subtract(amount));
            toUser.setBalance(toUser.getBalance().add(amount));
            
            mapper.updateById(fromUser);
            mapper.updateById(toUser);
            
            // 手动提交事务
            sqlSession.commit();
            
        } catch (Exception e) {
            // 回滚事务
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            throw new RuntimeException("转账失败", e);
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
}

Spring集成后的生命周期管理

最佳实践:

// Spring管理SqlSession生命周期
@Service
@Transactional
public class UserServiceImpl implements UserService {
    
    // Spring自动注入Mapper代理对象
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public User findUserById(Long id) {
        // Spring自动管理SqlSession
        // 1. 从连接池获取数据库连接
        // 2. 创建SqlSession并绑定到当前线程
        // 3. 执行SQL
        // 4. 方法结束后自动关闭SqlSession
        return userMapper.selectById(id);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchUpdateUsers(List<User> users) {
        // 同一个事务中使用同一个SqlSession
        for (User user : users) {
            userMapper.updateById(user);
            // 所有操作在同一个SqlSession中执行
            // 事务结束时统一提交或回滚
        }
    }
    
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void auditLog(String operation) {
        // 新事务,使用新的SqlSession
        auditMapper.insertLog(operation);
    }
}

SqlSession线程安全性:

// 错误示例:SqlSession不是线程安全的
@Component
public class BadExample {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    // ❌ 错误:SqlSession作为成员变量
    private SqlSession sqlSession = sqlSessionFactory.openSession();
    
    public User getUser(Long id) {
        // 多线程访问会出现问题
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectById(id);
    }
}

// 正确示例:每次使用时创建新的SqlSession
@Component
public class GoodExample {
    
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    public User getUser(Long id) {
        // ✅ 正确:每次创建新的SqlSession
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            return mapper.selectById(id);
        }
    }
}

🎭 Mapper接口代理机制

代理机制原理

面试深入:

面试官:"MyBatis是如何通过接口就能执行SQL的?JDK动态代理是怎么工作的?"

核心实现分析:

// 1. MapperRegistry - Mapper注册中心
public class MapperRegistry {
    
    private final Configuration config;
    // 存储Mapper接口和对应的代理工厂
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
    
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                // 为每个Mapper接口创建代理工厂
                knownMappers.put(type, new MapperProxyFactory<>(type));
                
                // 解析Mapper接口的注解和XML配置
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
    
    @SuppressWarnings("unchecked")
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyF

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java面试圣经 文章被收录于专栏

Java面试圣经,带你练透java圣经

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

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